Merge "Storwize: add mirrored volume support"

This commit is contained in:
Jenkins 2017-06-12 06:12:45 +00:00 committed by Gerrit Code Review
commit 31c2328c86
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.