Merge "PowerMax Driver - Safeguarding retype to some in-use replicated modes" into stable/train
This commit is contained in:
commit
0a7a3a3779
|
@ -248,7 +248,7 @@ class PowerMaxData(object):
|
||||||
|
|
||||||
test_volume_attachment = volume_attachment.VolumeAttachment(
|
test_volume_attachment = volume_attachment.VolumeAttachment(
|
||||||
id='2b06255d-f5f0-4520-a953-b029196add6b', volume_id=test_volume.id,
|
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',
|
location_info = {'location_info': '000197800123#SRP_1#Diamond#DSS',
|
||||||
'storage_protocol': 'FC'}
|
'storage_protocol': 'FC'}
|
||||||
|
@ -632,6 +632,17 @@ class PowerMaxData(object):
|
||||||
'snapvx_source': 'false',
|
'snapvx_source': 'false',
|
||||||
'storageGroupId': []}
|
'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 = [
|
volume_list = [
|
||||||
{'id': '6b70de13-98c5-46b2-8f24-e4e96a8988fa',
|
{'id': '6b70de13-98c5-46b2-8f24-e4e96a8988fa',
|
||||||
'count': 2,
|
'count': 2,
|
||||||
|
@ -1229,3 +1240,18 @@ class PowerMaxData(object):
|
||||||
legacy_mvs = [legacy_mv1, legacy_mv2]
|
legacy_mvs = [legacy_mv1, legacy_mv2]
|
||||||
legacy_not_shared_mv = 'OS-myhostA-SRP_1-Diamond-NONE-MV'
|
legacy_not_shared_mv = 'OS-myhostA-SRP_1-Diamond-NONE-MV'
|
||||||
legacy_not_shared_sg = 'OS-myhostA-SRP_1-Diamond-NONE-SG'
|
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'
|
||||||
|
}
|
||||||
|
|
|
@ -1415,6 +1415,8 @@ class PowerMaxCommonTest(test.TestCase):
|
||||||
new_type, host)
|
new_type, host)
|
||||||
mock_retype.assert_called_once()
|
mock_retype.assert_called_once()
|
||||||
|
|
||||||
|
@mock.patch.object(utils.PowerMaxUtils, 'get_volume_attached_hostname',
|
||||||
|
return_value='HostX')
|
||||||
@mock.patch.object(
|
@mock.patch.object(
|
||||||
rest.PowerMaxRest, 'get_volume',
|
rest.PowerMaxRest, 'get_volume',
|
||||||
return_value=tpd.PowerMaxData.volume_details_attached)
|
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,
|
def test_retype_inuse_volume_tgt_sg_exist(self, mck_vol_in_sg, mck_sg_move,
|
||||||
mck_child_sg_in_sg,
|
mck_child_sg_in_sg,
|
||||||
mck_get_sg_name,
|
mck_get_sg_name,
|
||||||
mck_get_sg, mck_get_vol):
|
mck_get_sg, mck_get_vol,
|
||||||
|
mock_host):
|
||||||
array = self.data.array
|
array = self.data.array
|
||||||
srp = self.data.srp
|
srp = self.data.srp
|
||||||
slo = self.data.slo
|
slo = self.data.slo
|
||||||
workload = self.data.workload
|
workload = self.data.workload
|
||||||
device_id = self.data.device_id
|
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'
|
rep_mode = 'Synchronous'
|
||||||
src_extra_specs = self.data.extra_specs_migrate
|
src_extra_specs = self.data.extra_specs_migrate
|
||||||
interval = src_extra_specs['interval']
|
interval = src_extra_specs['interval']
|
||||||
|
@ -1447,12 +1452,14 @@ class PowerMaxCommonTest(test.TestCase):
|
||||||
'interval': interval, 'retries': retries, 'rep_mode': rep_mode}
|
'interval': interval, 'retries': retries, 'rep_mode': rep_mode}
|
||||||
|
|
||||||
success = self.common._retype_inuse_volume(
|
success = self.common._retype_inuse_volume(
|
||||||
array, srp, volume, device_id, src_extra_specs, slo, workload,
|
array, srp, attached_volume, device_id, src_extra_specs, slo,
|
||||||
tgt_extra_specs, False)[0]
|
workload, tgt_extra_specs, False)[0]
|
||||||
self.assertTrue(success)
|
self.assertTrue(success)
|
||||||
mck_sg_move.assert_called()
|
mck_sg_move.assert_called()
|
||||||
mck_vol_in_sg.assert_called()
|
mck_vol_in_sg.assert_called()
|
||||||
|
|
||||||
|
@mock.patch.object(utils.PowerMaxUtils, 'get_volume_attached_hostname',
|
||||||
|
return_value='HostX')
|
||||||
@mock.patch.object(
|
@mock.patch.object(
|
||||||
rest.PowerMaxRest, 'get_volume',
|
rest.PowerMaxRest, 'get_volume',
|
||||||
return_value=tpd.PowerMaxData.volume_details_attached)
|
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,
|
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_sg_in_sg, mck_add_sg_to_sg,
|
||||||
mck_create_sg, mck_get_csg_name,
|
mck_create_sg, mck_get_csg_name,
|
||||||
mck_get_vol):
|
mck_get_vol, mock_host):
|
||||||
array = self.data.array
|
array = self.data.array
|
||||||
srp = self.data.srp
|
srp = self.data.srp
|
||||||
slo = self.data.slo
|
slo = self.data.slo
|
||||||
|
@ -1746,17 +1753,8 @@ class PowerMaxCommonTest(test.TestCase):
|
||||||
mck_setup_specs = src_extra_specs
|
mck_setup_specs = src_extra_specs
|
||||||
mck_setup_specs[utils.METROBIAS] = self.common.rep_config[
|
mck_setup_specs[utils.METROBIAS] = self.common.rep_config[
|
||||||
'metro_use_bias']
|
'metro_use_bias']
|
||||||
mck_setup.assert_called_once_with(
|
mck_setup.assert_not_called()
|
||||||
self.data.array, volume, device_id, mck_setup_specs)
|
self.assertFalse(success)
|
||||||
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)
|
|
||||||
_reset_mocks()
|
_reset_mocks()
|
||||||
|
|
||||||
# Scenario 4: rep => rep
|
# Scenario 4: rep => rep
|
||||||
|
|
|
@ -118,7 +118,7 @@ class PowerMaxProvisionTest(test.TestCase):
|
||||||
snap_name, extra_specs, create_snap=True)
|
snap_name, extra_specs, create_snap=True)
|
||||||
mock_modify.assert_called_once_with(
|
mock_modify.assert_called_once_with(
|
||||||
array, source_device_id, target_device_id, snap_name,
|
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(
|
mock_create_snapvx.assert_called_once_with(
|
||||||
array, source_device_id, snap_name, extra_specs, ttl=ttl)
|
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.rest, 'modify_volume_snap') as mock_modify:
|
||||||
self.provision.create_volume_replica(
|
self.provision.create_volume_replica(
|
||||||
array, source_device_id, target_device_id,
|
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(
|
mock_modify.assert_called_once_with(
|
||||||
array, source_device_id, target_device_id, snap_name,
|
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()
|
mock_create_snapvx.assert_not_called()
|
||||||
|
|
||||||
def test_break_replication_relationship(self):
|
def test_break_replication_relationship(self):
|
||||||
|
|
|
@ -503,24 +503,24 @@ class PowerMaxReplicationTest(test.TestCase):
|
||||||
self.extra_specs)
|
self.extra_specs)
|
||||||
mock_clean.assert_not_called()
|
mock_clean.assert_not_called()
|
||||||
|
|
||||||
@mock.patch.object(
|
@mock.patch.object(rest.PowerMaxRest, 'get_array_model_info',
|
||||||
common.PowerMaxCommon, 'get_remote_target_device',
|
return_value=('VMAX250F', False))
|
||||||
return_value=(tpd.PowerMaxData.device_id2, '', '', '', ''))
|
@mock.patch.object(common.PowerMaxCommon, '_cleanup_remote_target')
|
||||||
@mock.patch.object(common.PowerMaxCommon,
|
@mock.patch.object(utils.PowerMaxUtils, 'get_rdf_managed_storage_group',
|
||||||
'_add_volume_to_async_rdf_managed_grp')
|
return_value=(
|
||||||
def test_cleanup_lun_replication_exception(self, mock_add, mock_tgt):
|
tpd.PowerMaxData.rdf_managed_async_grp, {}))
|
||||||
self.assertRaises(exception.VolumeBackendAPIException,
|
@mock.patch.object(rest.PowerMaxRest, 'remove_vol_from_sg')
|
||||||
self.common.cleanup_lun_replication,
|
def test_cleanup_lun_replication_async(
|
||||||
self.data.test_volume, '1', self.data.device_id,
|
self, mock_rm_sg, mock_get_rdf_sg, mock_clean, mock_model):
|
||||||
self.extra_specs)
|
rep_extra_specs = deepcopy(self.data.rep_extra_specs)
|
||||||
# is metro or async volume
|
rep_extra_specs[utils.PORTGROUPNAME] = self.data.port_group_name_f
|
||||||
extra_specs = deepcopy(self.extra_specs)
|
rep_extra_specs['target_array_model'] = 'VMAX250F'
|
||||||
extra_specs[utils.REP_MODE] = utils.REP_METRO
|
self.common.cleanup_lun_replication(
|
||||||
self.assertRaises(exception.VolumeBackendAPIException,
|
self.data.test_volume, '1', self.data.device_id,
|
||||||
self.common.cleanup_lun_replication,
|
self.extra_specs)
|
||||||
self.data.test_volume, '1', self.data.device_id,
|
mock_rm_sg.assert_called_once_with(
|
||||||
extra_specs)
|
self.data.array, self.data.rdf_managed_async_grp,
|
||||||
mock_add.assert_called_once()
|
self.data.device_id, self.extra_specs)
|
||||||
|
|
||||||
@mock.patch.object(common.PowerMaxCommon, '_cleanup_metro_target')
|
@mock.patch.object(common.PowerMaxCommon, '_cleanup_metro_target')
|
||||||
@mock.patch.object(masking.PowerMaxMasking,
|
@mock.patch.object(masking.PowerMaxMasking,
|
||||||
|
@ -652,6 +652,21 @@ class PowerMaxReplicationTest(test.TestCase):
|
||||||
self.data.device_id))
|
self.data.device_id))
|
||||||
self.assertIsNone(target_device4)
|
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',
|
@mock.patch.object(rest.PowerMaxRest, 'get_array_model_info',
|
||||||
return_value=('VMAX250F', False))
|
return_value=('VMAX250F', False))
|
||||||
@mock.patch.object(common.PowerMaxCommon,
|
@mock.patch.object(common.PowerMaxCommon,
|
||||||
|
|
|
@ -509,14 +509,13 @@ class PowerMaxUtilsTest(test.TestCase):
|
||||||
self.utils.is_snapshot_manageable(volume))
|
self.utils.is_snapshot_manageable(volume))
|
||||||
|
|
||||||
def test_get_volume_attached_hostname(self):
|
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
|
# Success
|
||||||
hostname = self.utils.get_volume_attached_hostname(device_info_pass)
|
hostname = self.utils.get_volume_attached_hostname(attached_volume)
|
||||||
self.assertEqual('HostX', hostname)
|
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):
|
def test_validate_qos_input_exception(self):
|
||||||
qos_extra_spec = {'total_iops_sec': 90, 'DistributionType': 'Wrong',
|
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)
|
sl_3, wl_3 = self.utils.get_service_level_workload(extra_specs)
|
||||||
self.assertEqual('Diamond', sl_3)
|
self.assertEqual('Diamond', sl_3)
|
||||||
self.assertEqual('DSS', wl_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)
|
||||||
|
|
|
@ -2235,7 +2235,7 @@ class PowerMaxCommon(object):
|
||||||
create_snap = True
|
create_snap = True
|
||||||
self.provision.create_volume_replica(
|
self.provision.create_volume_replica(
|
||||||
array, source_device_id, target_device_id,
|
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:
|
except Exception as e:
|
||||||
if target_device_id:
|
if target_device_id:
|
||||||
LOG.warning("Create replica failed. Cleaning up the target "
|
LOG.warning("Create replica failed. Cleaning up the target "
|
||||||
|
@ -2396,7 +2396,7 @@ class PowerMaxCommon(object):
|
||||||
array, device_id, tgt_only=True)
|
array, device_id, tgt_only=True)
|
||||||
source_device_id = tgt_session['source_vol_id']
|
source_device_id = tgt_session['source_vol_id']
|
||||||
LOG.debug("Target %(tgt)s source device %(src)s",
|
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
|
return source_device_id
|
||||||
|
|
||||||
|
@ -3197,9 +3197,15 @@ class PowerMaxCommon(object):
|
||||||
# Scenario: Rep was not enabled, target VT has rep enabled, need to
|
# Scenario: Rep was not enabled, target VT has rep enabled, need to
|
||||||
# enable replication
|
# enable replication
|
||||||
elif not was_rep_enabled and is_rep_enabled:
|
elif not was_rep_enabled and is_rep_enabled:
|
||||||
metro_bias = utils.METROBIAS
|
if self.rep_config['mode'] is utils.REP_METRO or (
|
||||||
if metro_bias in self.rep_config:
|
self.rep_config['mode'] is utils.REP_ASYNC):
|
||||||
extra_specs[metro_bias] = self.rep_config[metro_bias]
|
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 = (
|
rep_status, rep_driver_data, rep_info_dict = (
|
||||||
self.setup_inuse_volume_replication(
|
self.setup_inuse_volume_replication(
|
||||||
array, volume, device_id, extra_specs))
|
array, volume, device_id, extra_specs))
|
||||||
|
@ -3213,21 +3219,6 @@ class PowerMaxCommon(object):
|
||||||
target_slo, target_workload, target_extra_specs,
|
target_slo, target_workload, target_extra_specs,
|
||||||
is_compression_disabled)
|
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
|
# If the volume was replication enabled both before and after
|
||||||
# retype, the volume needs to be retyped on the remote array also
|
# retype, the volume needs to be retyped on the remote array also
|
||||||
if was_rep_enabled and is_rep_enabled:
|
if was_rep_enabled and is_rep_enabled:
|
||||||
|
@ -3362,12 +3353,17 @@ class PowerMaxCommon(object):
|
||||||
"""
|
"""
|
||||||
success = False
|
success = False
|
||||||
device_info = self.rest.get_volume(array, device_id)
|
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)
|
source_sg = self.rest.get_storage_group(array, source_sg_name)
|
||||||
target_extra_specs[utils.PORTGROUPNAME] = extra_specs[
|
target_extra_specs[utils.PORTGROUPNAME] = extra_specs[
|
||||||
utils.PORTGROUPNAME]
|
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:
|
if not attached_host:
|
||||||
LOG.error(
|
LOG.error(
|
||||||
"There was an issue retrieving attached host from volume "
|
"There was an issue retrieving attached host from volume "
|
||||||
|
@ -3757,7 +3753,7 @@ class PowerMaxCommon(object):
|
||||||
local_vol_state, pair_state) = (
|
local_vol_state, pair_state) = (
|
||||||
self.get_remote_target_device(array, volume, device_id))
|
self.get_remote_target_device(array, volume, device_id))
|
||||||
|
|
||||||
if target_device is not None:
|
if target_device:
|
||||||
# Clean-up target
|
# Clean-up target
|
||||||
self._cleanup_remote_target(
|
self._cleanup_remote_target(
|
||||||
array, volume, remote_array, device_id, target_device,
|
array, volume, remote_array, device_id, target_device,
|
||||||
|
@ -3765,22 +3761,24 @@ class PowerMaxCommon(object):
|
||||||
LOG.info('Successfully destroyed replication for '
|
LOG.info('Successfully destroyed replication for '
|
||||||
'volume: %(volume)s',
|
'volume: %(volume)s',
|
||||||
{'volume': volume_name})
|
{'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:
|
else:
|
||||||
LOG.warning('Replication target not found for '
|
LOG.warning('Replication target not found for '
|
||||||
'replication-enabled volume: %(volume)s',
|
'replication-enabled volume: %(volume)s',
|
||||||
{'volume': volume_name})
|
{'volume': volume_name})
|
||||||
except Exception as e:
|
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 = (
|
exception_message = (
|
||||||
_('Cannot get necessary information to cleanup '
|
_('Cannot get necessary information to cleanup '
|
||||||
'replication target for volume: %(volume)s. '
|
'replication target for volume: %(volume)s. '
|
||||||
|
@ -4095,15 +4093,22 @@ class PowerMaxCommon(object):
|
||||||
replication_keybindings = ast.literal_eval(rep_target_data)
|
replication_keybindings = ast.literal_eval(rep_target_data)
|
||||||
remote_array = replication_keybindings['array']
|
remote_array = replication_keybindings['array']
|
||||||
remote_device = replication_keybindings['device_id']
|
remote_device = replication_keybindings['device_id']
|
||||||
target_device_info = self.rest.get_volume(
|
try:
|
||||||
remote_array, remote_device)
|
target_device_info = self.rest.get_volume(
|
||||||
if target_device_info is not None:
|
remote_array, remote_device)
|
||||||
|
except exception.VolumeBackendAPIException:
|
||||||
|
target_device_info = None
|
||||||
|
if target_device_info:
|
||||||
target_device = remote_device
|
target_device = remote_device
|
||||||
are_vols_paired, local_vol_state, pair_state = (
|
are_vols_paired, local_vol_state, pair_state = (
|
||||||
self.rest.are_vols_rdf_paired(
|
self.rest.are_vols_rdf_paired(
|
||||||
array, remote_array, device_id, target_device))
|
array, remote_array, device_id, target_device))
|
||||||
if not are_vols_paired:
|
if not are_vols_paired:
|
||||||
target_device = None
|
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):
|
except (KeyError, ValueError):
|
||||||
target_device = None
|
target_device = None
|
||||||
return (target_device, remote_array, rdf_group,
|
return (target_device, remote_array, rdf_group,
|
||||||
|
|
|
@ -123,9 +123,10 @@ class PowerMaxFCDriver(san.SanDriver, driver.FibreChannelDriver):
|
||||||
4.1.5 - Allowing for default volume type in group (#1866871)
|
4.1.5 - Allowing for default volume type in group (#1866871)
|
||||||
4.1.6 - Pools bug fix allowing 'None' variants (bug #1873253)
|
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.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
|
# ThirdPartySystems wiki
|
||||||
CI_WIKI_NAME = "EMC_VMAX_CI"
|
CI_WIKI_NAME = "EMC_VMAX_CI"
|
||||||
|
|
|
@ -128,9 +128,10 @@ class PowerMaxISCSIDriver(san.SanISCSIDriver):
|
||||||
4.1.5 - Allowing for default volume type in group (#1866871)
|
4.1.5 - Allowing for default volume type in group (#1866871)
|
||||||
4.1.6 - Pools bug fix allowing 'None' variants (bug #1873253)
|
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.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
|
# ThirdPartySystems wiki
|
||||||
CI_WIKI_NAME = "EMC_VMAX_CI"
|
CI_WIKI_NAME = "EMC_VMAX_CI"
|
||||||
|
|
|
@ -642,6 +642,15 @@ class PowerMaxVolumeMetadata(object):
|
||||||
is_rep_enabled=('yes' if is_rep_enabled else 'no'),
|
is_rep_enabled=('yes' if is_rep_enabled else 'no'),
|
||||||
rep_mode=rep_mode, is_compression_disabled=(
|
rep_mode=rep_mode, is_compression_disabled=(
|
||||||
True if is_compression_disabled else False))
|
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(
|
volume_metadata = self.update_volume_info_metadata(
|
||||||
datadict, self.version_dict)
|
datadict, self.version_dict)
|
||||||
self.print_pretty_table(volume_metadata)
|
self.print_pretty_table(volume_metadata)
|
||||||
|
|
|
@ -145,7 +145,7 @@ class PowerMaxProvision(object):
|
||||||
|
|
||||||
def create_volume_replica(
|
def create_volume_replica(
|
||||||
self, array, source_device_id, target_device_id,
|
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.
|
"""Create a snap vx of a source and copy to a target.
|
||||||
|
|
||||||
:param array: the array serial number
|
:param array: the array serial number
|
||||||
|
@ -166,7 +166,7 @@ class PowerMaxProvision(object):
|
||||||
def do_modify_volume_snap(src_device_id):
|
def do_modify_volume_snap(src_device_id):
|
||||||
self.rest.modify_volume_snap(
|
self.rest.modify_volume_snap(
|
||||||
array, src_device_id, target_device_id, snap_name,
|
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)
|
do_modify_volume_snap(source_device_id)
|
||||||
|
|
||||||
|
|
|
@ -1780,7 +1780,8 @@ class PowerMaxRest(object):
|
||||||
def modify_volume_snap(self, array, source_id, target_id, snap_name,
|
def modify_volume_snap(self, array, source_id, target_id, snap_name,
|
||||||
extra_specs, link=False, unlink=False,
|
extra_specs, link=False, unlink=False,
|
||||||
rename=False, new_snap_name=None, restore=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
|
"""Modify a snapvx snapshot
|
||||||
|
|
||||||
:param array: the array serial number
|
:param array: the array serial number
|
||||||
|
@ -1806,6 +1807,8 @@ class PowerMaxRest(object):
|
||||||
elif restore:
|
elif restore:
|
||||||
action = "Restore"
|
action = "Restore"
|
||||||
|
|
||||||
|
copy = 'true' if copy_mode else 'false'
|
||||||
|
|
||||||
payload = {}
|
payload = {}
|
||||||
if action == "Restore":
|
if action == "Restore":
|
||||||
operation = 'Restore snapVx snapshot'
|
operation = 'Restore snapVx snapshot'
|
||||||
|
@ -1825,7 +1828,7 @@ class PowerMaxRest(object):
|
||||||
tgt_list.append({'name': target_id})
|
tgt_list.append({'name': target_id})
|
||||||
payload = {"deviceNameListSource": src_list,
|
payload = {"deviceNameListSource": src_list,
|
||||||
"deviceNameListTarget": tgt_list,
|
"deviceNameListTarget": tgt_list,
|
||||||
"copy": 'false', "action": action,
|
"copy": copy, "action": action,
|
||||||
"star": 'false', "force": 'false',
|
"star": 'false', "force": 'false',
|
||||||
"exact": 'false', "remote": 'false',
|
"exact": 'false', "remote": 'false',
|
||||||
"symforce": 'false', "generation": generation}
|
"symforce": 'false', "generation": generation}
|
||||||
|
|
|
@ -909,18 +909,66 @@ class PowerMaxUtils(object):
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@staticmethod
|
def get_volume_attached_hostname(self, volume):
|
||||||
def get_volume_attached_hostname(device_info):
|
"""Get the host name from the attached volume
|
||||||
"""Parse a hostname from a storage group ID.
|
|
||||||
|
: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
|
:param device_info: the device info dict
|
||||||
:return: str -- the attached hostname
|
:returns: str -- the attached hostname
|
||||||
|
dict -- storage group details
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
sg_id = device_info.get("storageGroupId")[0]
|
sg_list = device_info.get("storageGroupId")
|
||||||
return sg_id.split('-')[1]
|
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:
|
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
|
@staticmethod
|
||||||
def validate_qos_input(input_key, sg_value, qos_extra_spec, property_dict):
|
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)
|
workload = extra_specs.get(WORKLOAD)
|
||||||
return service_level, 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):
|
def get_object_components_and_correct_host(self, regex_str, input_str):
|
||||||
"""Get components from input string.
|
"""Get components from input string.
|
||||||
|
|
||||||
|
@ -1026,3 +1099,42 @@ class PowerMaxUtils(object):
|
||||||
full_str = re.compile(regex_str)
|
full_str = re.compile(regex_str)
|
||||||
match = full_str.match(input_str)
|
match = full_str.match(input_str)
|
||||||
return match.groupdict() if match else None
|
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
|
||||||
|
|
|
@ -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.
|
Loading…
Reference in New Issue