PowerMax Driver - retype attached replication fix

Fixes for retyping attached volumes when moving from
non-replicated to replication enabled volume type.
Cases fixed:
-Retype to metro replication with existing pairs.
-Retype to non-replicated after retyping to metro.
-Retype to asynchronous replication with existing pairs present.
-Delete attached volume with asynchronous replication after
attaching to an instance.

Change-Id: Ibbf04dedade6e85649ec702574ce51ce82fc8733
Closes-Bug: #1851371
(cherry picked from commit 49b941b83a)
This commit is contained in:
odonos12 2019-10-03 16:31:11 +01:00 committed by Helen Walsh
parent 89bf1d2188
commit ad1b2d32a5
7 changed files with 143 additions and 47 deletions

View File

@ -296,6 +296,8 @@ class PowerMaxData(object):
rep_extra_specs4['rdf_group_label'] = rdf_group_name
rep_extra_specs5 = deepcopy(rep_extra_specs2)
rep_extra_specs5['target_array_model'] = 'VMAX250F'
rep_extra_specs6 = deepcopy(rep_extra_specs3)
rep_extra_specs6['target_array_model'] = 'PMAX2000'
rep_extra_specs_ode = deepcopy(rep_extra_specs2)
rep_extra_specs_ode['array'] = array

View File

@ -1514,11 +1514,12 @@ class PowerMaxCommonTest(test.TestCase):
mck_add_sg_to_sg.assert_called()
self.assertTrue(success)
@mock.patch.object(
rest.PowerMaxRest, 'get_volume',
return_value=tpd.PowerMaxData.volume_details_attached)
@mock.patch.object(provision.PowerMaxProvision, 'create_storage_group',
return_value=None)
@mock.patch.object(rest.PowerMaxRest, 'get_volume',
return_value=tpd.PowerMaxData.volume_details_attached)
@mock.patch.object(rest.PowerMaxRest, 'get_storage_group',
return_value=tpd.PowerMaxData.sg_details[1])
side_effect=[tpd.PowerMaxData.sg_details[1], None])
@mock.patch.object(utils.PowerMaxUtils, 'get_child_sg_name',
return_value=('OS-Test-SG', '', '', ''))
@mock.patch.object(rest.PowerMaxRest, 'is_child_sg_in_parent_sg',
@ -1529,7 +1530,7 @@ class PowerMaxCommonTest(test.TestCase):
return_value=False)
def test_retype_inuse_volume_fail(self, mck_vol_in_sg, mck_sg_move,
mck_child_sg_in_sg, mck_get_sg_name,
mck_get_sg, mck_get_vol):
mck_get_sg, mck_get_vol, mck_create_sg):
array = self.data.array
srp = self.data.srp
slo = self.data.slo
@ -1688,8 +1689,13 @@ class PowerMaxCommonTest(test.TestCase):
return_value=True)
@mock.patch.object(common.PowerMaxCommon, 'get_volume_metadata',
return_value='')
def test_migrate_in_use_volume(self, mck_meta, mck_remote_retype,
mck_setup, mck_retype, mck_cleanup):
@mock.patch.object(utils.PowerMaxUtils, 'get_async_rdf_managed_grp_name')
@mock.patch.object(rest.PowerMaxRest, 'get_storage_group',
return_value=True)
@mock.patch.object(masking.PowerMaxMasking, 'add_volume_to_storage_group')
def test_migrate_in_use_volume(
self, mck_add_vol, mck_get_sg, mck_get_rdf_name, mck_meta,
mck_remote_retype, mck_setup, mck_retype, mck_cleanup):
# Array/Volume info
array = self.data.array
srp = self.data.srp
@ -1752,6 +1758,8 @@ class PowerMaxCommonTest(test.TestCase):
# Scenario 3: no_rep => rep
with mock.patch.object(self.utils, 'is_replication_enabled',
side_effect=[False, True]):
tgt_extra_specs['rep_mode'] = utils.REP_METRO
self.common.rep_config['mode'] = utils.REP_METRO
success = self.common._migrate_volume(
array, volume, device_id, srp, slo, workload, volume_name,
new_type, src_extra_specs)[0]
@ -1763,6 +1771,9 @@ class PowerMaxCommonTest(test.TestCase):
mck_retype.assert_called_once_with(
array, srp, volume, device_id, src_extra_specs, slo,
workload, tgt_extra_specs, False)
mck_add_vol.assert_called_once()
mck_get_sg.assert_called_once()
mck_get_rdf_name.assert_called_once()
mck_cleanup.assert_not_called()
mck_remote_retype.assert_not_called()
self.assertTrue(success)
@ -1778,7 +1789,7 @@ class PowerMaxCommonTest(test.TestCase):
array, srp, volume, device_id, src_extra_specs, slo, workload,
tgt_extra_specs, False)
mck_remote_retype.assert_called_once_with(
array, volume, device_id, volume_name, rep_mode, True,
array, volume, device_id, volume_name, utils.REP_METRO, True,
tgt_extra_specs)
mck_cleanup.assert_not_called()
mck_setup.assert_not_called()

View File

@ -32,6 +32,7 @@ from cinder.volume.drivers.dell_emc.powermax import common
from cinder.volume.drivers.dell_emc.powermax import fc
from cinder.volume.drivers.dell_emc.powermax import iscsi
from cinder.volume.drivers.dell_emc.powermax import masking
from cinder.volume.drivers.dell_emc.powermax import metadata
from cinder.volume.drivers.dell_emc.powermax import provision
from cinder.volume.drivers.dell_emc.powermax import rest
from cinder.volume.drivers.dell_emc.powermax import utils
@ -238,6 +239,7 @@ class PowerMaxReplicationTest(test.TestCase):
def test_unmap_lun_volume_failed_over(self, mock_fo, mock_es, mock_rm):
extra_specs = deepcopy(self.extra_specs)
extra_specs[utils.PORTGROUPNAME] = self.data.port_group_name_f
extra_specs[utils.IS_RE] = True
rep_config = self.utils.get_replication_config(
[self.replication_device])
self.common._unmap_lun(self.data.test_volume, self.data.connector)
@ -399,32 +401,71 @@ class PowerMaxReplicationTest(test.TestCase):
'device_id': self.data.device_id2}, rep_data)
mock_create.assert_not_called()
@mock.patch.object(common.PowerMaxCommon, 'get_rdf_details',
return_value=(tpd.PowerMaxData.rdf_group_no,
tpd.PowerMaxData.remote_array))
@mock.patch.object(rest.PowerMaxRest, 'get_size_of_device_on_array',
return_value=2)
@mock.patch.object(rest.PowerMaxRest, 'get_rdf_group', return_value={
'numDevices': 1})
@mock.patch.object(rest.PowerMaxRest, 'get_size_of_device_on_array')
@mock.patch.object(common.PowerMaxCommon, '_get_replication_extra_specs',
return_value=tpd.PowerMaxData.rep_extra_specs5)
@mock.patch.object(common.PowerMaxCommon, '_create_volume',
return_value=tpd.PowerMaxData.provider_location)
return_value=tpd.PowerMaxData.rep_extra_specs6)
@mock.patch.object(common.PowerMaxCommon, '_create_volume', return_value={
'device_id': tpd.PowerMaxData.device_id2})
@mock.patch.object(rest.PowerMaxRest, 'get_storage_group',
return_value=None)
@mock.patch.object(rest.PowerMaxRest, 'create_storage_group')
@mock.patch.object(masking.PowerMaxMasking, 'add_volume_to_storage_group')
@mock.patch.object(common.PowerMaxCommon, '_sync_check')
@mock.patch.object(rest.PowerMaxRest, 'create_rdf_device_pair',
return_value=tpd.PowerMaxData.rdf_group_details)
def test_setup_inuse_volume_replication(self, mck_create_rdf_pair,
mck_sync_chk, mck_create_vol,
mck_rep_specs, mck_get_vol_size,
mck_get_rdf_info):
return_value={'rdf_dict'})
@mock.patch.object(metadata.PowerMaxVolumeMetadata,
'gather_replication_info',
return_value={'rep_info_dict'})
def test_setup_inuse_volume_replication(
self, mck_gather_rep_info, mck_create_rdf_pair, mck_sync_check,
mck_add_vol_to_sg, mck_create_sg, mck_get_sg, mck_create_vol,
mck_get_rep_specs, mck_get_size, mck_get_rdf_grp):
array = self.data.array
device_id = self.data.device_id
volume = self.data.test_attached_volume
extra_specs = self.data.extra_specs_migrate
self.rep_config = self.data.rep_extra_specs4
rep_status, rep_data, __ = (
volume_id = volume.id
target_name = self.common.utils.get_volume_element_name(volume_id)
target_device_id = tpd.PowerMaxData.device_id2
device_id = self.data.device_id
extra_specs = self.data.extra_specs_rep_enabled
self.common.rep_config['mode'] = utils.REP_METRO
rdf_group_no, remote_array = self.common.get_rdf_details(array)
rep_extra_specs = self.common._get_replication_extra_specs(
extra_specs, self.common.rep_config)
async_sg = self.common.utils.get_async_rdf_managed_grp_name(
self.common.rep_config)
status, driver_data, info_dict = (
self.common.setup_inuse_volume_replication(
array, volume, device_id, extra_specs))
self.assertEqual('enabled', rep_status)
self.assertEqual(self.data.rdf_group_details, rep_data)
self.assertEqual(status, common.REPLICATION_ENABLED)
self.assertEqual(driver_data, {'rdf_dict'})
self.assertEqual(info_dict, {'rep_info_dict'})
mck_get_rdf_grp.assert_called_with(array, rdf_group_no)
mck_get_size.assert_called_once_with(array, device_id)
mck_get_rep_specs.assert_called_with(
extra_specs, self.common.rep_config)
mck_create_vol.assert_called_once()
mck_get_sg.assert_called_once_with(remote_array, async_sg)
mck_create_sg.assert_called_once_with(
remote_array, async_sg, extra_specs['srp'], extra_specs['slo'],
extra_specs['workload'], rep_extra_specs)
mck_sync_check.assert_called_once_with(array, device_id, extra_specs,
tgt_only=True)
mck_add_vol_to_sg.assert_called_once_with(
remote_array, target_device_id, async_sg, target_name,
rep_extra_specs, True)
mck_create_rdf_pair.assert_called_once_with(
array, device_id, rdf_group_no, target_device_id, remote_array,
extra_specs)
mck_gather_rep_info.assert_called_with(
volume_id, 'replication', False, rdf_group_no=rdf_group_no,
target_name=target_name, remote_array=remote_array,
target_device_id=target_device_id,
replication_status=common.REPLICATION_ENABLED,
rep_mode=rep_extra_specs['rep_mode'],
rdf_group_label=self.common.rep_config['rdf_group_label'],
target_array_model=rep_extra_specs['target_array_model'])
@mock.patch.object(rest.PowerMaxRest, 'get_array_model_info',
return_value=('VMAX250F', False))

View File

@ -451,14 +451,24 @@ class PowerMaxUtilsTest(test.TestCase):
def test_get_child_sg_name(self):
host_name = 'HostX'
# Slo and rep enabled
extra_specs1 = self.data.extra_specs_rep_enabled
extra_specs1[utils.PORTGROUPNAME] = self.data.port_group_name_f
extra_specs1 = {
'pool_name': u'Diamond+DSS+SRP_1+000197800123',
'slo': 'Diamond',
'workload': 'DSS',
'srp': 'SRP_1',
'array': self.data.array,
'interval': 3,
'retries': 120,
'replication_enabled': True,
'rep_mode': 'Synchronous',
utils.PORTGROUPNAME: self.data.port_group_name_f}
child_sg_name, do_disable_compression, rep_enabled, pg_name = (
self.utils.get_child_sg_name(host_name, extra_specs1))
re_name = self.data.storagegroup_name_f + '-RE'
self.assertEqual(re_name, child_sg_name)
# Disable compression
extra_specs2 = self.data.extra_specs_disable_compression
extra_specs2 = deepcopy(self.data.extra_specs_disable_compression)
extra_specs2[utils.PORTGROUPNAME] = self.data.port_group_name_f
child_sg_name, do_disable_compression, rep_enabled, pg_name = (
self.utils.get_child_sg_name(host_name, extra_specs2))

View File

@ -3182,6 +3182,21 @@ class PowerMaxCommon(object):
target_slo, target_workload, target_extra_specs,
is_compression_disabled)
# Ensure that storage groups for metro volumes stay consistent
if not was_rep_enabled and is_rep_enabled and (
self.rep_config['mode'] is utils.REP_METRO):
async_sg = self.utils.get_async_rdf_managed_grp_name(
self.rep_config)
sg_exists = self.rest.get_storage_group(array, async_sg)
if not sg_exists:
self.rest.create_storage_group(
array, async_sg, extra_specs['srp'],
extra_specs['slo'], extra_specs['workload'],
extra_specs)
self.masking.add_volume_to_storage_group(
array, device_id, async_sg, volume_name, extra_specs,
True)
# If the volume was replication enabled both before and after
# retype, the volume needs to be retyped on the remote array also
if was_rep_enabled and is_rep_enabled:
@ -3334,20 +3349,16 @@ class PowerMaxCommon(object):
target_sg = self.rest.get_storage_group(array, target_sg_name)
if not target_sg:
self.provision.create_storage_group(array, target_sg_name, srp,
target_slo,
target_workload,
target_extra_specs,
is_compression_disabled)
parent_sg = source_sg['parent_storage_group'][0]
self.masking.add_child_sg_to_parent_sg(
array, target_sg_name, parent_sg, target_extra_specs)
target_sg = self.rest.get_storage_group(array, target_sg_name)
target_sg = self.provision.create_storage_group(
array, target_sg_name, srp, target_slo, target_workload,
target_extra_specs, is_compression_disabled)
parent_sg = source_sg.get('parent_storage_group')
if parent_sg:
parent_sg = parent_sg[0]
self.masking.add_child_sg_to_parent_sg(
array, target_sg_name, parent_sg, target_extra_specs)
target_in_parent = self.rest.is_child_sg_in_parent_sg(
array, target_sg_name, target_sg['parent_storage_group'][0])
if target_sg and target_in_parent:
if target_sg:
self.masking.move_volume_between_storage_groups(
array, device_id, source_sg_name, target_sg_name,
target_extra_specs)
@ -3588,6 +3599,9 @@ class PowerMaxCommon(object):
rdf_group_no, remote_array = self.get_rdf_details(array)
extra_specs['replication_enabled'] = '<is> True'
extra_specs['rep_mode'] = self.rep_config['mode']
group_details = self.rest.get_rdf_group(array, rdf_group_no)
volumes_in_group = group_details['numDevices']
async_sg = self.utils.get_async_rdf_managed_grp_name(self.rep_config)
rdf_vol_size = volume.size
if rdf_vol_size == 0:
@ -3598,8 +3612,24 @@ class PowerMaxCommon(object):
rep_extra_specs = self._get_replication_extra_specs(
extra_specs, self.rep_config)
volume_dict = self._create_volume(
target_name, rdf_vol_size, rep_extra_specs, in_use=True)
rep_mode = self.rep_config['mode']
if (volumes_in_group > 0) and (rep_mode is utils.REP_METRO or
rep_mode is utils.REP_ASYNC):
volume_dict = self._create_volume(
target_name, rdf_vol_size, rep_extra_specs)
sg_exists = self.rest.get_storage_group(remote_array, async_sg)
if not sg_exists:
self.rest.create_storage_group(
remote_array, async_sg, extra_specs['srp'],
extra_specs['slo'], extra_specs['workload'],
rep_extra_specs)
self.masking.add_volume_to_storage_group(
remote_array, volume_dict['device_id'], async_sg,
target_name, rep_extra_specs, True)
else:
volume_dict = self._create_volume(
target_name, rdf_vol_size, rep_extra_specs, in_use=True)
target_device_id = volume_dict['device_id']
LOG.debug("Create volume replica: Target device: %(target)s "

View File

@ -118,9 +118,10 @@ class PowerMaxFCDriver(san.SanDriver, driver.FibreChannelDriver):
- Debug metadata compression and service level info fix
4.1.1 - QoS calulation fix
4.1.2 - Volume group delete fix (bug #1853589)
4.1.3 - Retype attached replication fix (#1851371)
"""
VERSION = "4.1.2"
VERSION = "4.1.3"
# ThirdPartySystems wiki
CI_WIKI_NAME = "EMC_VMAX_CI"

View File

@ -123,9 +123,10 @@ class PowerMaxISCSIDriver(san.SanISCSIDriver):
- Debug metadata compression and service level info fix
4.1.1 - QoS calulation fix
4.1.2 - Volume group delete fix (bug #1853589)
4.1.3 - Retype attached replication fix (#1851371)
"""
VERSION = "4.1.2"
VERSION = "4.1.3"
# ThirdPartySystems wiki
CI_WIKI_NAME = "EMC_VMAX_CI"