summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHelen Walsh <helen.walsh@emc.com>2020-02-07 15:01:48 +0000
committerHelen Walsh <helen.walsh@emc.com>2020-10-09 10:14:14 +0100
commitfa48c21fba30c6d44ee9b96b8d657575e207e15c (patch)
treef4492fd400c933933ab9cf4339d86c087b59f26c
parent73ba22632b9edbb0fd65907adc0893614e300569 (diff)
downloadcinder-fa48c21fba30c6d44ee9b96b8d657575e207e15c.tar.gz
PowerMax Driver - Safeguarding retype to some in-use replicated modes
Host assisted migration is a better option for retyping from a non-replicated to a replicated (Asynchronous and Metro) modes. 1. Block in-use non-replicated to replicated(Metro and Asynchronous) 2. Remove any unused code 3. Differentiate between the production and the management storage groups 4. Remove rollback code on a cleanup_lun, its not necessary on delete and will not always work 5. Allow for a target device not to exist on the target array 6. Fix minor issue on metadata on a retype Closes-Bug: #1899137 Change-Id: I6c7c94528ee94183c1df21c25388505b28f1781a (cherry picked from commit 92aeec3ad9dbb0482c2531a0b42c06326ae945fd)
-rw-r--r--cinder/tests/unit/volume/drivers/dell_emc/powermax/powermax_data.py28
-rw-r--r--cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_common.py30
-rw-r--r--cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_provision.py6
-rw-r--r--cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_replication.py51
-rw-r--r--cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_utils.py135
-rw-r--r--cinder/volume/drivers/dell_emc/powermax/common.py79
-rw-r--r--cinder/volume/drivers/dell_emc/powermax/fc.py3
-rw-r--r--cinder/volume/drivers/dell_emc/powermax/iscsi.py3
-rw-r--r--cinder/volume/drivers/dell_emc/powermax/metadata.py9
-rw-r--r--cinder/volume/drivers/dell_emc/powermax/provision.py4
-rw-r--r--cinder/volume/drivers/dell_emc/powermax/rest.py7
-rw-r--r--cinder/volume/drivers/dell_emc/powermax/utils.py126
-rw-r--r--releasenotes/notes/powermax-disable-inuse-metro-89e9f398ec9e2672.yaml6
13 files changed, 393 insertions, 94 deletions
diff --git a/cinder/tests/unit/volume/drivers/dell_emc/powermax/powermax_data.py b/cinder/tests/unit/volume/drivers/dell_emc/powermax/powermax_data.py
index 3842b12bc..6bba029a6 100644
--- a/cinder/tests/unit/volume/drivers/dell_emc/powermax/powermax_data.py
+++ b/cinder/tests/unit/volume/drivers/dell_emc/powermax/powermax_data.py
@@ -248,7 +248,7 @@ class PowerMaxData(object):
test_volume_attachment = volume_attachment.VolumeAttachment(
id='2b06255d-f5f0-4520-a953-b029196add6b', volume_id=test_volume.id,
- connector=connector)
+ connector=connector, attached_host='HostX')
location_info = {'location_info': '000197800123#SRP_1#Diamond#DSS',
'storage_protocol': 'FC'}
@@ -632,6 +632,17 @@ class PowerMaxData(object):
'snapvx_source': 'false',
'storageGroupId': []}
+ volume_details_attached_async = (
+ {'cap_gb': 2,
+ 'num_of_storage_groups': 1,
+ 'volumeId': device_id,
+ 'volume_identifier': 'OS-%s' % test_volume.id,
+ 'wwn': volume_wwn,
+ 'snapvx_target': 'false',
+ 'snapvx_source': 'false',
+ 'storageGroupId': [
+ rdf_managed_async_grp, storagegroup_name_f + '-RA']})
+
volume_list = [
{'id': '6b70de13-98c5-46b2-8f24-e4e96a8988fa',
'count': 2,
@@ -1229,3 +1240,18 @@ class PowerMaxData(object):
legacy_mvs = [legacy_mv1, legacy_mv2]
legacy_not_shared_mv = 'OS-myhostA-SRP_1-Diamond-NONE-MV'
legacy_not_shared_sg = 'OS-myhostA-SRP_1-Diamond-NONE-SG'
+
+ # retype metadata dict
+ retype_metadata_dict = {
+ 'device_id': device_id,
+ 'rdf_group_no': '10',
+ 'remote_array': remote_array,
+ 'target_device_id': device_id,
+ 'rep_mode': 'Asynchronous',
+ 'replication_status': 'enabled',
+ 'target_array_model': array_model}
+
+ retype_metadata_dict2 = {
+ 'default_sg_name': 'default-sg',
+ 'service_level': 'Diamond'
+ }
diff --git a/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_common.py b/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_common.py
index 442cc1d1c..5b424193b 100644
--- a/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_common.py
+++ b/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_common.py
@@ -1415,6 +1415,8 @@ class PowerMaxCommonTest(test.TestCase):
new_type, host)
mock_retype.assert_called_once()
+ @mock.patch.object(utils.PowerMaxUtils, 'get_volume_attached_hostname',
+ return_value='HostX')
@mock.patch.object(
rest.PowerMaxRest, 'get_volume',
return_value=tpd.PowerMaxData.volume_details_attached)
@@ -1431,13 +1433,16 @@ class PowerMaxCommonTest(test.TestCase):
def test_retype_inuse_volume_tgt_sg_exist(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,
+ mock_host):
array = self.data.array
srp = self.data.srp
slo = self.data.slo
workload = self.data.workload
device_id = self.data.device_id
- volume = self.data.test_attached_volume
+ attached_volume = deepcopy(self.data.test_volume)
+ attached_volume.volume_attachment.objects = [
+ self.data.test_volume_attachment]
rep_mode = 'Synchronous'
src_extra_specs = self.data.extra_specs_migrate
interval = src_extra_specs['interval']
@@ -1447,12 +1452,14 @@ class PowerMaxCommonTest(test.TestCase):
'interval': interval, 'retries': retries, 'rep_mode': rep_mode}
success = self.common._retype_inuse_volume(
- array, srp, volume, device_id, src_extra_specs, slo, workload,
- tgt_extra_specs, False)[0]
+ array, srp, attached_volume, device_id, src_extra_specs, slo,
+ workload, tgt_extra_specs, False)[0]
self.assertTrue(success)
mck_sg_move.assert_called()
mck_vol_in_sg.assert_called()
+ @mock.patch.object(utils.PowerMaxUtils, 'get_volume_attached_hostname',
+ return_value='HostX')
@mock.patch.object(
rest.PowerMaxRest, 'get_volume',
return_value=tpd.PowerMaxData.volume_details_attached)
@@ -1469,7 +1476,7 @@ class PowerMaxCommonTest(test.TestCase):
def test_retype_inuse_volume_no_tgt_sg(self, mck_vol_in_sg, mck_move_vol,
mck_sg_in_sg, mck_add_sg_to_sg,
mck_create_sg, mck_get_csg_name,
- mck_get_vol):
+ mck_get_vol, mock_host):
array = self.data.array
srp = self.data.srp
slo = self.data.slo
@@ -1746,17 +1753,8 @@ class PowerMaxCommonTest(test.TestCase):
mck_setup_specs = src_extra_specs
mck_setup_specs[utils.METROBIAS] = self.common.rep_config[
'metro_use_bias']
- mck_setup.assert_called_once_with(
- self.data.array, volume, device_id, mck_setup_specs)
- 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)
+ mck_setup.assert_not_called()
+ self.assertFalse(success)
_reset_mocks()
# Scenario 4: rep => rep
diff --git a/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_provision.py b/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_provision.py
index 3d83342aa..125e0ff0d 100644
--- a/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_provision.py
+++ b/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_provision.py
@@ -118,7 +118,7 @@ class PowerMaxProvisionTest(test.TestCase):
snap_name, extra_specs, create_snap=True)
mock_modify.assert_called_once_with(
array, source_device_id, target_device_id, snap_name,
- extra_specs, link=True)
+ extra_specs, link=True, copy_mode=False)
mock_create_snapvx.assert_called_once_with(
array, source_device_id, snap_name, extra_specs, ttl=ttl)
@@ -134,10 +134,10 @@ class PowerMaxProvisionTest(test.TestCase):
self.provision.rest, 'modify_volume_snap') as mock_modify:
self.provision.create_volume_replica(
array, source_device_id, target_device_id,
- snap_name, extra_specs, create_snap=False)
+ snap_name, extra_specs, create_snap=False, copy_mode=True)
mock_modify.assert_called_once_with(
array, source_device_id, target_device_id, snap_name,
- extra_specs, link=True)
+ extra_specs, link=True, copy_mode=True)
mock_create_snapvx.assert_not_called()
def test_break_replication_relationship(self):
diff --git a/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_replication.py b/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_replication.py
index 6a22a5097..575c3d33b 100644
--- a/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_replication.py
+++ b/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_replication.py
@@ -503,24 +503,24 @@ class PowerMaxReplicationTest(test.TestCase):
self.extra_specs)
mock_clean.assert_not_called()
- @mock.patch.object(
- common.PowerMaxCommon, 'get_remote_target_device',
- return_value=(tpd.PowerMaxData.device_id2, '', '', '', ''))
- @mock.patch.object(common.PowerMaxCommon,
- '_add_volume_to_async_rdf_managed_grp')
- def test_cleanup_lun_replication_exception(self, mock_add, mock_tgt):
- self.assertRaises(exception.VolumeBackendAPIException,
- self.common.cleanup_lun_replication,
- self.data.test_volume, '1', self.data.device_id,
- self.extra_specs)
- # is metro or async volume
- extra_specs = deepcopy(self.extra_specs)
- extra_specs[utils.REP_MODE] = utils.REP_METRO
- self.assertRaises(exception.VolumeBackendAPIException,
- self.common.cleanup_lun_replication,
- self.data.test_volume, '1', self.data.device_id,
- extra_specs)
- mock_add.assert_called_once()
+ @mock.patch.object(rest.PowerMaxRest, 'get_array_model_info',
+ return_value=('VMAX250F', False))
+ @mock.patch.object(common.PowerMaxCommon, '_cleanup_remote_target')
+ @mock.patch.object(utils.PowerMaxUtils, 'get_rdf_managed_storage_group',
+ return_value=(
+ tpd.PowerMaxData.rdf_managed_async_grp, {}))
+ @mock.patch.object(rest.PowerMaxRest, 'remove_vol_from_sg')
+ def test_cleanup_lun_replication_async(
+ self, mock_rm_sg, mock_get_rdf_sg, mock_clean, mock_model):
+ rep_extra_specs = deepcopy(self.data.rep_extra_specs)
+ rep_extra_specs[utils.PORTGROUPNAME] = self.data.port_group_name_f
+ rep_extra_specs['target_array_model'] = 'VMAX250F'
+ self.common.cleanup_lun_replication(
+ self.data.test_volume, '1', self.data.device_id,
+ self.extra_specs)
+ mock_rm_sg.assert_called_once_with(
+ self.data.array, self.data.rdf_managed_async_grp,
+ self.data.device_id, self.extra_specs)
@mock.patch.object(common.PowerMaxCommon, '_cleanup_metro_target')
@mock.patch.object(masking.PowerMaxMasking,
@@ -652,6 +652,21 @@ class PowerMaxReplicationTest(test.TestCase):
self.data.device_id))
self.assertIsNone(target_device4)
+ @mock.patch.object(common.PowerMaxCommon, 'get_rdf_details',
+ return_value=(tpd.PowerMaxData.rdf_group_name,
+ tpd.PowerMaxData.remote_array))
+ @mock.patch.object(rest.PowerMaxRest, 'get_volume',
+ side_effect=exception.VolumeBackendAPIException(
+ data=''))
+ def test_get_remote_target_device_no_target(
+ self, mock_get_vol, mock_get_rdf):
+ target_device, remote_array, rdf_group, local_vol_state, pair_state = (
+ self.common.get_remote_target_device(
+ self.data.array, self.data.test_volume, self.data.device_id))
+ self.assertIsNone(target_device)
+ self.assertEqual('', local_vol_state)
+ self.assertEqual('', pair_state)
+
@mock.patch.object(rest.PowerMaxRest, 'get_array_model_info',
return_value=('VMAX250F', False))
@mock.patch.object(common.PowerMaxCommon,
diff --git a/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_utils.py b/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_utils.py
index 60587b311..0c78ef1eb 100644
--- a/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_utils.py
+++ b/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_utils.py
@@ -509,14 +509,13 @@ class PowerMaxUtilsTest(test.TestCase):
self.utils.is_snapshot_manageable(volume))
def test_get_volume_attached_hostname(self):
- device_info_pass = self.data.volume_details_attached
+
+ attached_volume = deepcopy(self.data.test_volume)
+ attached_volume.volume_attachment.objects = [
+ self.data.test_volume_attachment]
# Success
- hostname = self.utils.get_volume_attached_hostname(device_info_pass)
+ hostname = self.utils.get_volume_attached_hostname(attached_volume)
self.assertEqual('HostX', hostname)
- # Fail
- device_info_fail = self.data.volume_details_no_sg
- hostname = self.utils.get_volume_attached_hostname(device_info_fail)
- self.assertIsNone(hostname)
def test_validate_qos_input_exception(self):
qos_extra_spec = {'total_iops_sec': 90, 'DistributionType': 'Wrong',
@@ -620,3 +619,127 @@ class PowerMaxUtilsTest(test.TestCase):
sl_3, wl_3 = self.utils.get_service_level_workload(extra_specs)
self.assertEqual('Diamond', sl_3)
self.assertEqual('DSS', wl_3)
+
+ def test_get_rdf_managed_storage_group(self):
+ rdf_component_dict = ('OS-23_24_007-Asynchronous-rdf-sg',
+ {'prefix': 'OS',
+ 'rdf_label': '23_24_007',
+ 'sync_mode': 'Asynchronous',
+ 'after_mode': 'rdf-sg'})
+
+ async_rdf_details = (
+ self.utils.get_rdf_managed_storage_group(
+ self.data.volume_details_attached_async))
+ self.assertEqual(rdf_component_dict, async_rdf_details)
+
+ def test_get_storage_group_component_dict_no_slo(self):
+ """Test for get_storage_group_component_dict.
+
+ REST and no SLO.
+ """
+ sg_no_slo = 'OS-myhost-No_SLO-os-iscsi-pg'
+ component_dict = self.utils.get_storage_group_component_dict(
+ sg_no_slo)
+ self.assertEqual('myhost', component_dict['host'])
+ self.assertEqual('OS', component_dict['prefix'])
+ self.assertEqual('No_SLO', component_dict['no_slo'])
+ self.assertEqual('os-iscsi-pg', component_dict['portgroup'])
+ self.assertIsNone(component_dict['sloworkload'])
+ self.assertIsNone(component_dict['srp'])
+
+ def test_get_storage_group_component_dict_slo_workload_2(self):
+ """Test for get_storage_group_component_dict.
+
+ SLO, workload and test 2.
+ """
+ sg_slo_workload = 'OS-myhost-SRP_1-DiamodOLTP-os-iscsi-pg-RE'
+ component_dict = self.utils.get_storage_group_component_dict(
+ sg_slo_workload)
+ self.assertEqual('OS', component_dict['prefix'])
+ self.assertEqual('myhost', component_dict['host'])
+ self.assertEqual('SRP_1', component_dict['srp'])
+ self.assertEqual('os-iscsi-pg', component_dict['portgroup'])
+ self.assertEqual('DiamodOLTP', component_dict['sloworkload'])
+ self.assertIsNone(component_dict['no_slo'])
+
+ def test_get_storage_group_component_dict_compression_disabled(self):
+ """Test for get_storage_group_component_dict.
+
+ Compression disabled.
+ """
+ sg_compression_disabled = 'OS-myhost-SRP_1-DiamodNONE-os-iscsi-pg-CD'
+ component_dict = self.utils.get_storage_group_component_dict(
+ sg_compression_disabled)
+ self.assertEqual('OS', component_dict['prefix'])
+ self.assertEqual('myhost', component_dict['host'])
+ self.assertEqual('SRP_1', component_dict['srp'])
+ self.assertEqual('os-iscsi-pg', component_dict['portgroup'])
+ self.assertEqual('DiamodNONE', component_dict['sloworkload'])
+ self.assertEqual('-CD', component_dict['after_pg'])
+ self.assertIsNone(component_dict['no_slo'])
+
+ def test_get_storage_group_component_dict_replication_enabled(self):
+ """Test for get_storage_group_component_dict.
+
+ Replication enabled.
+ """
+ sg_slo_workload_rep = 'OS-myhost-SRP_1-DiamodOLTP-os-iscsi-pg-RE'
+ component_dict = self.utils.get_storage_group_component_dict(
+ sg_slo_workload_rep)
+ self.assertEqual('OS', component_dict['prefix'])
+ self.assertEqual('myhost', component_dict['host'])
+ self.assertEqual('SRP_1', component_dict['srp'])
+ self.assertEqual('os-iscsi-pg', component_dict['portgroup'])
+ self.assertEqual('DiamodOLTP', component_dict['sloworkload'])
+ self.assertEqual('-RE', component_dict['after_pg'])
+ self.assertIsNone(component_dict['no_slo'])
+
+ def test_get_storage_group_component_dict_slo_no_workload(self):
+ """Test for get_storage_group_component_dict.
+
+ SLO and no workload.
+ """
+ sg_slo_no_workload = 'OS-myhost-SRP_1-DiamodNONE-os-iscsi-pg'
+ component_dict = self.utils.get_storage_group_component_dict(
+ sg_slo_no_workload)
+ self.assertEqual('OS', component_dict['prefix'])
+ self.assertEqual('myhost', component_dict['host'])
+ self.assertEqual('SRP_1', component_dict['srp'])
+ self.assertEqual('os-iscsi-pg', component_dict['portgroup'])
+ self.assertEqual('DiamodNONE', component_dict['sloworkload'])
+ self.assertIsNone(component_dict['no_slo'])
+
+ def test_get_storage_group_component_dict_dashes(self):
+ """Test for get_storage_group_component_dict, dashes."""
+ sg_host_with_dashes = (
+ 'OS-host-with-dashes-SRP_1-DiamodOLTP-myportgroup-RE')
+ component_dict = self.utils.get_storage_group_component_dict(
+ sg_host_with_dashes)
+ self.assertEqual('host-with-dashes', component_dict['host'])
+ self.assertEqual('OS', component_dict['prefix'])
+ self.assertEqual('SRP_1', component_dict['srp'])
+ self.assertEqual('DiamodOLTP', component_dict['sloworkload'])
+ self.assertEqual('myportgroup', component_dict['portgroup'])
+ self.assertEqual('-RE', component_dict['after_pg'])
+
+ def test_delete_values_from_dict(self):
+ """Test delete_values_from_dict"""
+ delete_list = ['rdf_group_no', 'rep_mode', 'target_array_model',
+ 'service_level', 'remote_array', 'target_device_id',
+ 'replication_status', 'rdf_group_label']
+ data_dict = self.utils.delete_values_from_dict(
+ self.data.retype_metadata_dict, delete_list)
+ self.assertEqual({'device_id': self.data.device_id}, data_dict)
+
+ def test_update_values_in_dict(self):
+ """Test delete_values_from_dict"""
+ update_list = [('default_sg_name', 'source_sg_name'),
+ ('service_level', 'source_service_level')]
+
+ update_dict = {'default_sg_name': 'default-sg',
+ 'service_level': 'Diamond'}
+ ret_dict = {'source_sg_name': 'default-sg',
+ 'source_service_level': 'Diamond'}
+ data_dict = self.utils.update_values_in_dict(
+ update_dict, update_list)
+ self.assertEqual(ret_dict, data_dict)
diff --git a/cinder/volume/drivers/dell_emc/powermax/common.py b/cinder/volume/drivers/dell_emc/powermax/common.py
index 88f860360..4f1383f56 100644
--- a/cinder/volume/drivers/dell_emc/powermax/common.py
+++ b/cinder/volume/drivers/dell_emc/powermax/common.py
@@ -2235,7 +2235,7 @@ class PowerMaxCommon(object):
create_snap = True
self.provision.create_volume_replica(
array, source_device_id, target_device_id,
- snap_name, extra_specs, create_snap)
+ snap_name, extra_specs, create_snap, copy_mode=True)
except Exception as e:
if target_device_id:
LOG.warning("Create replica failed. Cleaning up the target "
@@ -2396,7 +2396,7 @@ class PowerMaxCommon(object):
array, device_id, tgt_only=True)
source_device_id = tgt_session['source_vol_id']
LOG.debug("Target %(tgt)s source device %(src)s",
- {'target': device_id, 'src': source_device_id})
+ {'tgt': device_id, 'src': source_device_id})
return source_device_id
@@ -3197,9 +3197,15 @@ class PowerMaxCommon(object):
# Scenario: Rep was not enabled, target VT has rep enabled, need to
# enable replication
elif not was_rep_enabled and is_rep_enabled:
- metro_bias = utils.METROBIAS
- if metro_bias in self.rep_config:
- extra_specs[metro_bias] = self.rep_config[metro_bias]
+ if self.rep_config['mode'] is utils.REP_METRO or (
+ self.rep_config['mode'] is utils.REP_ASYNC):
+ LOG.warning("Volume %(device_id)s cannot be retyped to "
+ "%(mode)s in an attached state. Please "
+ "detach first or use On Demand Migration "
+ "Policy to run host assisted migration.",
+ {'device_id': device_id,
+ 'mode': self.rep_config['mode']})
+ return False, model_update
rep_status, rep_driver_data, rep_info_dict = (
self.setup_inuse_volume_replication(
array, volume, device_id, extra_specs))
@@ -3213,21 +3219,6 @@ 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:
@@ -3362,12 +3353,17 @@ class PowerMaxCommon(object):
"""
success = False
device_info = self.rest.get_volume(array, device_id)
- source_sg_name = device_info['storageGroupId'][0]
+ if len(device_info.get('storageGroupId')) > 1:
+ LOG.warning('Device id %(dev)s is in more than 1 storage group.',
+ {'dev': device_id})
+ # Get the source group
+ source_sg_name, __ = self.utils.get_production_storage_group(
+ device_info)
source_sg = self.rest.get_storage_group(array, source_sg_name)
target_extra_specs[utils.PORTGROUPNAME] = extra_specs[
utils.PORTGROUPNAME]
- attached_host = self.utils.get_volume_attached_hostname(device_info)
+ attached_host = self.utils.get_volume_attached_hostname(volume)
if not attached_host:
LOG.error(
"There was an issue retrieving attached host from volume "
@@ -3757,7 +3753,7 @@ class PowerMaxCommon(object):
local_vol_state, pair_state) = (
self.get_remote_target_device(array, volume, device_id))
- if target_device is not None:
+ if target_device:
# Clean-up target
self._cleanup_remote_target(
array, volume, remote_array, device_id, target_device,
@@ -3765,22 +3761,24 @@ class PowerMaxCommon(object):
LOG.info('Successfully destroyed replication for '
'volume: %(volume)s',
{'volume': volume_name})
+
+ # Remove the source volume from the rdf management storage
+ # group if it exists there
+ device_info = self.rest.get_volume(array, device_id)
+ rdf_sg, __ = self.utils.get_rdf_managed_storage_group(
+ device_info)
+ if rdf_sg:
+ self.rest.remove_vol_from_sg(
+ array, rdf_sg, device_id, extra_specs)
+ LOG.info('Removed device %(dev)s from storage '
+ 'group %(sg)s.',
+ {'dev': device_id,
+ 'sg': rdf_sg})
else:
LOG.warning('Replication target not found for '
'replication-enabled volume: %(volume)s',
{'volume': volume_name})
except Exception as e:
- if extra_specs.get(utils.REP_MODE, None) in [
- utils.REP_ASYNC, utils.REP_METRO]:
- (target_device, remote_array, rdf_group_no,
- local_vol_state, pair_state) = (
- self.get_remote_target_device(
- extra_specs[utils.ARRAY], volume, device_id))
- if target_device is not None:
- # Return devices to their async rdf management groups
- self._add_volume_to_async_rdf_managed_grp(
- extra_specs[utils.ARRAY], device_id, volume_name,
- remote_array, target_device, extra_specs)
exception_message = (
_('Cannot get necessary information to cleanup '
'replication target for volume: %(volume)s. '
@@ -4095,15 +4093,22 @@ class PowerMaxCommon(object):
replication_keybindings = ast.literal_eval(rep_target_data)
remote_array = replication_keybindings['array']
remote_device = replication_keybindings['device_id']
- target_device_info = self.rest.get_volume(
- remote_array, remote_device)
- if target_device_info is not None:
+ try:
+ target_device_info = self.rest.get_volume(
+ remote_array, remote_device)
+ except exception.VolumeBackendAPIException:
+ target_device_info = None
+ if target_device_info:
target_device = remote_device
are_vols_paired, local_vol_state, pair_state = (
self.rest.are_vols_rdf_paired(
array, remote_array, device_id, target_device))
if not are_vols_paired:
target_device = None
+ else:
+ LOG.warning('Unable to find device %s(dev)s on remote '
+ 'array %(array)s',
+ {'dev': remote_device, 'array': remote_array})
except (KeyError, ValueError):
target_device = None
return (target_device, remote_array, rdf_group,
diff --git a/cinder/volume/drivers/dell_emc/powermax/fc.py b/cinder/volume/drivers/dell_emc/powermax/fc.py
index 845a49efb..4ed39590d 100644
--- a/cinder/volume/drivers/dell_emc/powermax/fc.py
+++ b/cinder/volume/drivers/dell_emc/powermax/fc.py
@@ -123,9 +123,10 @@ class PowerMaxFCDriver(san.SanDriver, driver.FibreChannelDriver):
4.1.5 - Allowing for default volume type in group (#1866871)
4.1.6 - Pools bug fix allowing 'None' variants (bug #1873253)
4.1.7 - Fix to enable legacy volumes to live migrate (#1867163)
+ 4.1.8 - Block retype to some in-use replicated modes (#1899137)
"""
- VERSION = "4.1.7"
+ VERSION = "4.1.8"
# ThirdPartySystems wiki
CI_WIKI_NAME = "EMC_VMAX_CI"
diff --git a/cinder/volume/drivers/dell_emc/powermax/iscsi.py b/cinder/volume/drivers/dell_emc/powermax/iscsi.py
index a5e338934..3e8cb11eb 100644
--- a/cinder/volume/drivers/dell_emc/powermax/iscsi.py
+++ b/cinder/volume/drivers/dell_emc/powermax/iscsi.py
@@ -128,9 +128,10 @@ class PowerMaxISCSIDriver(san.SanISCSIDriver):
4.1.5 - Allowing for default volume type in group (#1866871)
4.1.6 - Pools bug fix allowing 'None' variants (bug #1873253)
4.1.7 - Fix to enable legacy volumes to live migrate (#1867163)
+ 4.1.8 - Block retype to some in-use replicated modes (#1899137)
"""
- VERSION = "4.1.7"
+ VERSION = "4.1.8"
# ThirdPartySystems wiki
CI_WIKI_NAME = "EMC_VMAX_CI"
diff --git a/cinder/volume/drivers/dell_emc/powermax/metadata.py b/cinder/volume/drivers/dell_emc/powermax/metadata.py
index 883521139..7a61f3d8b 100644
--- a/cinder/volume/drivers/dell_emc/powermax/metadata.py
+++ b/cinder/volume/drivers/dell_emc/powermax/metadata.py
@@ -642,6 +642,15 @@ class PowerMaxVolumeMetadata(object):
is_rep_enabled=('yes' if is_rep_enabled else 'no'),
rep_mode=rep_mode, is_compression_disabled=(
True if is_compression_disabled else False))
+ if not is_rep_enabled:
+ delete_list = ['rdf_group_no', 'rep_mode', 'target_array_model',
+ 'service_level', 'remote_array', 'target_device_id',
+ 'replication_status', 'rdf_group_label']
+ self.utils.delete_values_from_dict(datadict, delete_list)
+ update_list = [('default_sg_name', 'source_sg_name'),
+ ('service_level', 'source_service_level')]
+ self.utils.update_values_in_dict(datadict, update_list)
+
volume_metadata = self.update_volume_info_metadata(
datadict, self.version_dict)
self.print_pretty_table(volume_metadata)
diff --git a/cinder/volume/drivers/dell_emc/powermax/provision.py b/cinder/volume/drivers/dell_emc/powermax/provision.py
index adba0bbd1..2bc85d3ba 100644
--- a/cinder/volume/drivers/dell_emc/powermax/provision.py
+++ b/cinder/volume/drivers/dell_emc/powermax/provision.py
@@ -145,7 +145,7 @@ class PowerMaxProvision(object):
def create_volume_replica(
self, array, source_device_id, target_device_id,
- snap_name, extra_specs, create_snap=False):
+ snap_name, extra_specs, create_snap=False, copy_mode=False):
"""Create a snap vx of a source and copy to a target.
:param array: the array serial number
@@ -166,7 +166,7 @@ class PowerMaxProvision(object):
def do_modify_volume_snap(src_device_id):
self.rest.modify_volume_snap(
array, src_device_id, target_device_id, snap_name,
- extra_specs, link=True)
+ extra_specs, link=True, copy_mode=copy_mode)
do_modify_volume_snap(source_device_id)
diff --git a/cinder/volume/drivers/dell_emc/powermax/rest.py b/cinder/volume/drivers/dell_emc/powermax/rest.py
index e61ce45f9..e2b50238a 100644
--- a/cinder/volume/drivers/dell_emc/powermax/rest.py
+++ b/cinder/volume/drivers/dell_emc/powermax/rest.py
@@ -1780,7 +1780,8 @@ class PowerMaxRest(object):
def modify_volume_snap(self, array, source_id, target_id, snap_name,
extra_specs, link=False, unlink=False,
rename=False, new_snap_name=None, restore=False,
- list_volume_pairs=None, generation=0):
+ list_volume_pairs=None, generation=0,
+ copy_mode=False):
"""Modify a snapvx snapshot
:param array: the array serial number
@@ -1806,6 +1807,8 @@ class PowerMaxRest(object):
elif restore:
action = "Restore"
+ copy = 'true' if copy_mode else 'false'
+
payload = {}
if action == "Restore":
operation = 'Restore snapVx snapshot'
@@ -1825,7 +1828,7 @@ class PowerMaxRest(object):
tgt_list.append({'name': target_id})
payload = {"deviceNameListSource": src_list,
"deviceNameListTarget": tgt_list,
- "copy": 'false', "action": action,
+ "copy": copy, "action": action,
"star": 'false', "force": 'false',
"exact": 'false', "remote": 'false',
"symforce": 'false', "generation": generation}
diff --git a/cinder/volume/drivers/dell_emc/powermax/utils.py b/cinder/volume/drivers/dell_emc/powermax/utils.py
index dbb9ba985..de8c690a2 100644
--- a/cinder/volume/drivers/dell_emc/powermax/utils.py
+++ b/cinder/volume/drivers/dell_emc/powermax/utils.py
@@ -909,18 +909,66 @@ class PowerMaxUtils(object):
return True
- @staticmethod
- def get_volume_attached_hostname(device_info):
- """Parse a hostname from a storage group ID.
+ def get_volume_attached_hostname(self, volume):
+ """Get the host name from the attached volume
+
+ :param volume: the volume object
+ :returns: str -- the attached hostname
+ """
+ host_name_set = set()
+ attachment_list = volume.volume_attachment
+ LOG.debug("Volume attachment list: %(atl)s. "
+ "Attachment type: %(at)s",
+ {'atl': attachment_list, 'at': type(attachment_list)})
+
+ try:
+ att_list = attachment_list.objects
+ except AttributeError:
+ att_list = attachment_list
+ for att in att_list:
+ host_name_set.add(att.attached_host)
+
+ if host_name_set:
+ if len(host_name_set) > 1:
+ LOG.warning("Volume is attached to multiple instances "
+ "on more than one compute node.")
+ else:
+ return host_name_set.pop()
+ return None
+
+ def get_rdf_managed_storage_group(self, device_info):
+ """Get the RDF managed storage group
:param device_info: the device info dict
- :return: str -- the attached hostname
+ :returns: str -- the attached hostname
+ dict -- storage group details
"""
try:
- sg_id = device_info.get("storageGroupId")[0]
- return sg_id.split('-')[1]
+ sg_list = device_info.get("storageGroupId")
+ for sg_id in sg_list:
+ sg_details = self.get_rdf_group_component_dict(sg_id)
+ if sg_details:
+ return sg_id, sg_details
except IndexError:
- return None
+ return None, None
+ return None, None
+
+ def get_production_storage_group(self, device_info):
+ """Get the production storage group
+
+ :param device_info: the device info dict
+ :return: str -- the storage group id
+ dict -- storage group details
+ """
+ try:
+ sg_list = device_info.get("storageGroupId")
+ for sg_id in sg_list:
+ sg_details = self.get_storage_group_component_dict(sg_id)
+ if sg_details:
+ return sg_id, sg_details
+ except IndexError:
+ return None, None
+ return None, None
@staticmethod
def validate_qos_input(input_key, sg_value, qos_extra_spec, property_dict):
@@ -1002,6 +1050,31 @@ class PowerMaxUtils(object):
workload = extra_specs.get(WORKLOAD)
return service_level, workload
+ def get_storage_group_component_dict(self, storage_group_name):
+ """Parse the storage group string.
+
+ :param storage_group_name: the storage group name -- str
+ :returns: object components -- dict
+ """
+ regex_str = (r'^(?P<prefix>OS)-(?P<host>.+?)'
+ r'((?P<no_slo>No_SLO)|((?P<srp>SRP.+?)-'
+ r'(?P<sloworkload>.+?)))-(?P<portgroup>.+?)'
+ r'(?P<after_pg>$|-CD|-RE|-RA|-RM)')
+ return self.get_object_components_and_correct_host(
+ regex_str, storage_group_name)
+
+ def get_rdf_group_component_dict(self, storage_group_name):
+ """Parse the storage group string.
+
+ :param storage_group_name: the storage group name -- str
+ :returns: object components -- dict
+ """
+ regex_str = (r'^(?P<prefix>OS)-(?P<rdf_label>.+?)-'
+ r'(?P<sync_mode>Asynchronous|Metro)-'
+ r'(?P<after_mode>rdf-sg)$')
+ return self.get_object_components(
+ regex_str, storage_group_name)
+
def get_object_components_and_correct_host(self, regex_str, input_str):
"""Get components from input string.
@@ -1026,3 +1099,42 @@ class PowerMaxUtils(object):
full_str = re.compile(regex_str)
match = full_str.match(input_str)
return match.groupdict() if match else None
+
+ def get_possible_initiator_name(self, host_label, protocol):
+ """Get possible initiator name based on the host
+
+ :param regex_str: the regex -- str
+ :param input_str: the input string -- str
+ :returns: dict
+ """
+ protocol = self.get_short_protocol_type(protocol)
+ return ("OS-%(shortHostName)s-%(protocol)s-IG"
+ % {'shortHostName': host_label,
+ 'protocol': protocol})
+
+ @staticmethod
+ def delete_values_from_dict(datadict, key_list):
+ """Delete values from a dict
+
+ :param datadict: dictionary
+ :param key_list: list of keys
+ :returns: dict
+ """
+ for key in key_list:
+ if datadict.get(key):
+ del datadict[key]
+ return datadict
+
+ @staticmethod
+ def update_values_in_dict(datadict, tuple_list):
+ """Delete values from a dict
+
+ :param datadict: dictionary
+ :param tuple_list: list of tuples
+ :returns: dict
+ """
+ for tuple in tuple_list:
+ if datadict.get(tuple[0]):
+ datadict.update({tuple[1]: datadict.get(tuple[0])})
+ del datadict[tuple[0]]
+ return datadict
diff --git a/releasenotes/notes/powermax-disable-inuse-metro-89e9f398ec9e2672.yaml b/releasenotes/notes/powermax-disable-inuse-metro-89e9f398ec9e2672.yaml
new file mode 100644
index 000000000..2bc932e5b
--- /dev/null
+++ b/releasenotes/notes/powermax-disable-inuse-metro-89e9f398ec9e2672.yaml
@@ -0,0 +1,6 @@
+---
+issues:
+ - |
+ PowerMax driver - Disabling inuse storage assisted migration to a metro
+ or asynchronous replicated volume type as this operation will
+ not facilitate FC scanning or iSCSI login of the target array.