Storwize: add mirrored volume support

Add mirrored volume support for IBM Storwize. Stretch cluster
volume support is also added for IBM SVC.

One new property is added in volume type config. User can
specify the pool in which mirrored copy is stored by
mirror_pool.

Implements: blueprint stretch-cluster-svc

Change-Id: I4870bfc1789a57b9083dc8551949943334a20a05
This commit is contained in:
Xiaoqin Li 2017-05-09 18:10:00 +08:00
parent 6ce8d6f32f
commit 76fc4edc64
5 changed files with 660 additions and 166 deletions

View File

@ -325,7 +325,8 @@ class StorwizeSVCManagementSimulator(object):
'aux',
'cluster',
'linkbandwidthmbits',
'backgroundcopyrate'
'backgroundcopyrate',
'copies'
]
no_or_one_param_args = [
'autoexpand',
@ -732,10 +733,25 @@ port_speed!N/A
volume_info['uid'] = ('ABCDEF' * 3) + ('0' * 14) + volume_info['id']
mdiskgrp = kwargs['mdiskgrp'].strip('\'\"')
sec_pool = None
is_mirror_vol = False
if 'copies' in kwargs:
# it is a mirror volume
pool_split = mdiskgrp.split(':')
if len(pool_split) != 2:
raise exception.InvalidInput(
reason=_('mdiskgrp %s is invalid for mirror '
'volume') % kwargs['mdiskgrp'])
else:
is_mirror_vol = True
mdiskgrp = pool_split[0]
sec_pool = pool_split[1]
if mdiskgrp == kwargs['mdiskgrp']:
raise exception.InvalidInput(
reason=_('mdiskgrp missing quotes %s') % kwargs['mdiskgrp'])
mdiskgrp_id = self._get_mdiskgrp_id(mdiskgrp)
sec_pool_id = self._get_mdiskgrp_id(sec_pool)
volume_info['mdisk_grp_name'] = mdiskgrp
volume_info['mdisk_grp_id'] = str(mdiskgrp_id)
@ -803,6 +819,16 @@ port_speed!N/A
'easy_tier': volume_info['easy_tier'],
'compressed_copy': volume_info['compressed_copy']}
volume_info['copies'] = {'0': vol_cp}
if is_mirror_vol:
vol_cp1 = {'id': '1',
'status': 'online',
'sync': 'yes',
'primary': 'no',
'mdisk_grp_id': str(sec_pool_id),
'mdisk_grp_name': sec_pool,
'easy_tier': volume_info['easy_tier'],
'compressed_copy': volume_info['compressed_copy']}
volume_info['copies']['1'] = vol_cp1
if volume_info['name'] in self._volumes_list:
return self._errors['CMMVC6035E']
@ -1588,32 +1614,17 @@ port_speed!N/A
return self._errors['CMMVC5707E']
mdiskgrp = kwargs['mdiskgrp'].strip('\'\"')
vdisk = kwargs['vdisk'].strip('\'\"')
copy_id = kwargs['copy']
if vdisk not in self._volumes_list:
return self._errors['CMMVC5753E']
mdiskgrp_id = str(self._get_mdiskgrp_id(mdiskgrp))
if vdisk in self._volumes_list:
curr_mdiskgrp = self._volumes_list
else:
for pool in self._other_pools:
if vdisk in pool:
curr_mdiskgrp = pool
break
else:
return self._errors['CMMVC5754E']
self._volumes_list[vdisk]['mdisk_grp_name'] = mdiskgrp
self._volumes_list[vdisk]['mdisk_grp_id'] = mdiskgrp_id
if mdiskgrp == self._flags['storwize_svc_volpool_name']:
tgt_mdiskgrp = self._volumes_list
elif mdiskgrp == 'openstack2':
tgt_mdiskgrp = self._other_pools['openstack2']
elif mdiskgrp == 'openstack3':
tgt_mdiskgrp = self._other_pools['openstack3']
else:
return self._errors['CMMVC5754E']
if curr_mdiskgrp == tgt_mdiskgrp:
return self._errors['CMMVC6430E']
vol = curr_mdiskgrp[vdisk]
tgt_mdiskgrp[vdisk] = vol
del curr_mdiskgrp[vdisk]
vol = self._volumes_list[vdisk]
vol['copies'][copy_id]['mdisk_grp_name'] = mdiskgrp
vol['copies'][copy_id]['mdisk_grp_id'] = mdiskgrp_id
return ('', '')
def _cmd_addvdiskcopy(self, **kwargs):
@ -1629,6 +1640,7 @@ port_speed!N/A
if mdiskgrp == kwargs['mdiskgrp']:
raise exception.InvalidInput(
reason=_('mdiskgrp missing quotes %s') % kwargs['mdiskgrp'])
auto_del = True if 'autodelete' in kwargs else False
copy_info = {}
copy_info['id'] = self._find_unused_id(vol['copies'])
@ -1649,6 +1661,14 @@ port_speed!N/A
else:
copy_info['compressed_copy'] = 'no'
vol['copies'][copy_info['id']] = copy_info
if auto_del:
del_copy_id = None
for v in vol['copies'].values():
if v['id'] != copy_info['id']:
del_copy_id = v['id']
break
if del_copy_id:
del vol['copies'][del_copy_id]
return ('Vdisk [%(vid)s] copy [%(cid)s] successfully created' %
{'vid': vol['id'], 'cid': copy_info['id']}, '')
@ -1725,6 +1745,8 @@ port_speed!N/A
for key, value in kwargs.items():
if key == 'easytier':
vol['easy_tier'] = value
for copy in vol['copies'].values():
vol['copies'][copy['id']]['easy_tier'] = value
continue
if key == 'warning':
vol['warning'] = value.rstrip('%')
@ -1749,6 +1771,9 @@ port_speed!N/A
return ('', err)
if key in params:
vol[key] = value
if key == 'autoexpand':
for copy in vol['copies'].values():
vol['copies'][copy['id']]['autoexpand'] = value
else:
err = self._errors['CMMVC5709E'][1] % {'VALUE': key}
return ('', err)
@ -3447,6 +3472,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
self.driver._helpers.check_fcmapping_interval = 0
self.mock_object(storwize_svc_iscsi.StorwizeSVCISCSIDriver,
'DEFAULT_GR_SLEEP', 0)
self._create_test_volume_types()
def _set_flag(self, flag, value, configuration=None):
if not configuration:
@ -3465,6 +3491,11 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
is_vol_defined = self.driver._helpers.is_vdisk_defined(name)
self.assertEqual(exists, is_vol_defined)
def _create_test_volume_types(self):
spec = {'mirror_pool': 'openstack1'}
self.mirror_vol_type = self._create_volume_type(spec, 'mirror_type')
self.default_vol_type = self._create_volume_type(None, 'default_type')
def test_storwize_svc_connectivity(self):
# Make sure we detect if the pool doesn't exist
no_exist_pool = 'i-dont-exist-%s' % random.randint(10000, 99999)
@ -3656,21 +3687,26 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
self.assertEqual(self._driver.configuration.storwize_san_secondary_ip,
self._driver.active_ip)
def _generate_vol_info(self, vol_name, vol_id):
def _create_volume_type(self, opts, type_name):
type_ref = volume_types.create(self.ctxt, type_name, opts)
vol_type = objects.VolumeType.get_by_id(self.ctxt, type_ref['id'])
return vol_type
def _generate_vol_info(self, vol_type=None, size=10):
pool = _get_test_pool()
prop = {'mdisk_grp_name': pool}
if vol_name:
prop.update(volume_name=vol_name,
volume_id=vol_id,
volume_size=10)
else:
prop.update(size=10,
volume_type_id=None,
mdisk_grp_name=pool,
host='openstack@svc#%s' % pool)
prop = {'size': size,
'host': 'openstack@svc#%s' % pool}
if vol_type:
prop['volume_type_id'] = vol_type.id
vol = testutils.create_volume(self.ctxt, **prop)
return vol
def _generate_snap_info(self, vol_id, size=10):
prop = {'volume_id': vol_id,
'volume_size': size}
snap = testutils.create_snapshot(self.ctxt, **prop)
return snap
def _create_volume(self, **kwargs):
pool = _get_test_pool()
prop = {'host': 'openstack@svc#%s' % pool,
@ -3739,7 +3775,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
def _create_test_vol(self, opts):
ctxt = testutils.get_test_admin_context()
type_ref = volume_types.create(ctxt, 'testtype', opts)
volume = self._generate_vol_info(None, None)
volume = self._generate_vol_info()
volume.volume_type_id = type_ref['id']
volume.volume_typ = objects.VolumeType.get_by_id(ctxt,
type_ref['id'])
@ -3761,7 +3797,8 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
'qos': None,
'replication': False,
'stretched_cluster': None,
'nofmtdisk': False}
'nofmtdisk': False,
'mirror_pool': None}
return opt
@mock.patch.object(storwize_svc_common.StorwizeHelpers, 'add_vdisk_qos')
@ -3792,7 +3829,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
def test_storwize_svc_snapshots(self):
vol1 = self._create_volume()
snap1 = self._generate_vol_info(vol1['name'], vol1['id'])
snap1 = self._generate_snap_info(vol1.id)
# Test timeout and volume cleanup
self._set_flag('storwize_svc_flashcopy_timeout', 1)
@ -3824,7 +3861,8 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
self._assert_vol_exists(snap1['name'], True)
# Try to create a snapshot from an non-existing volume - should fail
snap_novol = self._generate_vol_info('undefined-vol', '12345')
vol2 = self._generate_vol_info()
snap_novol = self._generate_snap_info(vol2.id)
self.assertRaises(exception.VolumeDriverException,
self.driver.create_snapshot,
snap_novol)
@ -3880,14 +3918,14 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
def test_storwize_svc_create_volume_from_snapshot(self):
vol1 = self._create_volume()
snap1 = self._generate_vol_info(vol1['name'], vol1['id'])
snap1 = self._generate_snap_info(vol1.id)
self.driver.create_snapshot(snap1)
vol2 = self._generate_vol_info(None, None)
vol3 = self._generate_vol_info(None, None)
vol2 = self._generate_vol_info()
vol3 = self._generate_vol_info()
# Try to create a volume from a non-existing snapshot
snap_novol = self._generate_vol_info('undefined-vol', '12345')
vol_novol = self._generate_vol_info(None, None)
vol_novol = self._generate_vol_info()
snap_novol = self._generate_snap_info(vol_novol.id)
self.assertRaises(exception.VolumeDriverException,
self.driver.create_volume_from_snapshot,
vol_novol,
@ -3938,10 +3976,10 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
def test_storwize_svc_create_volfromsnap_clone_with_qos(self,
add_vdisk_qos):
vol1 = self._create_volume()
snap1 = self._generate_vol_info(vol1['name'], vol1['id'])
snap1 = self._generate_snap_info(vol1.id)
self.driver.create_snapshot(snap1)
vol2 = self._generate_vol_info(None, None)
vol3 = self._generate_vol_info(None, None)
vol2 = self._generate_vol_info()
vol3 = self._generate_vol_info()
fake_opts = self._get_default_opts()
# Succeed
@ -4002,12 +4040,12 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
def test_storwize_svc_delete_vol_with_fcmap(self):
vol1 = self._create_volume()
# create two snapshots
snap1 = self._generate_vol_info(vol1['name'], vol1['id'])
snap2 = self._generate_vol_info(vol1['name'], vol1['id'])
snap1 = self._generate_snap_info(vol1.id)
snap2 = self._generate_snap_info(vol1.id)
self.driver.create_snapshot(snap1)
self.driver.create_snapshot(snap2)
vol2 = self._generate_vol_info(None, None)
vol3 = self._generate_vol_info(None, None)
vol2 = self._generate_vol_info()
vol3 = self._generate_vol_info()
# Create vol from the second snapshot
if self.USESIM:
@ -4045,7 +4083,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
def test_storwize_svc_volumes(self):
# Create a first volume
volume = self._generate_vol_info(None, None)
volume = self._generate_vol_info()
self.driver.create_volume(volume)
self.driver.ensure_export(None, volume)
@ -4067,7 +4105,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
volume)
# Try to delete a volume that doesn't exist (should not fail)
vol_no_exist = self._generate_vol_info(None, None)
vol_no_exist = self._generate_vol_info()
self.driver.delete_volume(vol_no_exist)
# Ensure export for volume that doesn't exist (should not fail)
self.driver.ensure_export(None, vol_no_exist)
@ -4076,7 +4114,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
self.driver.delete_volume(volume)
def test_storwize_svc_volume_name(self):
volume = self._generate_vol_info(None, None)
volume = self._generate_vol_info()
self.driver.create_volume(volume)
self.driver.ensure_export(None, volume)
@ -4154,7 +4192,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
self.driver.do_setup(None)
rand_id = random.randint(10000, 99999)
volume1 = self._generate_vol_info(None, None)
volume1 = self._generate_vol_info()
self.driver.create_volume(volume1)
self._assert_vol_exists(volume1['name'], True)
@ -4199,21 +4237,21 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
# Fail creating a snapshot - will force delete the snapshot
if self.USESIM and False:
snap = self._generate_vol_info(master['name'], master['id'])
snap = self._generate_snap_info(master.id)
self.sim.error_injection('startfcmap', 'bad_id')
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.create_snapshot, snap)
self._assert_vol_exists(snap['name'], False)
# Delete a snapshot
snap = self._generate_vol_info(master['name'], master['id'])
snap = self._generate_snap_info(master.id)
self.driver.create_snapshot(snap)
self._assert_vol_exists(snap['name'], True)
self.driver.delete_snapshot(snap)
self._assert_vol_exists(snap['name'], False)
# Delete a volume with snapshots (regular)
snap = self._generate_vol_info(master['name'], master['id'])
snap = self._generate_snap_info(master.id)
self.driver.create_snapshot(snap)
self._assert_vol_exists(snap['name'], True)
self.driver.delete_volume(master)
@ -4221,7 +4259,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
# Fail create volume from snapshot - will force delete the volume
if self.USESIM:
volfs = self._generate_vol_info(None, None)
volfs = self._generate_vol_info()
self.sim.error_injection('startfcmap', 'bad_id')
self.sim.error_injection('lsfcmap', 'speed_up')
self.assertRaises(exception.VolumeBackendAPIException,
@ -4230,7 +4268,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
self._assert_vol_exists(volfs['name'], False)
# Create volume from snapshot and delete it
volfs = self._generate_vol_info(None, None)
volfs = self._generate_vol_info()
if self.USESIM:
self.sim.error_injection('lsfcmap', 'speed_up')
self.driver.create_volume_from_snapshot(volfs, snap)
@ -4239,7 +4277,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
self._assert_vol_exists(volfs['name'], False)
# Create volume from snapshot and delete the snapshot
volfs = self._generate_vol_info(None, None)
volfs = self._generate_vol_info()
if self.USESIM:
self.sim.error_injection('lsfcmap', 'speed_up')
self.driver.create_volume_from_snapshot(volfs, snap)
@ -4248,7 +4286,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
# Fail create clone - will force delete the target volume
if self.USESIM:
clone = self._generate_vol_info(None, None)
clone = self._generate_vol_info()
self.sim.error_injection('startfcmap', 'bad_id')
self.sim.error_injection('lsfcmap', 'speed_up')
self.assertRaises(exception.VolumeBackendAPIException,
@ -4257,7 +4295,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
self._assert_vol_exists(clone['name'], False)
# Create the clone, delete the source and target
clone = self._generate_vol_info(None, None)
clone = self._generate_vol_info()
if self.USESIM:
self.sim.error_injection('lsfcmap', 'speed_up')
self.driver.create_cloned_volume(clone, volfs)
@ -4306,12 +4344,13 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
def test_get_pool(self):
ctxt = testutils.get_test_admin_context()
type_ref = volume_types.create(ctxt, 'testtype', None)
volume = self._generate_vol_info(None, None)
volume = self._generate_vol_info()
volume.volume_type_id = type_ref['id']
volume.volume_type = objects.VolumeType.get_by_id(ctxt,
type_ref['id'])
self.driver.create_volume(volume)
self.assertEqual(volume['mdisk_grp_name'],
vol = self.driver._helpers.get_vdisk_attributes(volume.name)
self.assertEqual(vol['mdisk_grp_name'],
self.driver.get_pool(volume))
self.driver.delete_volume(volume)
@ -4325,7 +4364,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
self.assertAlmostEqual(vol_size, 13)
snap = self._generate_vol_info(volume['name'], volume['id'])
snap = self._generate_snap_info(volume.id)
self.driver.create_snapshot(snap)
self._assert_vol_exists(snap['name'], True)
self.assertRaises(exception.VolumeDriverException,
@ -4537,10 +4576,9 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
diff, _equal = volume_types.volume_types_diff(ctxt, old_type_ref['id'],
new_type_ref['id'])
volume = self._generate_vol_info(None, None)
old_type = objects.VolumeType.get_by_id(ctxt,
old_type_ref['id'])
volume['volume_type'] = old_type
volume = self._generate_vol_info(old_type)
volume['host'] = host['host']
new_type = objects.VolumeType.get_by_id(ctxt,
new_type_ref['id'])
@ -4629,10 +4667,9 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
diff, _equal = volume_types.volume_types_diff(ctxt, old_type_ref['id'],
new_type_ref['id'])
volume = self._generate_vol_info(None, None)
old_type = objects.VolumeType.get_by_id(ctxt,
old_type_ref['id'])
volume['volume_type'] = old_type
volume = self._generate_vol_info(old_type)
volume['host'] = host['host']
new_type = objects.VolumeType.get_by_id(ctxt,
new_type_ref['id'])
@ -4666,10 +4703,9 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
diff, _equal = volume_types.volume_types_diff(ctxt, old_type_ref['id'],
new_type_ref['id'])
volume = self._generate_vol_info(None, None)
old_type = objects.VolumeType.get_by_id(ctxt,
old_type_ref['id'])
volume['volume_type'] = old_type
volume = self._generate_vol_info(old_type)
volume['host'] = host['host']
new_type = objects.VolumeType.get_by_id(ctxt,
new_type_ref['id'])
@ -4801,7 +4837,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
self.assertNotIn(volume['id'], self.driver._vdiskcopyops)
def test_storwize_create_volume_with_replication_disable(self):
volume = self._generate_vol_info(None, None)
volume = self._generate_vol_info()
model_update = self.driver.create_volume(volume)
self.assertIsNone(model_update)
@ -4814,7 +4850,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
self._set_flag('storwize_svc_stretched_cluster_partner', 'openstack2')
# Create a type for repliation.
volume = self._generate_vol_info(None, None)
volume = self._generate_vol_info()
volume_type = self._create_replication_volume_type(True)
volume['volume_type_id'] = volume_type['id']
@ -4907,11 +4943,11 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
self.driver.do_setup(self.ctxt)
# Create a source volume.
src_volume = self._generate_vol_info(None, None)
src_volume = self._generate_vol_info()
self.driver.create_volume(src_volume)
# Create a type for repliation.
volume = self._generate_vol_info(None, None)
volume = self._generate_vol_info()
volume_type = self._create_replication_volume_type(True)
volume['volume_type_id'] = volume_type['id']
@ -4929,12 +4965,12 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
self.driver.do_setup(self.ctxt)
vol1 = self._create_volume()
snap = self._generate_vol_info(vol1['name'], vol1['id'])
snap = self._generate_snap_info(vol1.id)
self.driver.create_snapshot(snap)
vol2 = self._generate_vol_info(None, None)
vol2 = self._generate_vol_info()
# Create a type for repliation.
vol2 = self._generate_vol_info(None, None)
vol2 = self._generate_vol_info()
volume_type = self._create_replication_volume_type(True)
vol2['volume_type_id'] = volume_type['id']
@ -5239,6 +5275,231 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
for volume in model_update[1]:
self.assertEqual('deleted', volume['status'])
# mirror/strtch cluster volume test cases
def test_storwize_svc_create_mirror_volume(self):
# create mirror volume in invalid pool
spec = {'mirror_pool': 'invalid_pool'}
mirror_vol_type = self._create_volume_type(spec, 'invalid_mirror_type')
vol = self._generate_vol_info(mirror_vol_type)
self.assertRaises(exception.InvalidInput,
self.driver.create_volume, vol)
spec = {'mirror_pool': 'openstack1'}
mirror_vol_type = self._create_volume_type(spec, 'test_mirror_type')
vol = self._generate_vol_info(mirror_vol_type)
self.driver.create_volume(vol)
self._assert_vol_exists(vol.name, True)
copies = self.driver._helpers.get_vdisk_copies(vol.name)
self.assertEqual(copies['primary']['mdisk_grp_name'], 'openstack')
self.assertEqual(copies['secondary']['mdisk_grp_name'], 'openstack1')
self.driver.delete_volume(vol)
self._assert_vol_exists(vol['name'], False)
def test_storwize_svc_snapshots_mirror_volume(self):
vol1 = self._generate_vol_info(self.mirror_vol_type)
self.driver.create_volume(vol1)
snap1 = self._generate_snap_info(vol1.id)
self._assert_vol_exists(snap1.name, False)
self.driver.create_snapshot(snap1)
if self.USESIM:
self.sim.error_injection('lsfcmap', 'speed_up')
self._assert_vol_exists(snap1.name, True)
copies = self.driver._helpers.get_vdisk_copies(snap1.name)
self.assertEqual(copies['primary']['mdisk_grp_name'], 'openstack')
self.assertEqual(copies['secondary']['mdisk_grp_name'], 'openstack1')
self.driver.delete_snapshot(snap1)
self.driver.delete_volume(vol1)
def test_storwize_svc_create_cloned_mirror_volume(self):
vol1 = self._generate_vol_info(self.mirror_vol_type)
self.driver.create_volume(vol1)
vol2 = self._generate_vol_info(self.mirror_vol_type)
if self.USESIM:
self.sim.error_injection('lsfcmap', 'speed_up')
self.driver.create_cloned_volume(vol2, vol1)
self._assert_vol_exists(vol2.name, True)
copies = self.driver._helpers.get_vdisk_copies(vol2.name)
self.assertEqual(copies['primary']['mdisk_grp_name'], 'openstack')
self.assertEqual(copies['secondary']['mdisk_grp_name'], 'openstack1')
self.driver.delete_volume(vol2)
self._assert_vol_exists(vol2.name, False)
self.driver.delete_volume(vol1)
self._assert_vol_exists(vol1.name, False)
def test_storwize_svc_create_mirror_volume_from_snapshot(self):
vol1 = self._generate_vol_info(self.mirror_vol_type)
self.driver.create_volume(vol1)
snap1 = self._generate_snap_info(vol1.id)
self.driver.create_snapshot(snap1)
if self.USESIM:
self.sim.error_injection('lsfcmap', 'speed_up')
vol2 = self._generate_vol_info(self.mirror_vol_type)
self.driver.create_volume_from_snapshot(vol2, snap1)
self._assert_vol_exists(vol2.name, True)
copies = self.driver._helpers.get_vdisk_copies(vol2.name)
self.assertEqual(copies['primary']['mdisk_grp_name'], 'openstack')
self.assertEqual(copies['secondary']['mdisk_grp_name'], 'openstack1')
self.driver.delete_volume(vol2)
self._assert_vol_exists(vol2['name'], False)
self.driver.delete_snapshot(snap1)
self._assert_vol_exists(snap1['name'], False)
self.driver.delete_volume(vol1)
self._assert_vol_exists(vol1['name'], False)
@mock.patch.object(storwize_svc_common.StorwizeHelpers, 'add_vdisk_copy')
def test_storwize_svc_mirror_volume_migrate(self, add_vdisk_copy):
# use migratevdisk for mirror volume migration, rather than
# addvdiskcopy
self.driver.do_setup(None)
loc = ('StorwizeSVCDriver:' + self.driver._state['system_id'] +
':openstack2')
host = {'host': 'openstack@svc#openstack2',
'capabilities': {'location_info': loc}}
ctxt = context.get_admin_context()
vol1 = self._generate_vol_info(self.mirror_vol_type)
self.driver.create_volume(vol1)
copies = self.driver._helpers.get_vdisk_copies(vol1.name)
self.assertEqual(copies['primary']['mdisk_grp_name'], 'openstack')
self.assertEqual(copies['secondary']['mdisk_grp_name'], 'openstack1')
self.driver.migrate_volume(ctxt, vol1, host)
copies = self.driver._helpers.get_vdisk_copies(vol1.name)
self.assertEqual(copies['primary']['mdisk_grp_name'], 'openstack2')
self.assertEqual(copies['secondary']['mdisk_grp_name'], 'openstack1')
self.assertFalse(add_vdisk_copy.called)
self._delete_volume(vol1)
@ddt.data(({'mirror_pool': 'openstack1'},
{'mirror_pool': 'openstack1', 'compression': True}),
({'compression': False},
{'mirror_pool': 'openstack1', 'compression': True}),
({}, {'mirror_pool': 'invalidpool'}))
@ddt.unpack
def test_storwize_svc_retype_mirror_volume_invalid(self, old_opts,
new_opts):
self.driver.do_setup(self.ctxt)
host = {'host': 'openstack@svc#openstack'}
ctxt = context.get_admin_context()
vol_type1 = self._create_volume_type(old_opts, 'old')
vol_type2 = self._create_volume_type(new_opts, 'new')
diff, _equal = volume_types.volume_types_diff(ctxt, vol_type1.id,
vol_type2.id)
vol1 = self._generate_vol_info(vol_type1)
self.driver.create_volume(vol1)
self.assertRaises(exception.VolumeDriverException,
self.driver.retype, self.ctxt, vol1,
vol_type2, diff, host)
self.driver.delete_volume(vol1)
@ddt.data(({'mirror_pool': 'openstack1'}, {}),
({'mirror_pool': 'openstack1'}, {'mirror_pool': ''}))
@ddt.unpack
def test_storwize_retype_from_mirror_to_none_mirror(self,
old_opts, new_opts):
self.driver.do_setup(self.ctxt)
host = {'host': 'openstack@svc#openstack'}
ctxt = context.get_admin_context()
vol_type1 = self._create_volume_type(old_opts, 'old')
vol_type2 = self._create_volume_type(new_opts, 'new')
diff, _equal = volume_types.volume_types_diff(ctxt, vol_type1.id,
vol_type2.id)
vol1 = self._generate_vol_info(vol_type1)
self.driver.create_volume(vol1)
self._assert_vol_exists(vol1.name, True)
copies = self.driver._helpers.lsvdiskcopy(vol1.name)
self.assertEqual(len(copies), 2)
self.driver.retype(self.ctxt, vol1, vol_type2, diff, host)
copies = self.driver._helpers.lsvdiskcopy(vol1.name)
self.assertEqual(len(copies), 1)
copies = self.driver._helpers.get_vdisk_copies(vol1.name)
self.assertEqual(copies['primary']['mdisk_grp_name'], 'openstack')
self.driver.delete_volume(vol1)
@ddt.data(({}, {'mirror_pool': 'openstack1'}),
({'mirror_pool': ''}, {'mirror_pool': 'openstack1'}))
@ddt.unpack
def test_storwize_retype_from_none_to_mirror_volume(self,
old_opts, new_opts):
self.driver.do_setup(self.ctxt)
host = {'host': 'openstack@svc#openstack'}
ctxt = context.get_admin_context()
old_opts = {}
new_opts = {'mirror_pool': 'openstack1'}
vol_type1 = self._create_volume_type(old_opts, 'old')
vol_type2 = self._create_volume_type(new_opts, 'new')
diff, _equal = volume_types.volume_types_diff(ctxt, vol_type1.id,
vol_type2.id)
vol1 = self._generate_vol_info(vol_type1)
self.driver.create_volume(vol1)
self._assert_vol_exists(vol1.name, True)
copies = self.driver._helpers.lsvdiskcopy(vol1.name)
self.assertEqual(len(copies), 1)
self.driver.retype(self.ctxt, vol1, vol_type2, diff, host)
copies = self.driver._helpers.lsvdiskcopy(vol1.name)
self.assertEqual(len(copies), 2)
copies = self.driver._helpers.get_vdisk_copies(vol1.name)
self.assertEqual(copies['primary']['mdisk_grp_name'], 'openstack')
self.assertEqual(copies['secondary']['mdisk_grp_name'], 'openstack1')
self.driver.delete_volume(vol1)
@ddt.data(({}, {'mirror_pool': 'openstack1'}),
({'mirror_pool': ''}, {'mirror_pool': 'openstack1'}),
({'mirror_pool': 'openstack1'}, {}),
({'mirror_pool': 'openstack1'}, {'mirror_pool': ''}),
({'mirror_pool': 'openstack1'}, {'mirror_pool': 'invalidpool'}))
@ddt.unpack
def test_storwize_manage_existing_mismatch_with_mirror_volume(
self, opts1, opts2):
self.driver.do_setup(self.ctxt)
vol_type1 = self._create_volume_type(opts1, 'vol_type1')
vol_type2 = self._create_volume_type(opts2, 'vol_type2')
vol1 = self._generate_vol_info(vol_type1)
self.driver.create_volume(vol1)
vol2 = self._generate_vol_info(vol_type2)
ref = {'source-name': vol1.name}
self.assertRaises(exception.ManageExistingVolumeTypeMismatch,
self.driver.manage_existing, vol2, ref)
self.driver.delete_volume(vol1)
def test_storwize_manage_existing_with_mirror_volume(self):
self.driver.do_setup(self.ctxt)
vol1 = self._generate_vol_info(self.mirror_vol_type)
self.driver.create_volume(vol1)
uid_of_vol1 = self._get_vdisk_uid(vol1.name)
opts1 = {'mirror_pool': 'openstack1'}
new_volume_type = self._create_volume_type(opts1, 'new_mirror_type')
new_volume = self._generate_vol_info(new_volume_type)
ref = {'source-name': vol1.name}
self.driver.manage_existing(new_volume, ref)
# Check the uid of the volume which has been renamed.
uid_of_new_vol = self._get_vdisk_uid(new_volume.name)
self.assertEqual(uid_of_vol1, uid_of_new_vol)
self.driver.delete_volume(new_volume)
def _create_volume_type_qos(self, extra_specs, fake_qos):
# Generate a QoS volume type for volume.
if extra_specs:
@ -5309,7 +5570,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
create_volume and then calling into the simulator to perform an
lsvdisk directly.
"""
volume = self._generate_vol_info(None, None)
volume = self._generate_vol_info()
self.driver.create_volume(volume)
return (volume, self._get_vdisk_uid(volume['name']))
@ -5321,14 +5582,14 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
a bad reference that the Storwize driver doesn't understand. We
expect an exception to be raised.
"""
volume = self._generate_vol_info(None, None)
volume = self._generate_vol_info()
ref = {}
self.assertRaises(exception.ManageExistingInvalidReference,
self.driver.manage_existing_get_size, volume, ref)
def test_manage_existing_get_size_bad_uid(self):
"""Error when the specified UUID does not exist."""
volume = self._generate_vol_info(None, None)
volume = self._generate_vol_info()
ref = {'source-id': 'bad_uid'}
self.assertRaises(exception.ManageExistingInvalidReference,
self.driver.manage_existing_get_size, volume, ref)
@ -5336,7 +5597,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
def test_manage_existing_get_size_bad_name(self):
"""Error when the specified name does not exist."""
volume = self._generate_vol_info(None, None)
volume = self._generate_vol_info()
ref = {'source-name': 'bad_name'}
self.assertRaises(exception.ManageExistingInvalidReference,
self.driver.manage_existing_get_size, volume, ref)
@ -5350,19 +5611,19 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
"""
# Error when neither UUID nor name are specified.
volume = self._generate_vol_info(None, None)
volume = self._generate_vol_info()
ref = {}
self.assertRaises(exception.ManageExistingInvalidReference,
self.driver.manage_existing, volume, ref)
# Error when the specified UUID does not exist.
volume = self._generate_vol_info(None, None)
volume = self._generate_vol_info()
ref = {'source-id': 'bad_uid'}
self.assertRaises(exception.ManageExistingInvalidReference,
self.driver.manage_existing, volume, ref)
# Error when the specified name does not exist.
volume = self._generate_vol_info(None, None)
volume = self._generate_vol_info()
ref = {'source-name': 'bad_name'}
self.assertRaises(exception.ManageExistingInvalidReference,
self.driver.manage_existing, volume, ref)
@ -5386,7 +5647,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
opts = {'rsize': -1, 'iogrp': 1}
type_iogrp_ref = volume_types.create(ctxt, 'testtype4', opts)
new_volume = self._generate_vol_info(None, None)
new_volume = self._generate_vol_info()
ref = {'source-name': _volume['name']}
fake_copy_thin = self._get_default_opts()
@ -5462,7 +5723,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
# Descriptor of the Cinder volume that we want to own the vdisk
# referenced by uid.
new_volume = self._generate_vol_info(None, None)
new_volume = self._generate_vol_info()
# Submit the request to manage it.
ref = {'source-id': uid}
@ -5490,7 +5751,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
# Descriptor of the Cinder volume that we want to own the vdisk
# referenced by uid.
new_volume = self._generate_vol_info(None, None)
new_volume = self._generate_vol_info()
# Submit the request to manage it.
ref = {'source-name': _volume['name']}
@ -5523,7 +5784,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
# Descriptor of the Cinder volume that we want to own the vdisk
# referenced by uid.
volume = self._generate_vol_info(None, None)
volume = self._generate_vol_info()
ref = {'source-id': uid}
# Attempt to manage this disk, and except an exception beause the
@ -5555,7 +5816,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
# Descriptor of the Cinder volume that we want to own the vdisk
# referenced by uid.
new_volume = self._generate_vol_info(None, None)
new_volume = self._generate_vol_info()
# Submit the request to manage it, specifying that it is OK to
# manage a volume that is already attached.
@ -5589,7 +5850,7 @@ class StorwizeSVCCommonDriverTestCase(test.TestCase):
# Descriptor of the Cinder volume that we want to own the vdisk
# referenced by uid.
new_volume = self._generate_vol_info(None, None)
new_volume = self._generate_vol_info()
# Submit the request to manage it, specifying that it is OK to
# manage a volume that is already attached.
@ -5856,6 +6117,7 @@ class StorwizeSSHTestCase(test.TestCase):
'fakevol', '1', 'gb', 'fakepool', opt, [])
@ddt.ddt
class StorwizeSVCReplicationTestCase(test.TestCase):
@mock.patch.object(time, 'sleep')
def setUp(self, mock_sleep):
@ -5949,7 +6211,8 @@ class StorwizeSVCReplicationTestCase(test.TestCase):
return snap
def _create_replica_volume_type(self, enable,
rep_type=storwize_const.METRO):
rep_type=storwize_const.METRO,
opts=None, vol_type_name=None):
# Generate a volume type for volume repliation.
if enable:
if rep_type == storwize_const.METRO:
@ -5960,6 +6223,9 @@ class StorwizeSVCReplicationTestCase(test.TestCase):
spec = {'replication_enabled': '<is> True',
'replication_type': '<in> global'}
type_name = 'rep_global'
elif opts:
spec = opts
type_name = vol_type_name
else:
spec = {'replication_enabled': '<is> False'}
type_name = "non_rep"
@ -6042,6 +6308,21 @@ class StorwizeSVCReplicationTestCase(test.TestCase):
self.driver._active_backend_id = None
self.driver._get_storwize_config()
@mock.patch.object(storwize_svc_common.StorwizeHelpers,
'create_vdisk')
def test_storwize_svc_create_stretch_volume_with_replication(self,
create_vdisk):
spec = {'mirror_pool': 'openstack1',
'replication_enabled': '<is> True',
'replication_type': '<in> global'
}
vol_type = self._create_replica_volume_type(
False, opts=spec, vol_type_name='test_type')
vol = self._generate_vol_info(vol_type)
self.assertRaises(exception.InvalidInput,
self.driver.create_volume, vol)
self.assertFalse(create_vdisk.called)
def test_storwize_create_volume_with_mirror_replication(self):
# Set replication target.
self.driver.configuration.set_override('replication_device',
@ -6133,6 +6414,43 @@ class StorwizeSVCReplicationTestCase(test.TestCase):
self.driver.delete_volume(src_volume)
self.driver.delete_volume(volume)
@ddt.data(({'replication_enabled': '<is> True',
'replication_type': '<in> global'},
{'replication_enabled': '<is> True',
'replication_type': '<in> metro'}),
({'replication_enabled': '<is> True',
'replication_type': '<in> metro'},
{'replication_enabled': '<is> True',
'replication_type': '<in> global'}),
({'replication_enabled': '<is> True',
'replication_type': '<in> metro'},
{'mirror_pool': 'openstack1'}),
({'mirror_pool': 'openstack1'},
{'mirror_pool': 'openstack1',
'replication_enabled': '<is> True',
'replication_type': '<in> metro'}),
({'replication_enabled': '<is> False'},
{'mirror_pool': 'openstack1',
'replication_enabled': '<is> True',
'replication_type': '<in> metro'}))
@ddt.unpack
def test_storwize_retype_invalid_replication(self, old_opts, new_opts):
# Set replication target
self.driver.configuration.set_override('replication_device',
[self.rep_target])
self.driver.do_setup(self.ctxt)
host = {'host': 'openstack@svc#openstack'}
old_type = self._create_replica_volume_type(
False, opts=old_opts, vol_type_name='test_old_type')
volume, model_update = self._create_test_volume(old_type)
new_type = self._create_replica_volume_type(
False, opts=new_opts, vol_type_name='test_new_type')
diff, _equal = volume_types.volume_types_diff(
self.ctxt, new_type['id'], old_type['id'])
self.assertRaises(exception.VolumeDriverException, self.driver.retype,
self.ctxt, volume, new_type, diff, host)
def test_storwize_retype_from_mirror_to_none_replication(self):
# Set replication target
self.driver.configuration.set_override('replication_device',
@ -6143,13 +6461,6 @@ class StorwizeSVCReplicationTestCase(test.TestCase):
volume, model_update = self._create_test_volume(self.mm_type)
self.assertEqual('enabled', model_update['replication_status'])
diff, _equal = volume_types.volume_types_diff(
self.ctxt, self.mm_type['id'], self.gm_type['id'])
# Change the mirror type
self.assertRaises(exception.VolumeDriverException,
self.driver.retype, self.ctxt,
volume, self.gm_type, diff, host)
diff, _equal = volume_types.volume_types_diff(
self.ctxt, self.non_replica_type['id'], self.mm_type['id'])
# Disable replica
@ -6275,6 +6586,33 @@ class StorwizeSVCReplicationTestCase(test.TestCase):
self.driver.delete_volume(rep_volume)
@mock.patch.object(storwize_svc_common.StorwizeHelpers, 'rename_vdisk')
@mock.patch.object(storwize_svc_common.StorwizeHelpers,
'get_relationship_info')
def test_storwize_update_migrated_replication_volume(
self, get_rp_info, rename_vdisk):
self.driver.configuration.set_override('replication_device',
[self.rep_target])
self.driver.do_setup(self.ctxt)
# Create replication volume.
backend_volume, model_update = self._create_test_volume(self.mm_type)
volume, model_update = self._create_test_volume(self.mm_type)
get_rp_info.side_effect = [{'aux_vdisk_name': 'aux_test'}]
model_update = self.driver.update_migrated_volume(self.ctxt, volume,
backend_volume,
'available')
aux_vol = (storwize_const.REPLICA_AUX_VOL_PREFIX + volume.name)
rename_vdisk.assert_called_with('aux_test', aux_vol)
self.assertEqual({'_name_id': None}, model_update)
rename_vdisk.reset_mock()
rename_vdisk.side_effect = exception.VolumeBackendAPIException
model_update = self.driver.update_migrated_volume(self.ctxt, volume,
backend_volume,
'available')
self.assertEqual({'_name_id': backend_volume.id}, model_update)
def test_storwize_delete_volume_with_mirror_replication(self):
# Set replication target.
self.driver.configuration.set_override('replication_device',

View File

@ -120,6 +120,10 @@ storwize_svc_opts = [
help='Specifies the Storwize FlashCopy copy rate to be used '
'when creating a full volume copy. The default is rate '
'is 50, and the valid rates are 1-100.'),
cfg.StrOpt('storwize_svc_mirror_pool',
default=None,
help='Specifies the name of the pool in which mirrored copy '
'is stored. Example: "pool2"'),
]
CONF = cfg.CONF
@ -196,7 +200,13 @@ class StorwizeSSH(object):
def lsmdiskgrp(self, pool):
ssh_cmd = ['svcinfo', 'lsmdiskgrp', '-bytes', '-delim', '!',
'"%s"' % pool]
return self.run_ssh_info(ssh_cmd)[0]
try:
return self.run_ssh_info(ssh_cmd)[0]
except exception.VolumeBackendAPIException as ex:
LOG.warning("Failed to get pool %(pool)s info. "
"Exception: %(ex)s.", {'pool': pool,
'ex': ex})
return None
def lsiogrp(self):
ssh_cmd = ['svcinfo', 'lsiogrp', '-delim', '!']
@ -542,9 +552,12 @@ class StorwizeSSH(object):
ssh_cmd = ['svctask', 'rmfcconsistgrp', '-force', fc_consist_group]
return self.run_ssh_assert_no_output(ssh_cmd)
def addvdiskcopy(self, vdisk, dest_pool, params):
def addvdiskcopy(self, vdisk, dest_pool, params, auto_delete):
ssh_cmd = (['svctask', 'addvdiskcopy'] + params + ['-mdiskgrp',
'"%s"' % dest_pool, '"%s"' % vdisk])
'"%s"' % dest_pool])
if auto_delete:
ssh_cmd += ['-autodelete']
ssh_cmd += ['"%s"' % vdisk]
return self.run_ssh_check_created(ssh_cmd)
def lsvdiskcopy(self, vdisk, copy_id=None):
@ -579,6 +592,11 @@ class StorwizeSSH(object):
'-filtervalue', 'node_id=%s' % node_id]
return self.run_ssh_info(ssh_cmd, with_header=True)
def migratevdisk(self, vdisk, dest_pool, copy_id='0'):
ssh_cmd = ['svctask', 'migratevdisk', '-mdiskgrp', dest_pool, '-copy',
copy_id, '-vdisk', vdisk]
self.run_ssh_assert_no_output(ssh_cmd)
class StorwizeHelpers(object):
@ -653,6 +671,11 @@ class StorwizeHelpers(object):
"""Return attributes for the specified pool."""
return self.ssh.lsmdiskgrp(pool)
def is_pool_defined(self, pool_name):
"""Check if vdisk is defined."""
attrs = self.get_pool_attrs(pool_name)
return attrs is not None
def get_available_io_groups(self):
"""Return list of available IO groups."""
iogrps = []
@ -1030,7 +1053,8 @@ class StorwizeHelpers(object):
'qos': None,
'stretched_cluster': cluster_partner,
'replication': False,
'nofmtdisk': config.storwize_svc_vol_nofmtdisk}
'nofmtdisk': config.storwize_svc_vol_nofmtdisk,
'mirror_pool': config.storwize_svc_mirror_pool}
return opt
@staticmethod
@ -1225,7 +1249,7 @@ class StorwizeHelpers(object):
return opts
@staticmethod
def _get_vdisk_create_params(opts):
def _get_vdisk_create_params(opts, add_copies=False):
easytier = 'on' if opts['easytier'] else 'off'
if opts['rsize'] == -1:
params = []
@ -1243,14 +1267,27 @@ class StorwizeHelpers(object):
else:
params.extend(['-grainsize', str(opts['grainsize'])])
if add_copies and opts['mirror_pool']:
params.extend(['-copies', '2'])
params.extend(['-easytier', easytier])
return params
def create_vdisk(self, name, size, units, pool, opts):
name = '"%s"' % name
LOG.debug('Enter: create_vdisk: vdisk %s.', name)
params = self._get_vdisk_create_params(opts)
self.ssh.mkvdisk(name, size, units, pool, opts, params)
mdiskgrp = pool
if opts['mirror_pool']:
if not self.is_pool_defined(opts['mirror_pool']):
raise exception.InvalidInput(
reason=_('The pool %s in which mirrored copy is stored '
'is invalid') % opts['mirror_pool'])
# The syntax of pool SVC expects is pool:mirror_pool in
# mdiskgrp for mirror volume
mdiskgrp = '%s:%s' % (pool, opts['mirror_pool'])
params = self._get_vdisk_create_params(
opts, add_copies=True if opts['mirror_pool'] else False)
self.ssh.mkvdisk(name, size, units, mdiskgrp, opts, params)
LOG.debug('Leave: _create_vdisk: volume %s.', name)
def get_vdisk_attributes(self, vdisk):
@ -1349,11 +1386,18 @@ class StorwizeHelpers(object):
for snapshot in snapshots:
opts = self.get_vdisk_params(config, state,
snapshot['volume_type_id'])
volume = snapshot.volume
if not volume:
msg = (_("Can't get volume from snapshot: %(id)s")
% {"id": snapshot.id})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
pool = utils.extract_host(volume.host, 'pool')
self.create_flashcopy_to_consistgrp(snapshot['volume_name'],
snapshot['name'],
fc_consistgrp,
config, opts)
config, opts, False,
pool=pool)
self.prepare_fc_consistgrp(fc_consistgrp, timeout)
self.start_fc_consistgrp(fc_consistgrp)
@ -1382,7 +1426,7 @@ class StorwizeHelpers(object):
try:
for snapshot in snapshots:
self.ssh.rmvdisk(snapshot['name'], True)
self.delete_vdisk(snapshot['name'], True)
except exception.VolumeBackendAPIException as err:
model_update['status'] = (
fields.GroupSnapshotStatus.ERROR_DELETING)
@ -1744,7 +1788,8 @@ class StorwizeHelpers(object):
def extend_vdisk(self, vdisk, amount):
self.ssh.expandvdisksize(vdisk, amount)
def add_vdisk_copy(self, vdisk, dest_pool, volume_type, state, config):
def add_vdisk_copy(self, vdisk, dest_pool, volume_type, state, config,
auto_delete=False):
"""Add a vdisk copy in the given pool."""
resp = self.ssh.lsvdiskcopy(vdisk)
if len(resp) > 1:
@ -1766,7 +1811,15 @@ class StorwizeHelpers(object):
opts = self.get_vdisk_params(config, state, volume_type['id'],
volume_type=volume_type)
params = self._get_vdisk_create_params(opts)
new_copy_id = self.ssh.addvdiskcopy(vdisk, dest_pool, params)
try:
new_copy_id = self.ssh.addvdiskcopy(vdisk, dest_pool, params,
auto_delete)
except exception.VolumeBackendAPIException as e:
msg = (_('Unable to add vdiskcopy for volume %(vol)s. '
'Exception: %(err)s.'),
{'vol': vdisk, 'err': e})
LOG.exception(msg)
raise exception.VolumeDriverException(message=msg)
return (orig_copy_id, new_copy_id)
def is_vdisk_copy_synced(self, vdisk, copy_id):
@ -1778,6 +1831,9 @@ class StorwizeHelpers(object):
def rm_vdisk_copy(self, vdisk, copy_id):
self.ssh.rmvdiskcopy(vdisk, copy_id)
def lsvdiskcopy(self, vdisk, copy_id=None):
return self.ssh.lsvdiskcopy(vdisk, copy_id)
@staticmethod
def can_migrate_to_host(host, state):
if 'location_info' not in host['capabilities']:
@ -1880,6 +1936,9 @@ class StorwizeHelpers(object):
def change_vdisk_primary_copy(self, vdisk, copy_id):
self.ssh.chvdisk(vdisk, ['-primary', copy_id])
def migratevdisk(self, vdisk, dest_pool, copy_id='0'):
self.ssh.migratevdisk(vdisk, dest_pool, copy_id)
class CLIResponse(object):
"""Parse SVC CLI output and generate iterable."""
@ -2148,11 +2207,9 @@ class StorwizeSVCCommonDriver(san.SanDriver,
# Validate that the pool exists
pools = self._get_backend_pools()
for pool in pools:
try:
self._helpers.get_pool_attrs(pool)
except exception.VolumeBackendAPIException:
msg = _('Failed getting details for pool %s.') % pool
raise exception.InvalidInput(reason=msg)
if not self._helpers.is_pool_defined(pool):
reason = (_('Failed getting details for pool %s.') % pool)
raise exception.InvalidInput(reason=reason)
def check_for_setup_error(self):
"""Ensure that the flags are set properly."""
@ -2337,8 +2394,14 @@ class StorwizeSVCCommonDriver(san.SanDriver,
opts = self._get_vdisk_params(volume['volume_type_id'],
volume_metadata=
volume.get('volume_metadata'))
pool = utils.extract_host(volume['host'], 'pool')
ctxt = context.get_admin_context()
rep_type = self._get_volume_replicated_type(ctxt, volume)
pool = utils.extract_host(volume['host'], 'pool')
if opts['mirror_pool'] and rep_type:
reason = _('Create mirror volume with replication enabled is '
'not supported.')
raise exception.InvalidInput(reason=reason)
opts['iogrp'] = self._helpers.select_io_group(self._state, opts)
self._helpers.create_vdisk(volume['name'], str(volume['size']),
'gb', pool, opts)
@ -2346,8 +2409,6 @@ class StorwizeSVCCommonDriver(san.SanDriver,
self._helpers.add_vdisk_qos(volume['name'], opts['qos'])
model_update = None
ctxt = context.get_admin_context()
rep_type = self._get_volume_replicated_type(ctxt, volume)
# The replication V2 has a higher priority than the replication V1.
# Check if V2 is available first, then check if V1 is available.
@ -2370,8 +2431,9 @@ class StorwizeSVCCommonDriver(san.SanDriver,
rep_type = self._get_volume_replicated_type(ctxt, volume)
if rep_type:
self._aux_backend_helpers.delete_rc_volume(volume['name'],
target_vol=True)
if self._aux_backend_helpers:
self._aux_backend_helpers.delete_rc_volume(volume['name'],
target_vol=True)
if not self._active_backend_id:
self._master_backend_helpers.delete_rc_volume(volume['name'])
else:
@ -2565,10 +2627,11 @@ class StorwizeSVCCommonDriver(san.SanDriver,
self._helpers.extend_vdisk(volume_name, extend_amt)
LOG.debug('leave: _extend_volume_op: volume %s', volume['id'])
def add_vdisk_copy(self, volume, dest_pool, vol_type):
def add_vdisk_copy(self, volume, dest_pool, vol_type, auto_delete=False):
return self._helpers.add_vdisk_copy(volume, dest_pool,
vol_type, self._state,
self.configuration)
self.configuration,
auto_delete=auto_delete)
def _add_vdisk_copy_op(self, ctxt, volume, new_op):
metadata = self.db.volume_admin_metadata_get(ctxt.elevated(),
@ -3164,13 +3227,78 @@ class StorwizeSVCCommonDriver(san.SanDriver,
else:
vol_type = None
self._check_volume_copy_ops()
new_op = self.add_vdisk_copy(volume['name'], dest_pool, vol_type)
self._add_vdisk_copy_op(ctxt, volume, new_op)
resp = self._helpers.lsvdiskcopy(volume.name)
if len(resp) > 1:
copies = self._helpers.get_vdisk_copies(volume.name)
self._helpers.migratevdisk(volume.name, dest_pool,
copies['primary']['copy_id'])
else:
self.add_vdisk_copy(volume.name, dest_pool, vol_type,
auto_delete=True)
LOG.debug('leave: migrate_volume: id=%(id)s, host=%(host)s',
{'id': volume['id'], 'host': host['host']})
{'id': volume.id, 'host': host['host']})
return (True, None)
def _verify_retype_params(self, volume, new_opts, old_opts, need_copy,
change_mirror, new_rep_type, old_rep_type):
# Some volume parameters can not be changed or changed at the same
# time during volume retype operation. This function checks the
# retype parameters.
resp = self._helpers.lsvdiskcopy(volume.name)
if old_opts['mirror_pool'] and len(resp) == 1:
msg = (_('Unable to retype: volume %s is a mirrorred vol. But it '
'has only one copy in storage.') % volume.name)
raise exception.VolumeDriverException(message=msg)
if need_copy:
# mirror volume can not add volume-copy again.
if len(resp) > 1:
msg = (_('Unable to retype: current action needs volume-copy. '
'A copy of volume %s exists. Adding another copy '
'would exceed the limit of 2 copies.') % volume.name)
raise exception.VolumeDriverException(message=msg)
if old_opts['mirror_pool'] or new_opts['mirror_pool']:
msg = (_('Unable to retype: current action needs volume-copy, '
'it is not allowed for mirror volume '
'%s.') % volume.name)
raise exception.VolumeDriverException(message=msg)
if change_mirror:
if (new_opts['mirror_pool'] and
not self._helpers.is_pool_defined(
new_opts['mirror_pool'])):
msg = (_('Unable to retype: The pool %s in which mirror copy '
'is stored is not valid') % new_opts['mirror_pool'])
raise exception.VolumeDriverException(message=msg)
# There are three options for rep_type: None, metro, global
if new_rep_type or old_rep_type:
# If volume is replicated, can't copy
if need_copy or new_opts['mirror_pool'] or old_opts['mirror_pool']:
msg = (_('Unable to retype: current action needs volume-copy, '
'it is not allowed for replication type. '
'Volume = %s') % volume.id)
raise exception.VolumeDriverException(message=msg)
if new_rep_type != old_rep_type:
old_io_grp = self._helpers.get_volume_io_group(volume.name)
if (old_io_grp not in
StorwizeHelpers._get_valid_requested_io_groups(
self._state, new_opts)):
msg = (_('Unable to retype: it is not allowed to change '
'replication type and io group at the same time.'))
LOG.error(msg)
raise exception.VolumeDriverException(message=msg)
if new_rep_type and old_rep_type:
msg = (_('Unable to retype: it is not allowed to change '
'%(old_rep_type)s volume to %(new_rep_type)s '
'volume.') %
{'old_rep_type': old_rep_type,
'new_rep_type': new_rep_type})
LOG.error(msg)
raise exception.VolumeDriverException(message=msg)
def retype(self, ctxt, volume, new_type, diff, host):
"""Convert the volume to be of the new type.
@ -3206,6 +3334,8 @@ class StorwizeSVCCommonDriver(san.SanDriver,
vdisk_changes = []
need_copy = False
change_mirror = False
for key in all_keys:
if old_opts[key] != new_opts[key]:
if key in copy_keys:
@ -3218,38 +3348,18 @@ class StorwizeSVCCommonDriver(san.SanDriver,
utils.extract_host(host['host'], 'pool')):
need_copy = True
if old_opts['mirror_pool'] != new_opts['mirror_pool']:
change_mirror = True
# Check if retype affects volume replication
model_update = None
new_rep_type = self._get_specs_replicated_type(new_type)
old_rep_type = self._get_volume_replicated_type(ctxt, volume)
old_io_grp = self._helpers.get_volume_io_group(volume['name'])
# There are three options for rep_type: None, metro, global
if new_rep_type != old_rep_type:
if (old_io_grp not in
StorwizeHelpers._get_valid_requested_io_groups(
self._state, new_opts)):
msg = (_('Unable to retype: it is not allowed to change '
'replication type and io group at the same time.'))
LOG.error(msg)
raise exception.VolumeDriverException(message=msg)
if new_rep_type and old_rep_type:
msg = (_('Unable to retype: it is not allowed to change '
'%(old_rep_type)s volume to %(new_rep_type)s '
'volume.') %
{'old_rep_type': old_rep_type,
'new_rep_type': new_rep_type})
LOG.error(msg)
raise exception.VolumeDriverException(message=msg)
# If volume is replicated, can't copy
if need_copy:
msg = (_('Unable to retype: Current action needs volume-copy,'
' it is not allowed when new type is replication.'
' Volume = %s') % volume['id'])
raise exception.VolumeDriverException(message=msg)
new_io_grp = self._helpers.select_io_group(self._state, new_opts)
self._verify_retype_params(volume, new_opts, old_opts, need_copy,
change_mirror, new_rep_type, old_rep_type)
if need_copy:
self._check_volume_copy_ops()
dest_pool = self._helpers.can_migrate_to_host(host, self._state)
@ -3259,10 +3369,8 @@ class StorwizeSVCCommonDriver(san.SanDriver,
retype_iogrp_property(volume,
new_io_grp, old_io_grp)
try:
new_op = self.add_vdisk_copy(volume['name'],
dest_pool,
new_type)
self._add_vdisk_copy_op(ctxt, volume, new_op)
self.add_vdisk_copy(volume['name'], dest_pool, new_type,
auto_delete=True)
except exception.VolumeDriverException:
# roll back changing iogrp property
retype_iogrp_property(volume, old_io_grp, new_io_grp)
@ -3275,7 +3383,23 @@ class StorwizeSVCCommonDriver(san.SanDriver,
self._helpers.change_vdisk_options(volume['name'], vdisk_changes,
new_opts, self._state)
if change_mirror:
copies = self._helpers.get_vdisk_copies(volume.name)
if not old_opts['mirror_pool'] and new_opts['mirror_pool']:
# retype from non mirror vol to mirror vol
self.add_vdisk_copy(volume['name'],
new_opts['mirror_pool'], new_type)
elif old_opts['mirror_pool'] and not new_opts['mirror_pool']:
# retype from mirror vol to non mirror vol
secondary = copies['secondary']
if secondary:
self._helpers.rm_vdisk_copy(
volume.name, secondary['copy_id'])
else:
# migrate the second copy to another pool.
self._helpers.migratevdisk(
volume.name, new_opts['mirror_pool'],
copies['secondary']['copy_id'])
if new_opts['qos']:
# Add the new QoS setting to the volume. If the volume has an
# old QoS setting, it will be overwritten.
@ -3322,6 +3446,13 @@ class StorwizeSVCCommonDriver(san.SanDriver,
original_volume_name = CONF.volume_name_template % volume['id']
try:
self._helpers.rename_vdisk(current_name, original_volume_name)
rep_type = self._get_volume_replicated_type(ctxt, new_volume)
if rep_type:
rel_info = self._helpers.get_relationship_info(current_name)
aux_vol = (storwize_const.REPLICA_AUX_VOL_PREFIX +
original_volume_name)
self._aux_backend_helpers.rename_vdisk(
rel_info['aux_vdisk_name'], aux_vol)
except exception.VolumeBackendAPIException:
LOG.error('Unable to rename the logical volume '
'for volume: %s', volume['id'])
@ -3357,6 +3488,7 @@ class StorwizeSVCCommonDriver(san.SanDriver,
rep_type = self._get_volume_replicated_type(ctxt, volume)
vol_rep_type = None
rel_info = self._helpers.get_relationship_info(vdisk['name'])
copies = self._helpers.get_vdisk_copies(vdisk['name'])
if rel_info:
vol_rep_type = rel_info['copy_type']
aux_info = self._aux_backend_helpers.get_system_info()
@ -3379,8 +3511,28 @@ class StorwizeSVCCommonDriver(san.SanDriver,
opts = self._get_vdisk_params(volume['volume_type_id'],
volume_metadata=
volume.get('volume_metadata'))
vdisk_copy = self._helpers.get_vdisk_copy_attrs(vdisk['name'], '0')
resp = self._helpers.lsvdiskcopy(vdisk['name'])
expected_copy_num = 2 if opts['mirror_pool'] else 1
if len(resp) != expected_copy_num:
msg = (_("Failed to manage existing volume due to mirror type "
"mismatch. Volume to be managed has %(resp_len)s "
"copies. mirror_pool of the chosen type is "
"%(mirror_pool)s.") %
{'resp_len': len(resp),
'mirror_pool': opts['mirror_pool']})
raise exception.ManageExistingVolumeTypeMismatch(reason=msg)
if (opts['mirror_pool']and opts['mirror_pool'] !=
copies['secondary']['mdisk_grp_name']):
msg = (_("Failed to manage existing volume due to mirror pool "
"mismatch. The secondary pool of the volume to be "
"managed is %(sec_copy_pool)s. mirror_pool of the "
"chosen type is %(mirror_pool)s.") %
{'sec_copy_pool': copies['secondary']['mdisk_grp_name'],
'mirror_pool': opts['mirror_pool']})
raise exception.ManageExistingVolumeTypeMismatch(
reason=msg)
vdisk_copy = self._helpers.get_vdisk_copy_attrs(vdisk['name'], '0')
if vdisk_copy['autoexpand'] == 'on' and opts['rsize'] == -1:
msg = (_("Failed to manage existing volume due to "
"the volume to be managed is thin, but "
@ -3418,15 +3570,14 @@ class StorwizeSVCCommonDriver(san.SanDriver,
'opt_iogrp': opts['iogrp']})
raise exception.ManageExistingVolumeTypeMismatch(reason=msg)
pool = utils.extract_host(volume['host'], 'pool')
if vdisk['mdisk_grp_name'] != pool:
if copies['primary']['mdisk_grp_name'] != pool:
msg = (_("Failed to manage existing volume due to the "
"pool of the volume to be managed does not "
"match the backend pool. Pool of the "
"volume to be managed is %(vdisk_pool)s. Pool "
"of the backend is %(backend_pool)s.") %
{'vdisk_pool': vdisk['mdisk_grp_name'],
'backend_pool':
self._get_backend_pools()})
{'vdisk_pool': copies['primary']['mdisk_grp_name'],
'backend_pool': pool})
raise exception.ManageExistingVolumeTypeMismatch(reason=msg)
model_update = {}

View File

@ -89,9 +89,10 @@ class StorwizeSVCFCDriver(storwize_common.StorwizeSVCCommonDriver):
mode
2.1.1 - Update replication to version 2.1
2.2 - Add CG capability to generic volume groups
2.2.1 - Add vdisk mirror/stretch cluster support
"""
VERSION = "2.2"
VERSION = "2.2.1"
# ThirdPartySystems wiki page
CI_WIKI_NAME = "IBM_STORAGE_CI"

View File

@ -89,9 +89,10 @@ class StorwizeSVCISCSIDriver(storwize_common.StorwizeSVCCommonDriver):
mode
2.1.1 - Update replication to version 2.1
2.2 - Add CG capability to generic volume groups
2.2.1 - Add vdisk mirror/stretch cluster support
"""
VERSION = "2.2"
VERSION = "2.2.1"
# ThirdPartySystems wiki page
CI_WIKI_NAME = "IBM_STORAGE_CI"

View File

@ -0,0 +1,3 @@
---
features:
- Add mirrored volume support in IBM SVC/Storwize driver.