Storwize: add CG capability to generic groups

This patch adds consistency group capability to generic volume
groups in Storwize driver. Re-use the CG implementations if it
is a CG type group. And if the group being created isn't a CG
then we bail out and the generic volume groups will take care of it.

Implements: blueprint storwize-generic-group
Change-Id: I64a7a29fd6620c3c8ea848c84bd564c9a632d307
This commit is contained in:
Xiaoqin Li 2017-03-13 18:00:11 +08:00
parent a55a6b5c71
commit 103870f40d
5 changed files with 387 additions and 266 deletions

View File

@ -48,6 +48,7 @@ from cinder.volume.drivers.ibm.storwize_svc import storwize_const
from cinder.volume.drivers.ibm.storwize_svc import storwize_svc_common from cinder.volume.drivers.ibm.storwize_svc import storwize_svc_common
from cinder.volume.drivers.ibm.storwize_svc import storwize_svc_fc from cinder.volume.drivers.ibm.storwize_svc import storwize_svc_fc
from cinder.volume.drivers.ibm.storwize_svc import storwize_svc_iscsi from cinder.volume.drivers.ibm.storwize_svc import storwize_svc_iscsi
from cinder.volume import group_types
from cinder.volume import qos_specs from cinder.volume import qos_specs
from cinder.volume import utils as volume_utils from cinder.volume import utils as volume_utils
from cinder.volume import volume_types from cinder.volume import volume_types
@ -3685,54 +3686,55 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
self.driver.delete_volume(volume) self.driver.delete_volume(volume)
self.db.volume_destroy(self.ctxt, volume['id']) self.db.volume_destroy(self.ctxt, volume['id'])
def _create_consistencygroup_in_db(self, **kwargs): def _create_group_in_db(self, **kwargs):
cg = testutils.create_consistencygroup(self.ctxt, **kwargs) cg = testutils.create_group(self.ctxt, **kwargs)
return cg return cg
def _create_consistencegroup(self, **kwargs): def _create_group(self, **kwargs):
cg = self._create_consistencygroup_in_db(**kwargs) grp = self._create_group_in_db(**kwargs)
model_update = self.driver.create_consistencygroup(self.ctxt, cg) model_update = self.driver.create_group(self.ctxt, grp)
self.assertEqual(fields.ConsistencyGroupStatus.AVAILABLE, self.assertEqual(fields.GroupStatus.AVAILABLE,
model_update['status'], model_update['status'],
"CG created failed") "CG created failed")
return cg return grp
def _create_cgsnapshot_in_db(self, cg_id, **kwargs): def _create_group_snapshot_in_db(self, group_id, **kwargs):
cg_snapshot = testutils.create_cgsnapshot(self.ctxt, group_snapshot = testutils.create_group_snapshot(self.ctxt,
consistencygroup_id= cg_id, group_id=group_id,
**kwargs) **kwargs)
snapshots = [] snapshots = []
cg_id = cg_snapshot['consistencygroup_id'] volumes = self.db.volume_get_all_by_generic_group(
volumes = self.db.volume_get_all_by_group(self.ctxt.elevated(), cg_id) self.ctxt.elevated(), group_id)
if not volumes: if not volumes:
msg = _("Consistency group is empty. No cgsnapshot " msg = _("Group is empty. No cgsnapshot will be created.")
"will be created.") raise exception.InvalidGroup(reason=msg)
raise exception.InvalidConsistencyGroup(reason=msg)
for volume in volumes: for volume in volumes:
snapshots.append(testutils.create_snapshot( snapshots.append(testutils.create_snapshot(
self.ctxt, volume['id'], self.ctxt, volume['id'],
cg_snapshot.id, group_snapshot.id,
cg_snapshot.name, group_snapshot.name,
cg_snapshot.id, group_snapshot.id,
fields.SnapshotStatus.CREATING)) fields.SnapshotStatus.CREATING))
return cg_snapshot, snapshots return group_snapshot, snapshots
def _create_cgsnapshot(self, cg_id, **kwargs): def _create_group_snapshot(self, cg_id, **kwargs):
cg_snapshot, snapshots = self._create_cgsnapshot_in_db(cg_id, **kwargs) group_snapshot, snapshots = self._create_group_snapshot_in_db(
cg_id, **kwargs)
model_update, snapshots_model = ( model_update, snapshots_model = (
self.driver.create_cgsnapshot(self.ctxt, cg_snapshot, snapshots)) self.driver.create_group_snapshot(self.ctxt, group_snapshot,
self.assertEqual('available', snapshots))
self.assertEqual(fields.GroupSnapshotStatus.AVAILABLE,
model_update['status'], model_update['status'],
"CGSnapshot created failed") "CGSnapshot created failed")
for snapshot in snapshots_model: for snapshot in snapshots_model:
self.assertEqual(fields.SnapshotStatus.AVAILABLE, self.assertEqual(fields.SnapshotStatus.AVAILABLE,
snapshot['status']) snapshot['status'])
return cg_snapshot, snapshots return group_snapshot, snapshots
def _create_test_vol(self, opts): def _create_test_vol(self, opts):
ctxt = testutils.get_test_admin_context() ctxt = testutils.get_test_admin_context()
@ -4065,8 +4067,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
volume) volume)
# Try to delete a volume that doesn't exist (should not fail) # Try to delete a volume that doesn't exist (should not fail)
vol_no_exist = {'name': 'i_dont_exist', vol_no_exist = self._generate_vol_info(None, None)
'id': '111111'}
self.driver.delete_volume(vol_no_exist) self.driver.delete_volume(vol_no_exist)
# Ensure export for volume that doesn't exist (should not fail) # Ensure export for volume that doesn't exist (should not fail)
self.driver.ensure_export(None, vol_no_exist) self.driver.ensure_export(None, vol_no_exist)
@ -4075,15 +4076,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
self.driver.delete_volume(volume) self.driver.delete_volume(volume)
def test_storwize_svc_volume_name(self): def test_storwize_svc_volume_name(self):
# Create a volume with space in name volume = self._generate_vol_info(None, None)
pool = _get_test_pool()
rand_id = six.text_type(random.randint(10000, 99999))
volume = {'name': 'volume_ space',
'size': 10,
'id': '%s' % rand_id,
'volume_type_id': None,
'mdisk_grp_name': pool,
'host': 'openstack@svc#%s' % pool}
self.driver.create_volume(volume) self.driver.create_volume(volume)
self.driver.ensure_export(None, volume) self.driver.ensure_export(None, volume)
@ -4295,6 +4288,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
each_pool['thin_provisioning_support']) each_pool['thin_provisioning_support'])
self.assertEqual(not is_thin_provisioning_enabled, self.assertEqual(not is_thin_provisioning_enabled,
each_pool['thick_provisioning_support']) each_pool['thick_provisioning_support'])
self.assertTrue(each_pool['consistent_group_snapshot_enabled'])
if self.USESIM: if self.USESIM:
expected = 'storwize-svc-sim' expected = 'storwize-svc-sim'
self.assertEqual(expected, stats['volume_backend_name']) self.assertEqual(expected, stats['volume_backend_name'])
@ -4951,232 +4945,300 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
attrs = self.driver._helpers.get_vdisk_attributes(vol2['name']) attrs = self.driver._helpers.get_vdisk_attributes(vol2['name'])
self.assertIn('openstack2', attrs['mdisk_grp_name']) self.assertIn('openstack2', attrs['mdisk_grp_name'])
# Test groups operation ####
@mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type')
def test_storwize_group_create_with_replication(
self, is_grp_a_cg_snapshot_type):
"""Test group create."""
is_grp_a_cg_snapshot_type.side_effect = True
spec = {'replication_enabled': '<is> True',
'replication_type': '<in> metro'}
rep_type_ref = volume_types.create(self.ctxt, 'rep_type', spec)
rep_group = testutils.create_group(
self.ctxt, group_type_id=fake.GROUP_TYPE_ID,
volume_type_ids=[rep_type_ref['id']])
model_update = self.driver.create_group(self.ctxt, rep_group)
self.assertEqual(fields.GroupStatus.ERROR,
model_update['status'])
self.assertFalse(is_grp_a_cg_snapshot_type.called)
@mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type')
def test_storwize_group_create(self, is_grp_a_cg_snapshot_type):
"""Test group create."""
is_grp_a_cg_snapshot_type.side_effect = [False, True]
group = mock.MagicMock()
self.assertRaises(NotImplementedError,
self.driver.create_group, self.ctxt, group)
model_update = self.driver.create_group(self.ctxt, group)
self.assertEqual(fields.GroupStatus.AVAILABLE,
model_update['status'])
@mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall', @mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall',
new=testutils.ZeroIntervalLoopingCall) new=testutils.ZeroIntervalLoopingCall)
def test_storwize_consistency_group_snapshot(self): @mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type')
cg_type = self._create_consistency_group_volume_type() def test_storwize_delete_group(self, is_grp_a_cg_snapshot_type):
self.ctxt.user_id = fake.USER_ID is_grp_a_cg_snapshot_type.side_effect = [False, True]
self.ctxt.project_id = fake.PROJECT_ID type_ref = volume_types.create(self.ctxt, 'testtype', None)
cg = self._create_consistencygroup_in_db(volume_type_id=cg_type['id']) group = testutils.create_group(self.ctxt,
group_type_id=fake.GROUP_TYPE_ID,
volume_type_id=type_ref['id'])
model_update = self.driver.create_consistencygroup(self.ctxt, cg) self._create_volume(volume_type_id=type_ref['id'], group_id=group.id)
self._create_volume(volume_type_id=type_ref['id'], group_id=group.id)
volumes = self.db.volume_get_all_by_generic_group(
self.ctxt.elevated(), group.id)
self.assertRaises(NotImplementedError,
self.driver.delete_group,
self.ctxt, group, volumes)
self.assertEqual(fields.ConsistencyGroupStatus.AVAILABLE, model_update = self.driver.delete_group(self.ctxt, group, volumes)
model_update['status'], self.assertEqual(fields.GroupStatus.DELETED,
"CG created failed")
volumes = [
self._create_volume(volume_type_id=cg_type['id'],
consistencygroup_id=cg['id']),
self._create_volume(volume_type_id=cg_type['id'],
consistencygroup_id=cg['id']),
self._create_volume(volume_type_id=cg_type['id'],
consistencygroup_id=cg['id'])
]
cg_snapshot, snapshots = self._create_cgsnapshot_in_db(cg['id'])
snapshots = objects.SnapshotList.get_all_for_cgsnapshot(
self.ctxt, cg_snapshot.id)
model_update = self.driver.create_cgsnapshot(self.ctxt, cg_snapshot,
snapshots)
self.assertEqual('available',
model_update[0]['status'],
"CGSnapshot created failed")
for snapshot in model_update[1]:
self.assertEqual(fields.SnapshotStatus.AVAILABLE,
snapshot['status'])
model_update = self.driver.delete_consistencygroup(self.ctxt,
cg, volumes)
self.assertEqual(fields.ConsistencyGroupStatus.DELETED,
model_update[0]['status']) model_update[0]['status'])
for volume in model_update[1]: for volume in model_update[1]:
self.assertEqual('deleted', volume['status']) self.assertEqual('deleted', volume['status'])
@mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type')
def test_storwize_group_update(self, is_grp_a_cg_snapshot_type):
"""Test group update."""
is_grp_a_cg_snapshot_type.side_effect = [False, True]
group = mock.MagicMock()
self.assertRaises(NotImplementedError, self.driver.update_group,
self.ctxt, group, None, None)
(model_update, add_volumes_update,
remove_volumes_update) = self.driver.update_group(self.ctxt, group)
self.assertIsNone(model_update)
self.assertIsNone(add_volumes_update)
self.assertIsNone(remove_volumes_update)
@mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall', @mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall',
new=testutils.ZeroIntervalLoopingCall) new=testutils.ZeroIntervalLoopingCall)
def test_storwize_consistency_group_from_src_invalid(self): @mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type')
# Invalid input case for create cg from src def test_storwize_create_group_snapshot(self, is_grp_a_cg_snapshot_type):
cg_type = self._create_consistency_group_volume_type() is_grp_a_cg_snapshot_type.side_effect = [False, True]
self.ctxt.user_id = fake.USER_ID type_ref = volume_types.create(self.ctxt, 'testtype', None)
self.ctxt.project_id = fake.PROJECT_ID group = testutils.create_group(self.ctxt,
# create cg in db group_type_id=fake.GROUP_TYPE_ID,
cg = self._create_consistencygroup_in_db(volume_type_id=cg_type['id']) volume_type_id=type_ref['id'])
self._create_volume(volume_type_id=type_ref['id'], group_id=group.id)
self._create_volume(volume_type_id=type_ref['id'], group_id=group.id)
group_snapshot, snapshots = self._create_group_snapshot_in_db(
group.id)
self.assertRaises(NotImplementedError,
self.driver.create_group_snapshot,
self.ctxt, group_snapshot, snapshots)
(model_update,
snapshots_model_update) = self.driver.create_group_snapshot(
self.ctxt, group_snapshot, snapshots)
self.assertEqual(fields.GroupSnapshotStatus.AVAILABLE,
model_update['status'],
"CGSnapshot created failed")
for snapshot in snapshots_model_update:
self.assertEqual(fields.SnapshotStatus.AVAILABLE,
snapshot['status'])
@mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall',
new=testutils.ZeroIntervalLoopingCall)
@mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type')
def test_storwize_delete_group_snapshot(self, is_grp_a_cg_snapshot_type):
is_grp_a_cg_snapshot_type.side_effect = [True, False, True]
type_ref = volume_types.create(self.ctxt, 'testtype', None)
group = testutils.create_group(self.ctxt,
group_type_id=fake.GROUP_TYPE_ID,
volume_type_id=type_ref['id'])
self._create_volume(volume_type_id=type_ref['id'], group_id=group.id)
self._create_volume(volume_type_id=type_ref['id'], group_id=group.id)
group_snapshot, snapshots = self._create_group_snapshot(group.id)
self.assertRaises(NotImplementedError,
self.driver.delete_group_snapshot,
self.ctxt, group_snapshot, snapshots)
model_update = self.driver.delete_group_snapshot(self.ctxt,
group_snapshot,
snapshots)
self.assertEqual(fields.GroupSnapshotStatus.DELETED,
model_update[0]['status'])
for volume in model_update[1]:
self.assertEqual(fields.SnapshotStatus.DELETED, volume['status'])
@mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall',
new=testutils.ZeroIntervalLoopingCall)
def test_storwize_create_group_from_src_invalid(self):
# Invalid input case for create group from src
type_ref = volume_types.create(self.ctxt, 'testtype', None)
spec = {'consistent_group_snapshot_enabled': '<is> True'}
cg_type_ref = group_types.create(self.ctxt, 'cg_type', spec)
vg_type_ref = group_types.create(self.ctxt, 'vg_type', None)
# create group in db
group = self._create_group_in_db(volume_type_id=type_ref.id,
group_type_id=vg_type_ref.id)
self.assertRaises(NotImplementedError,
self.driver.create_group_from_src,
self.ctxt, group, None, None, None,
None, None)
group = self._create_group_in_db(volume_type_id=type_ref.id,
group_type_id=cg_type_ref.id)
# create volumes in db # create volumes in db
vol1 = testutils.create_volume(self.ctxt, volume_type_id=cg_type['id'], vol1 = testutils.create_volume(self.ctxt, volume_type_id=type_ref.id,
consistencygroup_id=cg['id']) group_id=group.id)
vol2 = testutils.create_volume(self.ctxt, volume_type_id=cg_type['id'], vol2 = testutils.create_volume(self.ctxt, volume_type_id=type_ref.id,
consistencygroup_id=cg['id']) group_id=group.id)
volumes = [vol1, vol2] volumes = [vol1, vol2]
source_cg = self._create_consistencegroup(volume_type_id=cg_type['id']) source_cg = self._create_group_in_db(volume_type_id=type_ref.id,
group_type_id=cg_type_ref.id)
# Add volumes to source CG # Add volumes to source CG
src_vol1 = self._create_volume(volume_type_id=cg_type['id'], src_vol1 = self._create_volume(volume_type_id=type_ref.id,
consistencygroup_id=source_cg['id']) group_id=source_cg['id'])
src_vol2 = self._create_volume(volume_type_id=cg_type['id'], src_vol2 = self._create_volume(volume_type_id=type_ref.id,
consistencygroup_id=source_cg['id']) group_id=source_cg['id'])
source_vols = [src_vol1, src_vol2] source_vols = [src_vol1, src_vol2]
cgsnapshot, snapshots = self._create_cgsnapshot(source_cg['id']) group_snapshot, snapshots = self._create_group_snapshot(
source_cg['id'], group_type_id=cg_type_ref.id)
# Create cg from src with null input # Create group from src with null input
self.assertRaises(exception.InvalidInput, self.assertRaises(exception.InvalidInput,
self.driver.create_consistencygroup_from_src, self.driver.create_group_from_src,
self.ctxt, cg, volumes, None, None, self.ctxt, group, volumes, None, None,
None, None) None, None)
# Create cg from src with source_cg and empty source_vols # Create cg from src with source_cg and empty source_vols
self.assertRaises(exception.InvalidInput, self.assertRaises(exception.InvalidInput,
self.driver.create_consistencygroup_from_src, self.driver.create_group_from_src,
self.ctxt, cg, volumes, None, None, self.ctxt, group, volumes, None, None,
source_cg, None) source_cg, None)
# Create cg from src with source_vols and empty source_cg # Create cg from src with source_vols and empty source_cg
self.assertRaises(exception.InvalidInput, self.assertRaises(exception.InvalidInput,
self.driver.create_consistencygroup_from_src, self.driver.create_group_from_src,
self.ctxt, cg, volumes, None, None, self.ctxt, group, volumes, None, None,
None, source_vols) None, source_vols)
# Create cg from src with cgsnapshot and empty snapshots # Create cg from src with cgsnapshot and empty snapshots
self.assertRaises(exception.InvalidInput, self.assertRaises(exception.InvalidInput,
self.driver.create_consistencygroup_from_src, self.driver.create_group_from_src,
self.ctxt, cg, volumes, cgsnapshot, None, self.ctxt, group, volumes, group_snapshot, None,
None, None) None, None)
# Create cg from src with snapshots and empty cgsnapshot # Create cg from src with snapshots and empty cgsnapshot
self.assertRaises(exception.InvalidInput, self.assertRaises(exception.InvalidInput,
self.driver.create_consistencygroup_from_src, self.driver.create_group_from_src,
self.ctxt, cg, volumes, None, snapshots, self.ctxt, group, volumes, None, snapshots,
None, None) None, None)
model_update = self.driver.delete_consistencygroup(self.ctxt, model_update = self.driver.delete_group(self.ctxt, group, volumes)
cg, volumes)
self.assertEqual(fields.ConsistencyGroupStatus.DELETED, self.assertEqual(fields.GroupStatus.DELETED,
model_update[0]['status']) model_update[0]['status'])
for volume in model_update[1]: for volume in model_update[1]:
self.assertEqual('deleted', volume['status']) self.assertEqual('deleted', volume['status'])
model_update = ( model_update = self.driver.delete_group(self.ctxt,
self.driver.delete_consistencygroup(self.ctxt, source_cg, source_vols)
source_cg, source_vols))
self.assertEqual(fields.ConsistencyGroupStatus.DELETED, self.assertEqual(fields.GroupStatus.DELETED,
model_update[0]['status']) model_update[0]['status'])
for volume in model_update[1]: for volume in model_update[1]:
self.assertEqual('deleted', volume['status']) self.assertEqual('deleted', volume['status'])
model_update = ( model_update = self.driver.delete_group(self.ctxt,
self.driver.delete_consistencygroup(self.ctxt, group_snapshot, snapshots)
cgsnapshot, snapshots))
self.assertEqual(fields.ConsistencyGroupStatus.DELETED, self.assertEqual(fields.GroupStatus.DELETED,
model_update[0]['status']) model_update[0]['status'])
for volume in model_update[1]: for volume in model_update[1]:
self.assertEqual('deleted', volume['status']) self.assertEqual('deleted', volume['status'])
@mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall', @mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall',
new=testutils.ZeroIntervalLoopingCall) new=testutils.ZeroIntervalLoopingCall)
def test_storwize_consistency_group_from_src(self): def test_storwize_group_from_src(self):
# Valid case for create cg from src # Valid case for create cg from src
cg_type = self._create_consistency_group_volume_type() type_ref = volume_types.create(self.ctxt, 'testtype', None)
self.ctxt.user_id = fake.USER_ID spec = {'consistent_group_snapshot_enabled': '<is> True'}
self.ctxt.project_id = fake.PROJECT_ID cg_type_ref = group_types.create(self.ctxt, 'cg_type', spec)
pool = _get_test_pool() pool = _get_test_pool()
# Create cg in db # Create cg in db
cg = self._create_consistencygroup_in_db(volume_type_id=cg_type['id']) group = self._create_group_in_db(volume_type_id=type_ref.id,
group_type_id=cg_type_ref.id)
# Create volumes in db # Create volumes in db
testutils.create_volume(self.ctxt, volume_type_id=cg_type['id'], testutils.create_volume(self.ctxt, volume_type_id=type_ref.id,
consistencygroup_id=cg['id'], group_id=group.id,
host='openstack@svc#%s' % pool) host='openstack@svc#%s' % pool)
testutils.create_volume(self.ctxt, volume_type_id=cg_type['id'], testutils.create_volume(self.ctxt, volume_type_id=type_ref.id,
consistencygroup_id=cg['id'], consistencygroup_id=group.id,
host='openstack@svc#%s' % pool) host='openstack@svc#%s' % pool)
volumes = ( volumes = self.db.volume_get_all_by_generic_group(
self.db.volume_get_all_by_group(self.ctxt.elevated(), cg['id'])) self.ctxt.elevated(), group.id)
# Create source CG # Create source CG
source_cg = self._create_consistencegroup(volume_type_id=cg_type['id']) source_cg = self._create_group_in_db(volume_type_id=type_ref.id,
group_type_id=cg_type_ref.id)
# Add volumes to source CG # Add volumes to source CG
self._create_volume(volume_type_id=cg_type['id'], self._create_volume(volume_type_id=type_ref.id,
consistencygroup_id=source_cg['id']) group_id=source_cg['id'])
self._create_volume(volume_type_id=cg_type['id'], self._create_volume(volume_type_id=type_ref.id,
consistencygroup_id=source_cg['id']) group_id=source_cg['id'])
source_vols = self.db.volume_get_all_by_group( source_vols = self.db.volume_get_all_by_generic_group(
self.ctxt.elevated(), source_cg['id']) self.ctxt.elevated(), source_cg['id'])
# Create cgsnapshot # Create cgsnapshot
cgsnapshot, snapshots = self._create_cgsnapshot(source_cg['id']) group_snapshot, snapshots = self._create_group_snapshot(
source_cg['id'], group_type_id=cg_type_ref.id)
# Create cg from source cg # Create cg from source cg
model_update, volumes_model_update = ( model_update, volumes_model_update = (
self.driver.create_consistencygroup_from_src(self.ctxt, self.driver.create_group_from_src(self.ctxt, group, volumes, None,
cg, None, source_cg, source_vols))
volumes, self.assertEqual(fields.GroupStatus.AVAILABLE,
None, None,
source_cg,
source_vols))
self.assertEqual(fields.ConsistencyGroupStatus.AVAILABLE,
model_update['status'], model_update['status'],
"CG create from src created failed") "CG create from src created failed")
for each_vol in volumes_model_update: for each_vol in volumes_model_update:
self.assertEqual('available', each_vol['status']) self.assertEqual('available', each_vol['status'])
model_update = self.driver.delete_consistencygroup(self.ctxt,
cg,
volumes)
self.assertEqual(fields.ConsistencyGroupStatus.DELETED, model_update = self.driver.delete_group(self.ctxt, group, volumes)
self.assertEqual(fields.GroupStatus.DELETED,
model_update[0]['status']) model_update[0]['status'])
for each_vol in model_update[1]: for each_vol in model_update[1]:
self.assertEqual('deleted', each_vol['status']) self.assertEqual('deleted', each_vol['status'])
# Create cg from cg snapshot # Create cg from cg snapshot
model_update, volumes_model_update = ( model_update, volumes_model_update = (
self.driver.create_consistencygroup_from_src(self.ctxt, self.driver.create_group_from_src(self.ctxt, group, volumes,
cg, group_snapshot, snapshots,
volumes,
cgsnapshot,
snapshots,
None, None)) None, None))
self.assertEqual(fields.ConsistencyGroupStatus.AVAILABLE, self.assertEqual(fields.GroupStatus.AVAILABLE,
model_update['status'], model_update['status'],
"CG create from src created failed") "CG create from src created failed")
for each_vol in volumes_model_update: for each_vol in volumes_model_update:
self.assertEqual('available', each_vol['status']) self.assertEqual('available', each_vol['status'])
model_update = self.driver.delete_consistencygroup(self.ctxt, model_update = self.driver.delete_group(self.ctxt, group, volumes)
cg, volumes) self.assertEqual(fields.GroupStatus.DELETED,
self.assertEqual(fields.ConsistencyGroupStatus.DELETED,
model_update[0]['status']) model_update[0]['status'])
for each_vol in model_update[1]: for each_vol in model_update[1]:
self.assertEqual('deleted', each_vol['status']) self.assertEqual('deleted', each_vol['status'])
model_update = self.driver.delete_consistencygroup(self.ctxt, model_update = self.driver.delete_group_snapshot(self.ctxt,
cgsnapshot, group_snapshot,
snapshots) snapshots)
self.assertEqual(fields.GroupStatus.DELETED,
self.assertEqual(fields.ConsistencyGroupStatus.DELETED,
model_update[0]['status']) model_update[0]['status'])
for volume in model_update[1]: for volume in model_update[1]:
self.assertEqual('deleted', volume['status']) self.assertEqual('deleted', volume['status'])
model_update = self.driver.delete_consistencygroup(self.ctxt,
source_cg,
source_vols)
self.assertEqual(fields.ConsistencyGroupStatus.DELETED,
model_update[0]['status'])
for each_vol in model_update[1]:
self.assertEqual('deleted', each_vol['status'])
def _create_volume_type_qos(self, extra_specs, fake_qos): def _create_volume_type_qos(self, extra_specs, fake_qos):
# Generate a QoS volume type for volume. # Generate a QoS volume type for volume.
if extra_specs: if extra_specs:
@ -5871,27 +5933,20 @@ class StorwizeSVCReplicationTestCase(test.TestCase):
is_vol_defined = self.driver._helpers.is_vdisk_defined(name) is_vol_defined = self.driver._helpers.is_vdisk_defined(name)
self.assertEqual(exists, is_vol_defined) self.assertEqual(exists, is_vol_defined)
def _generate_vol_info(self, vol_name, vol_id, vol_type=None): def _generate_vol_info(self, vol_type=None, size=1):
pool = _get_test_pool() pool = _get_test_pool()
rand_id = six.text_type(random.randint(10000, 99999)) volume_type = vol_type if vol_type else self.non_replica_type
volume_type = self.non_replica_type prop = {'size': size,
if vol_type: 'volume_type_id': volume_type.id,
volume_type = vol_type 'host': 'openstack@svc#%s' % pool
if vol_name: }
return {'name': 'snap_volume%s' % rand_id, vol = testutils.create_volume(self.ctxt, **prop)
'volume_name': vol_name, return vol
'id': rand_id,
'volume_id': vol_id, def _generate_snap_info(self, vol_id):
'volume_size': 10, prop = {'volume_id': vol_id}
'mdisk_grp_name': pool} snap = testutils.create_snapshot(self.ctxt, **prop)
else: return snap
return {'name': 'test_volume%s' % rand_id,
'size': 10,
'id': '%s' % rand_id,
'mdisk_grp_name': pool,
'host': 'openstack@svc#%s' % pool,
'volume_type_id': volume_type['id'],
'volume_type': volume_type}
def _create_replica_volume_type(self, enable, def _create_replica_volume_type(self, enable,
rep_type=storwize_const.METRO): rep_type=storwize_const.METRO):
@ -5910,8 +5965,7 @@ class StorwizeSVCReplicationTestCase(test.TestCase):
type_name = "non_rep" type_name = "non_rep"
type_ref = volume_types.create(self.ctxt, type_name, spec) type_ref = volume_types.create(self.ctxt, type_name, spec)
replication_type = objects.VolumeType.get_by_id(self.ctxt,
replication_type = volume_types.get_volume_type(self.ctxt,
type_ref['id']) type_ref['id'])
return replication_type return replication_type
@ -5923,14 +5977,7 @@ class StorwizeSVCReplicationTestCase(test.TestCase):
self.non_replica_type = self._create_replica_volume_type(False) self.non_replica_type = self._create_replica_volume_type(False)
def _create_test_volume(self, rep_type): def _create_test_volume(self, rep_type):
volume = self._generate_vol_info(None, None, rep_type) volume = self._generate_vol_info(rep_type)
opts = {}
for p in volume.keys():
if p not in volume:
opts[p] = volume[p]
vol = testutils.create_volume(self.ctxt, **opts)
volume['id'] = vol['id']
volume['name'] = vol['name']
model_update = self.driver.create_volume(volume) model_update = self.driver.create_volume(volume)
volume['status'] = 'available' volume['status'] = 'available'
return volume, model_update return volume, model_update
@ -6054,10 +6101,10 @@ class StorwizeSVCReplicationTestCase(test.TestCase):
vol1, model_update = self._create_test_volume(self.mm_type) vol1, model_update = self._create_test_volume(self.mm_type)
self.assertEqual('enabled', model_update['replication_status']) self.assertEqual('enabled', model_update['replication_status'])
snap = self._generate_vol_info(vol1['name'], vol1['id']) snap = testutils.create_snapshot(self.ctxt, vol1.id)
self.driver.create_snapshot(snap) self.driver.create_snapshot(snap)
vol2 = self._generate_vol_info(None, None, self.mm_type) vol2 = self._generate_vol_info(self.mm_type)
model_update = self.driver.create_volume_from_snapshot(vol2, snap) model_update = self.driver.create_volume_from_snapshot(vol2, snap)
self.assertEqual('enabled', model_update['replication_status']) self.assertEqual('enabled', model_update['replication_status'])
self._validate_replic_vol_creation(vol2) self._validate_replic_vol_creation(vol2)
@ -6076,7 +6123,7 @@ class StorwizeSVCReplicationTestCase(test.TestCase):
src_volume, model_update = self._create_test_volume(self.mm_type) src_volume, model_update = self._create_test_volume(self.mm_type)
self.assertEqual('enabled', model_update['replication_status']) self.assertEqual('enabled', model_update['replication_status'])
volume = self._generate_vol_info(None, None, self.mm_type) volume = self._generate_vol_info(self.mm_type)
# Create a cloned volume from source volume. # Create a cloned volume from source volume.
model_update = self.driver.create_cloned_volume(volume, src_volume) model_update = self.driver.create_cloned_volume(volume, src_volume)
@ -6177,7 +6224,7 @@ class StorwizeSVCReplicationTestCase(test.TestCase):
non_rep_volume, model_update = self._create_test_volume( non_rep_volume, model_update = self._create_test_volume(
self.non_replica_type) self.non_replica_type)
new_volume = self._generate_vol_info(None, None) new_volume = self._generate_vol_info()
ref = {'source-name': rep_volume['name']} ref = {'source-name': rep_volume['name']}
new_volume['volume_type_id'] = self.non_replica_type['id'] new_volume['volume_type_id'] = self.non_replica_type['id']
@ -6213,7 +6260,7 @@ class StorwizeSVCReplicationTestCase(test.TestCase):
uid_of_aux = self._get_vdisk_uid( uid_of_aux = self._get_vdisk_uid(
storwize_const.REPLICA_AUX_VOL_PREFIX + rep_volume['name']) storwize_const.REPLICA_AUX_VOL_PREFIX + rep_volume['name'])
new_volume = self._generate_vol_info(None, None) new_volume = self._generate_vol_info()
ref = {'source-name': rep_volume['name']} ref = {'source-name': rep_volume['name']}
new_volume['volume_type_id'] = self.mm_type['id'] new_volume['volume_type_id'] = self.mm_type['id']
new_volume['volume_type'] = self.mm_type new_volume['volume_type'] = self.mm_type
@ -6692,7 +6739,7 @@ class StorwizeSVCReplicationTestCase(test.TestCase):
'start_relationship') 'start_relationship')
def test_sync_replica_volumes_with_aux(self, start_relationship): def test_sync_replica_volumes_with_aux(self, start_relationship):
# Create metro mirror replication. # Create metro mirror replication.
mm_vol = self._generate_vol_info(None, None, self.mm_type) mm_vol = self._generate_vol_info(self.mm_type)
tgt_volume = storwize_const.REPLICA_AUX_VOL_PREFIX + mm_vol['name'] tgt_volume = storwize_const.REPLICA_AUX_VOL_PREFIX + mm_vol['name']
volumes = [mm_vol] volumes = [mm_vol]
@ -6741,7 +6788,7 @@ class StorwizeSVCReplicationTestCase(test.TestCase):
new=testutils.ZeroIntervalLoopingCall) new=testutils.ZeroIntervalLoopingCall)
def test_wait_replica_vol_ready(self, get_relationship_info): def test_wait_replica_vol_ready(self, get_relationship_info):
# Create metro mirror replication. # Create metro mirror replication.
mm_vol = self._generate_vol_info(None, None, self.mm_type) mm_vol = self._generate_vol_info(self.mm_type)
fake_info = {'volume': 'fake', fake_info = {'volume': 'fake',
'master_vdisk_name': 'fake', 'master_vdisk_name': 'fake',
'aux_vdisk_name': 'fake', 'aux_vdisk_name': 'fake',

View File

@ -1343,7 +1343,7 @@ class StorwizeHelpers(object):
def run_consistgrp_snapshots(self, fc_consistgrp, snapshots, state, def run_consistgrp_snapshots(self, fc_consistgrp, snapshots, state,
config, timeout): config, timeout):
model_update = {'status': fields.ConsistencyGroupStatus.AVAILABLE} model_update = {'status': fields.GroupSnapshotStatus.AVAILABLE}
snapshots_model_update = [] snapshots_model_update = []
try: try:
for snapshot in snapshots: for snapshot in snapshots:
@ -1362,7 +1362,7 @@ class StorwizeHelpers(object):
# Cinder general will maintain the CG and snapshots relationship. # Cinder general will maintain the CG and snapshots relationship.
self.delete_fc_consistgrp(fc_consistgrp) self.delete_fc_consistgrp(fc_consistgrp)
except exception.VolumeBackendAPIException as err: except exception.VolumeBackendAPIException as err:
model_update['status'] = fields.ConsistencyGroupStatus.ERROR model_update['status'] = fields.GroupSnapshotStatus.ERROR
# Release cg # Release cg
self.delete_fc_consistgrp(fc_consistgrp) self.delete_fc_consistgrp(fc_consistgrp)
LOG.error("Failed to create CGSnapshot. " LOG.error("Failed to create CGSnapshot. "
@ -1377,7 +1377,7 @@ class StorwizeHelpers(object):
def delete_consistgrp_snapshots(self, fc_consistgrp, snapshots): def delete_consistgrp_snapshots(self, fc_consistgrp, snapshots):
"""Delete flashcopy maps and consistent group.""" """Delete flashcopy maps and consistent group."""
model_update = {'status': fields.ConsistencyGroupStatus.DELETED} model_update = {'status': fields.GroupSnapshotStatus.DELETED}
snapshots_model_update = [] snapshots_model_update = []
try: try:
@ -1385,7 +1385,7 @@ class StorwizeHelpers(object):
self.ssh.rmvdisk(snapshot['name'], True) self.ssh.rmvdisk(snapshot['name'], True)
except exception.VolumeBackendAPIException as err: except exception.VolumeBackendAPIException as err:
model_update['status'] = ( model_update['status'] = (
fields.ConsistencyGroupStatus.ERROR_DELETING) fields.GroupSnapshotStatus.ERROR_DELETING)
LOG.error("Failed to delete the snapshot %(snap)s of " LOG.error("Failed to delete the snapshot %(snap)s of "
"CGSnapshot. Exception: %(exception)s.", "CGSnapshot. Exception: %(exception)s.",
{'snap': snapshot['name'], 'exception': err}) {'snap': snapshot['name'], 'exception': err})
@ -1429,7 +1429,7 @@ class StorwizeHelpers(object):
LOG.debug('Enter: create_cg_from_source: cg %(cg)s' LOG.debug('Enter: create_cg_from_source: cg %(cg)s'
' source %(source)s, target %(target)s', ' source %(source)s, target %(target)s',
{'cg': fc_consistgrp, 'source': sources, 'target': targets}) {'cg': fc_consistgrp, 'source': sources, 'target': targets})
model_update = {'status': fields.ConsistencyGroupStatus.AVAILABLE} model_update = {'status': fields.GroupStatus.AVAILABLE}
ctxt = context.get_admin_context() ctxt = context.get_admin_context()
try: try:
for source, target in zip(sources, targets): for source, target in zip(sources, targets):
@ -1447,7 +1447,7 @@ class StorwizeHelpers(object):
volumes_model_update = self._get_volume_model_updates( volumes_model_update = self._get_volume_model_updates(
ctxt, targets, group['id'], model_update['status']) ctxt, targets, group['id'], model_update['status'])
except exception.VolumeBackendAPIException as err: except exception.VolumeBackendAPIException as err:
model_update['status'] = fields.ConsistencyGroupStatus.ERROR model_update['status'] = fields.GroupStatus.ERROR
volumes_model_update = self._get_volume_model_updates( volumes_model_update = self._get_volume_model_updates(
ctxt, targets, group['id'], model_update['status']) ctxt, targets, group['id'], model_update['status'])
with excutils.save_and_reraise_exception(): with excutils.save_and_reraise_exception():
@ -3065,13 +3065,13 @@ class StorwizeSVCCommonDriver(san.SanDriver,
raise exception.InvalidInput(reason=msg) raise exception.InvalidInput(reason=msg)
return replication_type return replication_type
def _get_volume_replicated_type(self, ctxt, volume): def _get_volume_replicated_type(self, ctxt, volume, vol_type_id=None):
replication_type = None replication_type = None
if volume.get("volume_type_id"): volume_type = (volume.volume_type if volume else
volume_type = volume_types.get_volume_type( objects.VolumeType.get_by_name_or_id(ctxt,
ctxt, volume["volume_type_id"]) vol_type_id))
if volume_type:
replication_type = self._get_specs_replicated_type(volume_type) replication_type = self._get_specs_replicated_type(volume_type)
return replication_type return replication_type
def _get_storwize_config(self): def _get_storwize_config(self):
@ -3479,23 +3479,49 @@ class StorwizeSVCCommonDriver(san.SanDriver,
return self._stats return self._stats
def create_consistencygroup(self, context, group): # Add CG capability to generic volume groups
"""Create a consistency group. def create_group(self, context, group):
"""Creates a group.
IBM Storwize will create CG until cg-snapshot creation, :param context: the context of the caller.
db will maintain the volumes and CG relationship. :param group: the group object.
:returns: model_update
""" """
LOG.debug("Creating consistency group.")
model_update = {'status': fields.ConsistencyGroupStatus.AVAILABLE} LOG.debug("Creating group.")
model_update = {'status': fields.GroupStatus.AVAILABLE}
for vol_type_id in group.volume_type_ids:
replication_type = self._get_volume_replicated_type(
context, None, vol_type_id)
if replication_type:
# An unsupported configuration
LOG.error('Unable to create group: create group with '
'replication volume type is not supported.')
model_update = {'status': fields.GroupStatus.ERROR}
return model_update return model_update
def delete_consistencygroup(self, context, group, volumes): if utils.is_group_a_cg_snapshot_type(group):
"""Deletes a consistency group. return {'status': fields.GroupStatus.AVAILABLE}
# we'll rely on the generic group implementation if it is not a
# consistency group request.
raise NotImplementedError()
IBM Storwize will delete the volumes of the CG. def delete_group(self, context, group, volumes):
"""Deletes a group.
:param context: the context of the caller.
:param group: the group object.
:param volumes: a list of volume objects in the group.
:returns: model_update, volumes_model_update
""" """
LOG.debug("Deleting consistency group.") LOG.debug("Deleting group.")
model_update = {'status': fields.ConsistencyGroupStatus.DELETED} if not utils.is_group_a_cg_snapshot_type(group):
# we'll rely on the generic group implementation if it is
# not a consistency group request.
raise NotImplementedError()
model_update = {'status': fields.GroupStatus.DELETED}
volumes_model_update = [] volumes_model_update = []
for volume in volumes: for volume in volumes:
@ -3505,51 +3531,69 @@ class StorwizeSVCCommonDriver(san.SanDriver,
{'id': volume['id'], 'status': 'deleted'}) {'id': volume['id'], 'status': 'deleted'})
except exception.VolumeBackendAPIException as err: except exception.VolumeBackendAPIException as err:
model_update['status'] = ( model_update['status'] = (
fields.ConsistencyGroupStatus.ERROR_DELETING) fields.GroupStatus.ERROR_DELETING)
LOG.error("Failed to delete the volume %(vol)s of CG. " LOG.error("Failed to delete the volume %(vol)s of CG. "
"Exception: %(exception)s.", "Exception: %(exception)s.",
{'vol': volume['name'], 'exception': err}) {'vol': volume['name'], 'exception': err})
volumes_model_update.append( volumes_model_update.append(
{'id': volume['id'], 'status': 'error_deleting'}) {'id': volume['id'],
'status': fields.GroupStatus.ERROR_DELETING})
return model_update, volumes_model_update return model_update, volumes_model_update
def update_consistencygroup(self, ctxt, group, add_volumes, def update_group(self, context, group, add_volumes=None,
remove_volumes): remove_volumes=None):
"""Adds or removes volume(s) to/from an existing consistency group.""" """Updates a group.
LOG.debug("Updating consistency group.")
return None, None, None
def create_consistencygroup_from_src(self, context, group, volumes,
cgsnapshot=None, snapshots=None,
source_cg=None, source_vols=None):
"""Creates a consistencygroup from source.
:param context: the context of the caller. :param context: the context of the caller.
:param group: the dictionary of the consistency group to be created. :param group: the group object.
:param volumes: a list of volume dictionaries in the group. :param add_volumes: a list of volume objects to be added.
:param cgsnapshot: the dictionary of the cgsnapshot as source. :param remove_volumes: a list of volume objects to be removed.
:param snapshots: a list of snapshot dictionaries in the cgsnapshot. :returns: model_update, add_volumes_update, remove_volumes_update
:param source_cg: the dictionary of a consistency group as source. """
:param source_vols: a list of volume dictionaries in the source_cg.
LOG.debug("Updating group.")
if utils.is_group_a_cg_snapshot_type(group):
return None, None, None
# we'll rely on the generic group implementation if it is not a
# consistency group request.
raise NotImplementedError()
def create_group_from_src(self, context, group, volumes,
group_snapshot=None, snapshots=None,
source_group=None, source_vols=None):
"""Creates a group from source.
:param context: the context of the caller.
:param group: the Group object to be created.
:param volumes: a list of Volume objects in the group.
:param group_snapshot: the GroupSnapshot object as source.
:param snapshots: a list of snapshot objects in group_snapshot.
:param source_group: the Group object as source.
:param source_vols: a list of volume objects in the source_group.
:returns: model_update, volumes_model_update :returns: model_update, volumes_model_update
""" """
LOG.debug('Enter: create_consistencygroup_from_src.') LOG.debug('Enter: create_group_from_src.')
if cgsnapshot and snapshots: if not utils.is_group_a_cg_snapshot_type(group):
cg_name = 'cg-' + cgsnapshot.id # we'll rely on the generic volume groups implementation if it is
# not a consistency group request.
raise NotImplementedError()
if group_snapshot and snapshots:
cg_name = 'cg-' + group_snapshot.id
sources = snapshots sources = snapshots
elif source_cg and source_vols: elif source_group and source_vols:
cg_name = 'cg-' + source_cg.id cg_name = 'cg-' + source_group.id
sources = source_vols sources = source_vols
else: else:
error_msg = _("create_consistencygroup_from_src must be " error_msg = _("create_group_from_src must be creating from a "
"creating from a CG snapshot, or a source CG.") "group snapshot, or a source group.")
raise exception.InvalidInput(reason=error_msg) raise exception.InvalidInput(reason=error_msg)
LOG.debug('create_consistencygroup_from_src: cg_name %(cg_name)s' LOG.debug('create_group_from_src: cg_name %(cg_name)s'
' %(sources)s', {'cg_name': cg_name, 'sources': sources}) ' %(sources)s', {'cg_name': cg_name, 'sources': sources})
self._helpers.create_fc_consistgrp(cg_name) self._helpers.create_fc_consistgrp(cg_name)
timeout = self.configuration.storwize_svc_flashcopy_timeout timeout = self.configuration.storwize_svc_flashcopy_timeout
@ -3561,13 +3605,24 @@ class StorwizeSVCCommonDriver(san.SanDriver,
self._state, self._state,
self.configuration, self.configuration,
timeout)) timeout))
LOG.debug("Leave: create_consistencygroup_from_src.") LOG.debug("Leave: create_group_from_src.")
return model_update, snapshots_model return model_update, snapshots_model
def create_cgsnapshot(self, ctxt, cgsnapshot, snapshots): def create_group_snapshot(self, context, group_snapshot, snapshots):
"""Creates a cgsnapshot.""" """Creates a group_snapshot.
# Use cgsnapshot id as cg name
cg_name = 'cg_snap-' + cgsnapshot.id :param context: the context of the caller.
:param group_snapshot: the GroupSnapshot object to be created.
:param snapshots: a list of Snapshot objects in the group_snapshot.
:returns: model_update, snapshots_model_update
"""
if not utils.is_group_a_cg_snapshot_type(group_snapshot):
# we'll rely on the generic group implementation if it is not a
# consistency group request.
raise NotImplementedError()
# Use group_snapshot id as cg name
cg_name = 'cg_snap-' + group_snapshot.id
# Create new cg as cg_snapshot # Create new cg as cg_snapshot
self._helpers.create_fc_consistgrp(cg_name) self._helpers.create_fc_consistgrp(cg_name)
@ -3582,9 +3637,21 @@ class StorwizeSVCCommonDriver(san.SanDriver,
return model_update, snapshots_model return model_update, snapshots_model
def delete_cgsnapshot(self, context, cgsnapshot, snapshots): def delete_group_snapshot(self, context, group_snapshot, snapshots):
"""Deletes a cgsnapshot.""" """Deletes a group_snapshot.
cgsnapshot_id = cgsnapshot['id']
:param context: the context of the caller.
:param group_snapshot: the GroupSnapshot object to be deleted.
:param snapshots: a list of snapshot objects in the group_snapshot.
:returns: model_update, snapshots_model_update
"""
if not utils.is_group_a_cg_snapshot_type(group_snapshot):
# we'll rely on the generic group implementation if it is not a
# consistency group request.
raise NotImplementedError()
cgsnapshot_id = group_snapshot.id
cg_name = 'cg_snap-' + cgsnapshot_id cg_name = 'cg_snap-' + cgsnapshot_id
model_update, snapshots_model = ( model_update, snapshots_model = (
@ -3671,6 +3738,7 @@ class StorwizeSVCCommonDriver(san.SanDriver,
'thin_provisioning_support': not use_thick_provisioning, 'thin_provisioning_support': not use_thick_provisioning,
'thick_provisioning_support': use_thick_provisioning, 'thick_provisioning_support': use_thick_provisioning,
'max_over_subscription_ratio': over_sub_ratio, 'max_over_subscription_ratio': over_sub_ratio,
'consistent_group_snapshot_enabled': True,
} }
if self._replica_enabled: if self._replica_enabled:
pool_stats.update({ pool_stats.update({

View File

@ -88,9 +88,10 @@ class StorwizeSVCFCDriver(storwize_common.StorwizeSVCCommonDriver):
2.1 - Added replication V2 support to the global/metro mirror 2.1 - Added replication V2 support to the global/metro mirror
mode mode
2.1.1 - Update replication to version 2.1 2.1.1 - Update replication to version 2.1
2.2 - Add CG capability to generic volume groups
""" """
VERSION = "2.1.1" VERSION = "2.2"
# ThirdPartySystems wiki page # ThirdPartySystems wiki page
CI_WIKI_NAME = "IBM_STORAGE_CI" CI_WIKI_NAME = "IBM_STORAGE_CI"

View File

@ -88,9 +88,10 @@ class StorwizeSVCISCSIDriver(storwize_common.StorwizeSVCCommonDriver):
2.1 - Added replication V2 support to the global/metro mirror 2.1 - Added replication V2 support to the global/metro mirror
mode mode
2.1.1 - Update replication to version 2.1 2.1.1 - Update replication to version 2.1
2.2 - Add CG capability to generic volume groups
""" """
VERSION = "2.1.1" VERSION = "2.2"
# ThirdPartySystems wiki page # ThirdPartySystems wiki page
CI_WIKI_NAME = "IBM_STORAGE_CI" CI_WIKI_NAME = "IBM_STORAGE_CI"

View File

@ -0,0 +1,4 @@
---
features:
- Add consistency group capability to generic volume groups in Storwize
drivers.