PowerMax Driver - Short host name and port group name override

This feature allows the user to override the short host name and port group
name seen in PowerMax masking view and storage view terminology. This
facilitates the user to give more meaningful names, especially when the
short host name exceeds 16 characters and the port group name exceeds
12 characters, which is the condition where the driver truncates these value.

Change-Id: I38153c731170f736f83f3bda36ee6c8969a79d50
Implements: blueprint powermax-user-defined-hostname-portgroup
This commit is contained in:
Helen Walsh 2020-01-13 14:38:16 +00:00
parent cff7fffc36
commit e276986e4b
14 changed files with 1130 additions and 185 deletions

View File

@ -44,6 +44,7 @@ class PowerMaxData(object):
port_group_name_f = 'OS-fibre-PG'
port_group_name_i = 'OS-iscsi-PG'
masking_view_name_f = 'OS-HostX-F-OS-fibre-PG-MV'
masking_view_name_Y_f = 'OS-HostY-F-OS-fibre-PG-MV'
masking_view_name_i = 'OS-HostX-SRP_1-I-OS-iscsi-PG-MV'
initiatorgroup_name_f = 'OS-HostX-F-IG'
initiatorgroup_name_i = 'OS-HostX-I-IG'
@ -277,7 +278,8 @@ class PowerMaxData(object):
'storagetype:storagegrouptags': u'good, comma, separated,list'}
vol_type_extra_specs_tags_bad = {
'storagetype:storagegrouptags': u'B&d, [list]'}
extra_specs_port_group_template = deepcopy(extra_specs)
extra_specs_port_group_template['port_group_template'] = 'portGroupName'
extra_specs_migrate = deepcopy(extra_specs)
extra_specs_migrate[utils.PORTGROUPNAME] = port_group_name_f
@ -401,7 +403,9 @@ class PowerMaxData(object):
'storagegroup_name': storagegroup_name_f,
'volume_name': test_volume.name,
'workload': workload,
'replication_enabled': False}
'replication_enabled': False,
'used_host_name': 'HostX',
'port_group_label': port_group_name_f}
masking_view_dict_no_slo = deepcopy(masking_view_dict)
masking_view_dict_no_slo.update(

View File

@ -323,6 +323,10 @@ class FakeConfiguration(object):
self.u4p_failover_timeout = value
elif key == 'u4p_primary':
self.u4p_primary = value
elif key == 'powermax_short_host_name_template':
self.powermax_short_host_name_template = value
elif key == 'powermax_port_group_name_template':
self.powermax_port_group_name_template = value
def safe_get(self, key):
try:

View File

@ -44,9 +44,11 @@ class PowerMaxCommonTest(test.TestCase):
self.mock_object(volume_utils, 'get_max_over_subscription_ratio',
return_value=1.0)
configuration = tpfo.FakeConfiguration(
None, 'CommonTests', 1, 1, san_ip='1.1.1.1', san_login='smc',
emc_file=None, volume_backend_name='CommonTests', interval=1,
retries=1, san_ip='1.1.1.1', san_login='smc',
vmax_array=self.data.array, vmax_srp='SRP_1', san_password='smc',
san_api_port=8443, vmax_port_groups=[self.data.port_group_name_f])
san_api_port=8443, vmax_port_groups=[self.data.port_group_name_f],
powermax_port_group_name_template='portGroupName')
rest.PowerMaxRest._establish_rest_session = mock.Mock(
return_value=tpfo.FakeRequestsSession())
driver = fc.PowerMaxFCDriver(configuration=configuration)
@ -71,7 +73,6 @@ class PowerMaxCommonTest(test.TestCase):
side_effect=[[], tpd.PowerMaxData.array_info_wl])
def test_gather_info_tests(self, mck_parse, mck_combo, mck_rest,
mck_nextgen, mck_ucode):
# Use-Case 1: Gather info no-opts
configuration = tpfo.FakeConfiguration(
None, 'config_group', None, None)
@ -82,6 +83,54 @@ class PowerMaxCommonTest(test.TestCase):
self.assertTrue(self.common.next_gen)
self.assertEqual(self.common.ucode_level, self.data.next_gen_ucode)
@mock.patch.object(common.PowerMaxCommon,
'_gather_info')
def test_get_attributes_from_config_short_host_template(
self, mock_gather):
configuration = tpfo.FakeConfiguration(
emc_file=None, volume_backend_name='config_group', interval='10',
retries='10', replication_device=None,
powermax_short_host_name_template='shortHostName')
driver = fc.PowerMaxFCDriver(configuration=configuration)
driver.common._get_attributes_from_config()
self.assertEqual(
'shortHostName', driver.common.powermax_short_host_name_template)
@mock.patch.object(common.PowerMaxCommon,
'_gather_info')
def test_get_attributes_from_config_no_short_host_template(
self, mock_gather):
configuration = tpfo.FakeConfiguration(
emc_file=None, volume_backend_name='config_group', interval='10',
retries='10', replication_device=None)
driver = fc.PowerMaxFCDriver(configuration=configuration)
driver.common._get_attributes_from_config()
self.assertIsNone(driver.common.powermax_short_host_name_template)
@mock.patch.object(common.PowerMaxCommon,
'_gather_info')
def test_get_attributes_from_config_port_group_template(
self, mock_gather):
configuration = tpfo.FakeConfiguration(
emc_file=None, volume_backend_name='config_group', interval='10',
retries='10', replication_device=None,
powermax_port_group_name_template='portGroupName')
driver = fc.PowerMaxFCDriver(configuration=configuration)
driver.common._get_attributes_from_config()
self.assertEqual(
'portGroupName', driver.common.powermax_port_group_name_template)
@mock.patch.object(common.PowerMaxCommon,
'_gather_info')
def test_get_attributes_from_config_no_port_group_template(
self, mock_gather):
configuration = tpfo.FakeConfiguration(
emc_file=None, volume_backend_name='config_group', interval='10',
retries='10', replication_device=None)
driver = fc.PowerMaxFCDriver(configuration=configuration)
driver.common._get_attributes_from_config()
self.assertIsNone(driver.common.powermax_port_group_name_template)
def test_get_slo_workload_combinations_powermax(self):
array_info = self.common.get_attributes_from_cinder_config()
finalarrayinfolist = self.common._get_slo_workload_combinations(
@ -266,7 +315,8 @@ class PowerMaxCommonTest(test.TestCase):
array, volume, device_id, extra_specs, self.data.connector, False)
mock_rm.assert_called_once_with(
array, volume, device_id, volume_name,
extra_specs, True, self.data.connector, async_grp=None)
extra_specs, True, self.data.connector, async_grp=None,
host_template=None)
@mock.patch.object(masking.PowerMaxMasking,
'return_volume_to_fast_managed_group')
@ -281,7 +331,8 @@ class PowerMaxCommonTest(test.TestCase):
array, volume, device_id, extra_specs, self.data.connector, True)
mock_rm.assert_called_once_with(
array, volume, device_id, volume_name,
extra_specs, False, self.data.connector, async_grp=None)
extra_specs, False, self.data.connector, async_grp=None,
host_template=None)
mock_return.assert_called_once()
def test_unmap_lun(self):
@ -295,7 +346,18 @@ class PowerMaxCommonTest(test.TestCase):
self.common._unmap_lun(volume, connector)
mock_remove.assert_called_once_with(
array, volume, device_id, extra_specs,
connector, False, async_grp=None)
connector, False, async_grp=None, host_template=None)
def test_unmap_lun_force(self):
volume = self.data.test_volume
extra_specs = deepcopy(self.data.extra_specs_intervals_set)
extra_specs[utils.PORTGROUPNAME] = self.data.port_group_name_f
connector = deepcopy(self.data.connector)
del connector['host']
with mock.patch.object(
self.common.utils, 'get_host_short_name') as mock_host:
self.common._unmap_lun(volume, connector)
mock_host.assert_not_called()
@mock.patch.object(common.PowerMaxCommon, '_remove_members')
def test_unmap_lun_attachments(self, mock_rm):
@ -326,7 +388,7 @@ class PowerMaxCommonTest(test.TestCase):
self.common._unmap_lun(volume, connector)
mock_remove.assert_called_once_with(
array, volume, device_id, extra_specs,
connector, False, async_grp=None)
connector, False, async_grp=None, host_template=None)
def test_unmap_lun_not_mapped(self):
volume = self.data.test_volume
@ -349,7 +411,7 @@ class PowerMaxCommonTest(test.TestCase):
self.common._unmap_lun(volume, None)
mock_remove.assert_called_once_with(
array, volume, device_id, extra_specs, None,
False, async_grp=None)
False, async_grp=None, host_template=None)
def test_initialize_connection_already_mapped(self):
volume = self.data.test_volume
@ -612,7 +674,9 @@ class PowerMaxCommonTest(test.TestCase):
@mock.patch.object(
common.PowerMaxCommon, '_get_masking_views_from_volume',
return_value=([], [tpd.PowerMaxData.masking_view_name_f]))
return_value=([tpd.PowerMaxData.masking_view_name_f],
[tpd.PowerMaxData.masking_view_name_f,
tpd.PowerMaxData.masking_view_name_Y_f]))
def test_find_host_lun_id_multiattach(self, mock_mask):
volume = self.data.test_volume
extra_specs = self.data.extra_specs
@ -630,6 +694,27 @@ class PowerMaxCommonTest(test.TestCase):
self.data.extra_specs, self.data.rep_extra_specs)
mock_tgt.assert_called_once()
@mock.patch.object(rest.PowerMaxRest, 'find_mv_connections_for_vol',
return_value='1')
@mock.patch.object(common.PowerMaxCommon, '_get_masking_views_from_volume',
side_effect=[([], ['OS-HostX-I-PG-MV']),
(['OS-HostX-I-PG-MV'],
['OS-HostX-I-PG-MV'])])
@mock.patch.object(rest.PowerMaxRest, 'get_volume',
return_value=tpd.PowerMaxData.volume_details[0])
def test_find_host_lun_id_backward_compatible(
self, mock_vol, mock_mvs, mock_mv_conns):
expected_dict = {'hostlunid': '1', 'maskingview': 'OS-HostX-I-PG-MV',
'array': '000197800123', 'device_id': '00001'}
self.common.powermax_short_host_name_template = (
'shortHostName[:7]finance')
masked_vols, is_multiattach = self.common.find_host_lun_id(
self.data.test_volume, 'HostX',
self.data.extra_specs)
self.assertEqual(expected_dict, masked_vols)
self.assertFalse(is_multiattach)
mock_mv_conns.assert_called_once()
def test_get_masking_views_from_volume(self):
array = self.data.array
device_id = self.data.device_id
@ -691,6 +776,7 @@ class PowerMaxCommonTest(test.TestCase):
extra_specs[utils.WORKLOAD] = self.data.workload
ref_mv_dict = self.data.masking_view_dict
self.common.next_gen = False
self.common.powermax_port_group_name_template = 'portGroupName'
masking_view_dict = self.common._populate_masking_dict(
volume, connector, extra_specs)
self.assertEqual(ref_mv_dict, masking_view_dict)
@ -1140,6 +1226,31 @@ class PowerMaxCommonTest(test.TestCase):
self.data.test_volume, self.data.connector)
self.assertEqual([self.data.wwnn1], metro_wwns)
@mock.patch.object(common.PowerMaxCommon,
'_get_target_wwns_from_masking_view')
@mock.patch.object(utils.PowerMaxUtils, 'get_host_name_label',
return_value = 'my_short_h94485')
@mock.patch.object(utils.PowerMaxUtils, 'is_replication_enabled',
return_value=False)
def test_get_target_wwns_host_override(
self, mock_rep_check, mock_label, mock_mv):
host_record = {'host': 'my_short_host_name'}
connector = deepcopy(self.data.connector)
connector.update(host_record)
extra_specs = {'pool_name': 'Diamond+DSS+SRP_1+000197800123',
'srp': 'SRP_1', 'array': '000197800123',
'storagetype:portgroupname': 'OS-fibre-PG',
'interval': 1, 'retries': 1, 'slo': 'Diamond',
'workload': 'DSS'}
host_template = 'shortHostName[:10]uuid[:5]'
self.common.powermax_short_host_name_template = host_template
self.common.get_target_wwns_from_masking_view(
self.data.test_volume, connector)
mock_label.assert_called_once_with(
connector['host'], host_template)
mock_mv.assert_called_once_with(
self.data.device_id, 'my_short_h94485', extra_specs)
def test_get_port_group_from_masking_view(self):
array = self.data.array
maskingview_name = self.data.masking_view_name_f
@ -1462,7 +1573,7 @@ class PowerMaxCommonTest(test.TestCase):
@mock.patch.object(rest.PowerMaxRest, 'get_storage_group',
return_value=tpd.PowerMaxData.sg_details[1])
@mock.patch.object(utils.PowerMaxUtils, 'get_child_sg_name',
return_value=('OS-Test-SG', '', '', ''))
return_value=('OS-Test-SG', '', ''))
@mock.patch.object(rest.PowerMaxRest, 'is_child_sg_in_parent_sg',
return_value=True)
@mock.patch.object(masking.PowerMaxMasking,
@ -1498,7 +1609,7 @@ class PowerMaxCommonTest(test.TestCase):
rest.PowerMaxRest, 'get_volume',
return_value=tpd.PowerMaxData.volume_details_attached)
@mock.patch.object(utils.PowerMaxUtils, 'get_child_sg_name',
return_value=('OS-Test-SG', '', '', ''))
return_value=('OS-Test-SG', '', ''))
@mock.patch.object(provision.PowerMaxProvision, 'create_storage_group')
@mock.patch.object(masking.PowerMaxMasking, 'add_child_sg_to_parent_sg')
@mock.patch.object(rest.PowerMaxRest, 'is_child_sg_in_parent_sg',
@ -1542,7 +1653,7 @@ class PowerMaxCommonTest(test.TestCase):
@mock.patch.object(rest.PowerMaxRest, 'get_storage_group',
side_effect=[tpd.PowerMaxData.sg_details[1], None])
@mock.patch.object(utils.PowerMaxUtils, 'get_child_sg_name',
return_value=('OS-Test-SG', '', '', ''))
return_value=('OS-Test-SG', '', ''))
@mock.patch.object(rest.PowerMaxRest, 'is_child_sg_in_parent_sg',
return_value=False)
@mock.patch.object(masking.PowerMaxMasking,

View File

@ -148,6 +148,19 @@ class PowerMaxFCTest(test.TestCase):
self.data.test_volume, self.data.connector)
self.assertEqual({}, zoning_mappings)
@mock.patch.object(
common.PowerMaxCommon, 'get_masking_views_from_volume',
side_effect = ([(None, False),
([tpd.PowerMaxData.masking_view_name_f], False)]))
def test_get_zoning_mappings_retry_backward_compatibility(
self, mock_views):
with mock.patch.object(self.common.utils, 'get_host_name_label',
return_value=None) as mock_label:
self.driver._get_zoning_mappings(
self.data.test_volume, self.data.connector)
self.assertEqual(2, mock_label.call_count)
self.assertEqual(2, mock_views.call_count)
@mock.patch.object(
common.PowerMaxCommon, 'get_masking_views_from_volume',
return_value=([tpd.PowerMaxData.masking_view_name_f], True))

View File

@ -833,7 +833,7 @@ class PowerMaxMaskingTest(test.TestCase):
self.data.masking_view_name_f]
with mock.patch.object(
rest.PowerMaxRest, 'get_masking_views_by_initiator_group',
side_effect=[mv_list, []]):
side_effect=[mv_list, mv_list, [], []]):
self.mask._last_volume_delete_initiator_group(
self.data.array, self.data.initiatorgroup_name_i,
self.data.connector['host'])
@ -974,7 +974,8 @@ class PowerMaxMaskingTest(test.TestCase):
def test_pre_multiattach(self, mock_return):
mv_dict = self.mask.pre_multiattach(
self.data.array, self.data.device_id,
self.data.masking_view_dict_multiattach, self.data.extra_specs)
self.data.masking_view_dict_multiattach,
self.data.extra_specs)
mock_return.assert_not_called()
self.assertEqual(self.data.storagegroup_name_f,
mv_dict[utils.FAST_SG])
@ -993,7 +994,8 @@ class PowerMaxMaskingTest(test.TestCase):
return_value='DiamondDSS'):
self.mask.pre_multiattach(
self.data.array, self.data.device_id,
self.data.masking_view_dict_multiattach, self.data.extra_specs)
self.data.masking_view_dict_multiattach,
self.data.extra_specs)
utils.PowerMaxUtils.truncate_string.assert_called_once_with(
'DiamondDSS', 10)
@ -1007,7 +1009,8 @@ class PowerMaxMaskingTest(test.TestCase):
self, mock_return, mock_sg):
for x in range(0, 2):
self.mask.return_volume_to_fast_managed_group(
self.data.array, self.data.device_id, self.data.extra_specs)
self.data.array, self.data.device_id,
self.data.extra_specs)
no_slo_specs = deepcopy(self.data.extra_specs)
no_slo_specs[utils.SLO] = None
self.mask.return_volume_to_fast_managed_group(
@ -1093,3 +1096,44 @@ class PowerMaxMaskingTest(test.TestCase):
self.data.array, self.data.add_volume_sg_info_dict,
self.data.extra_specs_tags)
mock_except.assert_called()
@mock.patch.object(rest.PowerMaxRest,
'get_masking_views_from_storage_group',
return_value=[tpd.PowerMaxData.masking_view_name_f])
def test_get_host_and_port_group_labels(self, mock_mv):
host_label, port_group_label = (
self.mask._get_host_and_port_group_labels(
self.data.array, self.data.parent_sg_f))
self.assertEqual('HostX', host_label)
self.assertEqual('OS-fibre-PG', port_group_label)
@mock.patch.object(rest.PowerMaxRest,
'get_masking_views_from_storage_group',
return_value=['OS-HostX699ea-I-p-name3b02c-MV'])
def test_get_host_and_port_group_labels_complex(self, mock_mv):
host_label, port_group_label = (
self.mask._get_host_and_port_group_labels(
self.data.array, self.data.parent_sg_f))
self.assertEqual('HostX699ea', host_label)
self.assertEqual('p-name3b02c', port_group_label)
@mock.patch.object(rest.PowerMaxRest,
'get_masking_views_from_storage_group',
return_value=['OS-myhost-I-myportgroup-MV'])
def test_get_host_and_port_group_labels_plain(self, mock_mv):
host_label, port_group_label = (
self.mask._get_host_and_port_group_labels(
self.data.array, self.data.parent_sg_f))
self.assertEqual('myhost', host_label)
self.assertEqual('myportgroup', port_group_label)
@mock.patch.object(rest.PowerMaxRest,
'get_masking_views_from_storage_group',
return_value=[
'OS-host-with-dash-I-portgroup-with-dashes-MV'])
def test_get_host_and_port_group_labels_dashes(self, mock_mv):
host_label, port_group_label = (
self.mask._get_host_and_port_group_labels(
self.data.array, self.data.parent_sg_f))
self.assertEqual('host-with-dash', host_label)
self.assertEqual('portgroup-with-dashes', port_group_label)

View File

@ -51,18 +51,18 @@ class PowerMaxReplicationTest(test.TestCase):
'allow_extend': 'True'}
volume_utils.get_max_over_subscription_ratio = mock.Mock()
configuration = tpfo.FakeConfiguration(
None, 'CommonReplicationTests', 1, 1, san_ip='1.1.1.1',
san_login='smc', vmax_array=self.data.array, vmax_srp='SRP_1',
san_password='smc', san_api_port=8443,
None, 'CommonReplicationTests', interval=1, retries=1,
san_ip='1.1.1.1', san_login='smc', vmax_array=self.data.array,
vmax_srp='SRP_1', san_password='smc', san_api_port=8443,
vmax_port_groups=[self.data.port_group_name_f],
replication_device=self.replication_device)
rest.PowerMaxRest._establish_rest_session = mock.Mock(
return_value=tpfo.FakeRequestsSession())
driver = fc.PowerMaxFCDriver(configuration=configuration)
iscsi_config = tpfo.FakeConfiguration(
None, 'CommonReplicationTests', 1, 1, san_ip='1.1.1.1',
san_login='smc', vmax_array=self.data.array, vmax_srp='SRP_1',
san_password='smc', san_api_port=8443,
None, 'CommonReplicationTests', interval=1, retries=1,
san_ip='1.1.1.1', san_login='smc', vmax_array=self.data.array,
vmax_srp='SRP_1', san_password='smc', san_api_port=8443,
vmax_port_groups=[self.data.port_group_name_i],
replication_device=self.replication_device)
iscsi_driver = iscsi.PowerMaxISCSIDriver(configuration=iscsi_config)
@ -87,9 +87,9 @@ class PowerMaxReplicationTest(test.TestCase):
'rdf_group_label': self.data.rdf_group_name,
'allow_extend': 'True', 'mode': 'async'}
async_configuration = tpfo.FakeConfiguration(
None, 'CommonReplicationTests', 1, 1, san_ip='1.1.1.1',
san_login='smc', vmax_array=self.data.array, vmax_srp='SRP_1',
san_password='smc', san_api_port=8443,
None, 'CommonReplicationTests', interval=1, retries=1,
san_ip='1.1.1.1', san_login='smc', vmax_array=self.data.array,
vmax_srp='SRP_1', san_password='smc', san_api_port=8443,
vmax_port_groups=[self.data.port_group_name_f],
replication_device=self.async_rep_device)
self.async_driver = fc.PowerMaxFCDriver(
@ -101,9 +101,9 @@ class PowerMaxReplicationTest(test.TestCase):
'rdf_group_label': self.data.rdf_group_name,
'allow_extend': 'True', 'mode': 'metro'}
metro_configuration = tpfo.FakeConfiguration(
None, 'CommonReplicationTests', 1, 1, san_ip='1.1.1.1',
san_login='smc', vmax_array=self.data.array, vmax_srp='SRP_1',
san_password='smc', san_api_port=8443,
None, 'CommonReplicationTests', interval=1, retries=1,
san_ip='1.1.1.1', san_login='smc', vmax_array=self.data.array,
vmax_srp='SRP_1', san_password='smc', san_api_port=8443,
vmax_port_groups=[self.data.port_group_name_f],
replication_device=self.metro_rep_device)
self.metro_driver = fc.PowerMaxFCDriver(

View File

@ -450,6 +450,7 @@ class PowerMaxUtilsTest(test.TestCase):
def test_get_child_sg_name(self):
host_name = 'HostX'
port_group_label = self.data.port_group_name_f
# Slo and rep enabled
extra_specs1 = {
'pool_name': u'Diamond+DSS+SRP_1+000197800123',
@ -463,23 +464,24 @@ class PowerMaxUtilsTest(test.TestCase):
'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))
child_sg_name, do_disable_compression, rep_enabled = (
self.utils.get_child_sg_name(
host_name, extra_specs1, port_group_label))
re_name = self.data.storagegroup_name_f + '-RE'
self.assertEqual(re_name, child_sg_name)
# 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))
child_sg_name, do_disable_compression, rep_enabled = (
self.utils.get_child_sg_name(
host_name, extra_specs2, port_group_label))
cd_name = self.data.storagegroup_name_f + '-CD'
self.assertEqual(cd_name, child_sg_name)
# No slo
extra_specs3 = deepcopy(self.data.extra_specs)
extra_specs3[utils.SLO] = None
extra_specs3[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_specs3))
child_sg_name, do_disable_compression, rep_enabled = (
self.utils.get_child_sg_name(
host_name, extra_specs3, port_group_label))
self.assertEqual(self.data.no_slo_sg_name, child_sg_name)
def test_change_multiattach(self):
@ -732,3 +734,285 @@ class PowerMaxUtilsTest(test.TestCase):
input_list = 'one,two,three'
output_string = self.utils.convert_list_to_string(input_list)
self.assertEqual('one,two,three', output_string)
def test_regex_check_case_2(self):
test_template = 'shortHostName[:10]uuid[:5]'
is_ok, case = self.utils.regex_check(test_template, True)
self.assertTrue(is_ok)
self.assertEqual('2', case)
def test_regex_check_case_3(self):
test_template = 'shortHostName[-10:]uuid[:5]'
is_ok, case = self.utils.regex_check(test_template, True)
self.assertTrue(is_ok)
self.assertEqual('3', case)
def test_regex_check_case_4(self):
test_template = 'shortHostName[:7]finance'
is_ok, case = self.utils.regex_check(test_template, True)
self.assertTrue(is_ok)
self.assertEqual('4', case)
def test_regex_check_case_5(self):
test_template = 'shortHostName[-6:]production'
is_ok, case = self.utils.regex_check(test_template, True)
self.assertTrue(is_ok)
self.assertEqual('5', case)
def test_regex_check_case_2_misspelt(self):
test_template = 'shortHstName[:10]uuid[:5]'
is_ok, case = self.utils.regex_check(test_template, True)
self.assertFalse(is_ok)
self.assertEqual('0', case)
def test_regex_check_case_3_misspelt(self):
test_template = 'shortHostName[-10:]uud[:5]'
is_ok, case = self.utils.regex_check(test_template, True)
self.assertFalse(is_ok)
self.assertEqual('0', case)
def test_regex_check_case_4_misspelt(self):
test_template = 'shortHotName[:7]finance'
is_ok, case = self.utils.regex_check(test_template, True)
self.assertFalse(is_ok)
self.assertEqual('0', case)
def test_regex_check_case_5_misspelt(self):
test_template = 'shortHstName[-6:]production'
is_ok, case = self.utils.regex_check(test_template, True)
self.assertFalse(is_ok)
self.assertEqual('0', case)
def test_regex_check_case_4_invalid_chars(self):
test_template = 'shortHostName[:7]f*n&nce'
is_ok, case = self.utils.regex_check(test_template, True)
self.assertFalse(is_ok)
self.assertEqual('0', case)
def test_regex_check_case_5_invalid_chars(self):
test_template = 'shortHostName[-6:]pr*ducti*n'
is_ok, case = self.utils.regex_check(test_template, True)
self.assertFalse(is_ok)
self.assertEqual('0', case)
def test_regex_check_case_2_missing_square_bracket(self):
test_template = 'shortHostName[:10uuid[:5]'
is_ok, case = self.utils.regex_check(test_template, True)
self.assertFalse(is_ok)
self.assertEqual('0', case)
def test_regex_check_case_4_missing_square_bracket(self):
test_template = 'shortHostName[:10finance'
is_ok, case = self.utils.regex_check(test_template, True)
self.assertFalse(is_ok)
self.assertEqual('0', case)
def test_prepare_string_entity_case_2(self):
test_template = 'shortHostName[:10]uuid[:5]'
altered_string = self.utils.prepare_string_entity(
test_template, 'my_short_host_name', True)
self.assertEqual(
'my_short_host_name[:10]uuid[:5]',
altered_string)
def test_prepare_string_entity_case_3(self):
test_template = 'shortHostName[-10:]uuid[:5]'
altered_string = self.utils.prepare_string_entity(
test_template, 'my_short_host_name', True)
self.assertEqual(
'my_short_host_name[-10:]uuid[:5]',
altered_string)
def test_prepare_string_entity_case_4(self):
test_template = 'shortHostName[:7]finance'
altered_string = self.utils.prepare_string_entity(
test_template, 'my_short_host_name', True)
self.assertEqual(
'my_short_host_name[:7]finance',
altered_string)
def test_prepare_string_entity_case_5(self):
test_template = 'shortHostName[-6:]production'
altered_string = self.utils.prepare_string_entity(
test_template, 'my_short_host_name', True)
self.assertEqual(
'my_short_host_name[-6:]production',
altered_string)
def test_prepare_string_with_uuid_case_2(self):
test_template = 'shortHostName[:10]uuid[:5]'
pass_two, uuid = self.utils.prepare_string_with_uuid(
test_template, 'my_short_host_name', True)
self.assertEqual(
'my_short_host_name[:10]944854dce45898b544a1cb9071d3cc35[:5]',
pass_two)
self.assertEqual('944854dce45898b544a1cb9071d3cc35', uuid)
def test_prepare_string_with_uuid_case_3(self):
test_template = 'shortHostName[-10:]uuid[:5]'
pass_two, uuid = self.utils.prepare_string_with_uuid(
test_template, 'my_short_host_name', True)
self.assertEqual(
'my_short_host_name[-10:]944854dce45898b544a1cb9071d3cc35[:5]',
pass_two)
self.assertEqual('944854dce45898b544a1cb9071d3cc35', uuid)
def test_check_upper_limit_short_host(self):
self.assertRaises(exception.VolumeBackendAPIException,
self.utils.check_upper_limit,
12, 12, True)
def test_check_upper_limit_short_host_case_4(self):
user_define_name = 'Little_too_long'
self.assertRaises(exception.VolumeBackendAPIException,
self.utils.check_upper_limit,
12, len(user_define_name), True)
def test_validate_short_host_name_from_template_case_1(self):
test_template = 'shortHostName'
short_host_name = 'my_short_host'
result_string = self.utils.validate_short_host_name_from_template(
test_template, short_host_name)
self.assertEqual('my_short_host', result_string)
def test_validate_short_host_name_from_template_case_1_exceeds_16char(
self):
test_template = 'shortHostName'
short_host_name = 'my_short_host_greater_than_16chars'
result_string = self.utils.validate_short_host_name_from_template(
test_template, short_host_name)
self.assertEqual('6chars0bc43f914e', result_string)
def test_validate_short_host_name_from_template_case_1_template_misspelt(
self):
test_template = 'shortHstName'
short_host_name = 'my_short_host'
self.assertRaises(exception.VolumeBackendAPIException,
self.utils.validate_short_host_name_from_template,
test_template, short_host_name)
def test_validate_short_host_name_from_template_case_2(self):
test_template = 'shortHostName[:10]uuid[:5]'
short_host_name = 'my_short_host_name'
result_string = self.utils.validate_short_host_name_from_template(
test_template, short_host_name)
self.assertEqual('my_short_h94485', result_string)
def test_validate_short_host_name_from_template_case_2_shorter_than(self):
test_template = 'shortHostName[:10]uuid[:5]'
short_host_name = 'HostX'
result_string = self.utils.validate_short_host_name_from_template(
test_template, short_host_name)
self.assertEqual('HostX699ea', result_string)
def test_validate_short_host_name_from_template_case_3(self):
test_template = 'shortHostName[-10:]uuid[:5]'
short_host_name = 'my_short_host_name'
result_string = self.utils.validate_short_host_name_from_template(
test_template, short_host_name)
self.assertEqual('_host_name94485', result_string)
def test_validate_short_host_name_from_template_case_3_shorter_than(self):
test_template = 'shortHostName[-10:]uuid[:5]'
short_host_name = 'HostX'
result_string = self.utils.validate_short_host_name_from_template(
test_template, short_host_name)
self.assertEqual('HostX699ea', result_string)
def test_validate_short_host_name_from_template_case_4(self):
test_template = 'shortHostName[:7]finance'
short_host_name = 'my_short_host_name'
result_string = self.utils.validate_short_host_name_from_template(
test_template, short_host_name)
self.assertEqual('my_shorfinance', result_string)
def test_validate_short_host_name_from_template_case_5(self):
test_template = 'shortHostName[-6:]production'
short_host_name = 'my_short_host_name'
result_string = self.utils.validate_short_host_name_from_template(
test_template, short_host_name)
self.assertEqual('t_nameproduction', result_string)
def test_validate_short_host_name_exception_missing_minus(self):
test_template = 'shortHostName[6:]production'
short_host_name = 'my_short_host_name'
self.assertRaises(exception.VolumeBackendAPIException,
self.utils.validate_short_host_name_from_template,
test_template, short_host_name)
def test_validate_port_group_from_template_case_1(self):
test_template = 'portGroupName'
port_group_name = 'my_pg'
result_string = self.utils.validate_port_group_name_from_template(
test_template, port_group_name)
self.assertEqual('my_pg', result_string)
def test_validate_port_group_from_template_case_1_long(self):
test_template = 'portGroupName'
port_group_name = 'my_port_group_name'
result_string = self.utils.validate_port_group_name_from_template(
test_template, port_group_name)
self.assertEqual('p_name5ba163', result_string)
def test_validate_port_group_from_template_case_1_misspelt(self):
test_template = 'portGr*upName'
port_group_name = 'my_port_group_name'
self.assertRaises(exception.VolumeBackendAPIException,
self.utils.validate_port_group_name_from_template,
test_template, port_group_name)
def test_validate_port_group_from_template_case_2(self):
test_template = 'portGroupName[:6]uuid[:5]'
port_group_name = 'my_port_group_name'
result_string = self.utils.validate_port_group_name_from_template(
test_template, port_group_name)
self.assertEqual('my_por3b02c', result_string)
def test_validate_port_group_from_template_case_3(self):
test_template = 'portGroupName[-6:]uuid[:5]'
port_group_name = 'my_port_group_name'
result_string = self.utils.validate_port_group_name_from_template(
test_template, port_group_name)
self.assertEqual('p_name3b02c', result_string)
def test_validate_port_group_from_template_case_4(self):
test_template = 'portGroupName[:6]test'
port_group_name = 'my_port_group_name'
result_string = self.utils.validate_port_group_name_from_template(
test_template, port_group_name)
self.assertEqual('my_portest', result_string)
def test_validate_port_group_from_template_case_5(self):
test_template = 'portGroupName[-7:]test'
port_group_name = 'my_port_group_name'
result_string = self.utils.validate_port_group_name_from_template(
test_template, port_group_name)
self.assertEqual('up_nametest', result_string)
def test_validate_port_group_name_exception_missing_minus(self):
test_template = 'portGroupName[6:]test'
port_group_name = 'my_port_group_name'
self.assertRaises(exception.VolumeBackendAPIException,
self.utils.validate_port_group_name_from_template,
test_template, port_group_name)
def test_validate_port_group_name_exception_chars_exceeded(self):
test_template = 'portGroupName[:10]test'
port_group_name = 'my_port_group_name'
self.assertRaises(exception.VolumeBackendAPIException,
self.utils.validate_port_group_name_from_template,
test_template, port_group_name)
def test_get_port_name_label_default(self):
port_name_in = 'my_port_group_name'
port_group_template = 'portGroupName'
port_name_out = self.utils.get_port_name_label(
port_name_in, port_group_template)
self.assertEqual('p_name5ba163', port_name_out)
def test_get_port_name_label_template(self):
port_name_in = 'my_port_group_name'
port_group_template = 'portGroupName[-6:]uuid[:5]'
port_name_out = self.utils.get_port_name_label(
port_name_in, port_group_template)
self.assertEqual('p_name3b02c', port_name_out)

View File

@ -140,7 +140,13 @@ powermax_opts = [
'configured prior for server connection.'),
cfg.ListOpt(utils.POWERMAX_ARRAY_TAG_LIST,
bounds=True,
help='List of user assigned name for storage array.')]
help='List of user assigned name for storage array.'),
cfg.StrOpt(utils.POWERMAX_SHORT_HOST_NAME_TEMPLATE,
default='shortHostName',
help='User defined override for short host name.'),
cfg.StrOpt(utils.POWERMAX_PORT_GROUP_NAME_TEMPLATE,
default='portGroupName',
help='User defined override for port group name.')]
CONF.register_opts(powermax_opts, group=configuration.SHARED_CONF_GROUP)
@ -185,6 +191,8 @@ class PowerMaxCommon(object):
self.rep_devices = []
self.failover = False
self.powermax_array_tag_list = None
self.powermax_short_host_name_template = None
self.powermax_port_group_name_template = None
# Gather environment info
self._get_replication_info()
@ -220,6 +228,10 @@ class PowerMaxCommon(object):
utils.POWERMAX_SNAPVX_UNLINK_LIMIT)
self.powermax_array_tag_list = self.configuration.safe_get(
utils.POWERMAX_ARRAY_TAG_LIST)
self.powermax_short_host_name_template = self.configuration.safe_get(
utils.POWERMAX_SHORT_HOST_NAME_TEMPLATE)
self.powermax_port_group_name_template = self.configuration.safe_get(
utils.POWERMAX_PORT_GROUP_NAME_TEMPLATE)
self.pool_info['backend_name'] = (
self.configuration.safe_get('volume_backend_name'))
mosr = volume_utils.get_max_over_subscription_ratio(
@ -673,7 +685,7 @@ class PowerMaxCommon(object):
def _remove_members(self, array, volume, device_id,
extra_specs, connector, is_multiattach,
async_grp=None):
async_grp=None, host_template=None):
"""This method unmaps a volume from a host.
Removes volume from the storage group that belongs to a masking view.
@ -690,7 +702,8 @@ class PowerMaxCommon(object):
reset = False if is_multiattach else True
self.masking.remove_and_reset_members(
array, volume, device_id, volume_name,
extra_specs, reset, connector, async_grp=async_grp)
extra_specs, reset, connector, async_grp=async_grp,
host_template=host_template)
if is_multiattach:
self.masking.return_volume_to_fast_managed_group(
array, device_id, extra_specs)
@ -713,7 +726,7 @@ class PowerMaxCommon(object):
async_grp = None
LOG.info("Unmap volume: %(volume)s.", {'volume': volume})
if connector is not None:
host = self.utils.get_host_short_name(connector['host'])
host_name = connector.get('host')
attachment_list = volume.volume_attachment
LOG.debug("Volume attachment list: %(atl)s. "
"Attachment type: %(at)s",
@ -725,7 +738,7 @@ class PowerMaxCommon(object):
if att_list is not None:
host_list = [att.connector['host'] for att in att_list if
att is not None and att.connector is not None]
current_host_occurances = host_list.count(host)
current_host_occurances = host_list.count(host_name)
if current_host_occurances > 1:
LOG.info("Volume is attached to multiple instances on "
"this host. Not removing the volume from the "
@ -734,10 +747,10 @@ class PowerMaxCommon(object):
else:
LOG.warning("Cannot get host name from connector object - "
"assuming force-detach.")
host = None
host_name = None
device_info, is_multiattach = (
self.find_host_lun_id(volume, host, extra_specs))
self.find_host_lun_id(volume, host_name, extra_specs))
if 'hostlunid' not in device_info:
LOG.info("Volume %s is not mapped. No volume to unmap.",
volume_name)
@ -746,23 +759,25 @@ class PowerMaxCommon(object):
if self.utils.does_vol_need_rdf_management_group(extra_specs):
async_grp = self.utils.get_async_rdf_managed_grp_name(
self.rep_config)
self._remove_members(array, volume, device_info['device_id'],
extra_specs, connector, is_multiattach,
async_grp=async_grp)
self._remove_members(
array, volume, device_info['device_id'], extra_specs, connector,
is_multiattach, async_grp=async_grp,
host_template=self.powermax_short_host_name_template)
if self.utils.is_metro_device(self.rep_config, extra_specs):
# Need to remove from remote masking view
device_info, __ = (self.find_host_lun_id(
volume, host, extra_specs, rep_extra_specs))
volume, host_name, extra_specs, rep_extra_specs))
if 'hostlunid' in device_info:
self._remove_members(
rep_extra_specs[utils.ARRAY], volume,
device_info['device_id'], rep_extra_specs, connector,
is_multiattach, async_grp=async_grp)
is_multiattach, async_grp=async_grp,
host_template=self.powermax_short_host_name_template)
else:
# Make an attempt to clean up initiator group
self.masking.attempt_ig_cleanup(
connector, self.protocol, rep_extra_specs[utils.ARRAY],
True)
True, host_template=self.powermax_short_host_name_template)
if is_multiattach and LOG.isEnabledFor(logging.DEBUG):
mv_list, sg_list = (
self._get_mvs_and_sgs_from_volume(
@ -817,7 +832,7 @@ class PowerMaxCommon(object):
if self.utils.is_volume_failed_over(volume):
extra_specs = rep_extra_specs
device_info_dict, is_multiattach = (
self.find_host_lun_id(volume, connector['host'], extra_specs))
self.find_host_lun_id(volume, connector.get('host'), extra_specs))
masking_view_dict = self._populate_masking_dict(
volume, connector, extra_specs)
masking_view_dict[utils.IS_MULTIATTACH] = is_multiattach
@ -843,7 +858,7 @@ class PowerMaxCommon(object):
device_info_dict['maskingview']))
if self.utils.is_metro_device(self.rep_config, extra_specs):
remote_info_dict, is_multiattach = (
self.find_host_lun_id(volume, connector['host'],
self.find_host_lun_id(volume, connector.get('host'),
extra_specs, rep_extra_specs))
if remote_info_dict.get('hostlunid') is None:
# Need to attach on remote side
@ -987,7 +1002,7 @@ class PowerMaxCommon(object):
# Find host lun id again after the volume is exported to the host.
device_info_dict, __ = self.find_host_lun_id(
volume, connector['host'], extra_specs, rep_extra_specs)
volume, connector.get('host'), extra_specs, rep_extra_specs)
if 'hostlunid' not in device_info_dict:
# Did not successfully attach to host, so a rollback is required.
error_message = (_("Error Attaching volume %(vol)s. Cannot "
@ -1499,13 +1514,23 @@ class PowerMaxCommon(object):
device_id = self.get_remote_target_device(
extra_specs[utils.ARRAY], volume, device_id)[0]
extra_specs = rep_extra_specs
host_name = self.utils.get_host_short_name(host) if host else None
host_name = self.utils.get_host_name_label(
host, self.powermax_short_host_name_template) if host else None
if device_id:
array = extra_specs[utils.ARRAY]
# Return only masking views for this host
host_maskingviews, all_masking_view_list = (
self._get_masking_views_from_volume(
array, device_id, host_name))
if not host_maskingviews:
# Backward compatibility if a new template was added to
# an existing backend.
host_name = self.utils.get_host_short_name(
host) if host else None
host_maskingviews, all_masking_view_list = (
self._get_masking_views_from_volume_for_host(
all_masking_view_list, host_name))
for maskingview in host_maskingviews:
host_lun_id = self.rest.find_mv_connections_for_vol(
@ -1516,6 +1541,7 @@ class PowerMaxCommon(object):
'array': array,
'device_id': device_id}
maskedvols = devicedict
if not maskedvols:
LOG.debug(
"Host lun id not found for volume: %(volume_name)s "
@ -1573,17 +1599,28 @@ class PowerMaxCommon(object):
:returns: masking view list, all masking view list
"""
LOG.debug("Getting masking views from volume")
host_maskingview_list, all_masking_view_list = [], []
host_compare = True if host else False
mvs, __ = self._get_mvs_and_sgs_from_volume(array, device_id)
for mv in mvs:
all_masking_view_list.append(mv)
if host_compare:
if host.lower() in mv.lower():
host_maskingview_list.append(mv)
maskingview_list = (host_maskingview_list if host_compare else
all_masking_view_list)
return maskingview_list, all_masking_view_list
return self._get_masking_views_from_volume_for_host(mvs, host)
def _get_masking_views_from_volume_for_host(
self, masking_views, host_name):
"""Check all masking views for host_name
:param masking_views: list of masking view
:param host_name: the host name for comparision
:returns: masking view list, all masking view list
"""
LOG.debug("Getting masking views from volume for host %(host)s ",
{'host': host_name})
host_masking_view_list, all_masking_view_list = [], []
for masking_view in masking_views:
all_masking_view_list.append(masking_view)
if host_name:
if host_name.lower() in masking_view.lower():
host_masking_view_list.append(masking_view)
host_masking_view_list = (host_masking_view_list if host_name else
all_masking_view_list)
return host_masking_view_list, all_masking_view_list
def _get_mvs_and_sgs_from_volume(self, array, device_id):
"""Helper function to retrieve masking views and storage groups.
@ -1664,7 +1701,10 @@ class PowerMaxCommon(object):
raise exception.VolumeBackendAPIException(exception_message)
protocol = self.utils.get_short_protocol_type(self.protocol)
short_host_name = self.utils.get_host_short_name(connector['host'])
short_host_name = self.utils.get_host_name_label(
connector['host'], self.powermax_short_host_name_template)
masking_view_dict[utils.USED_HOST_NAME] = short_host_name
masking_view_dict[utils.SLO] = extra_specs[utils.SLO]
masking_view_dict[utils.WORKLOAD] = 'NONE' if self.next_gen else (
extra_specs[utils.WORKLOAD])
@ -1675,19 +1715,27 @@ class PowerMaxCommon(object):
"in cinder.conf or as an extra spec. Port group "
"cannot be left empty as creating a new masking "
"view will fail.")
masking_view_dict[utils.PORT_GROUP_LABEL] = (
self.utils.get_port_name_label(
extra_specs[utils.PORTGROUPNAME],
self.powermax_port_group_name_template))
masking_view_dict[utils.PORTGROUPNAME] = (
extra_specs[utils.PORTGROUPNAME])
masking_view_dict[utils.INITIATOR_CHECK] = (
self._get_initiator_check_flag())
child_sg_name, do_disable_compression, rep_enabled, short_pg_name = (
self.utils.get_child_sg_name(short_host_name, extra_specs))
child_sg_name, do_disable_compression, rep_enabled = (
self.utils.get_child_sg_name(
short_host_name, extra_specs,
masking_view_dict[utils.PORT_GROUP_LABEL]))
masking_view_dict[utils.DISABLECOMPRESSION] = do_disable_compression
masking_view_dict[utils.IS_RE] = rep_enabled
mv_prefix = (
"OS-%(shortHostName)s-%(protocol)s-%(pg)s"
% {'shortHostName': short_host_name,
'protocol': protocol, 'pg': short_pg_name})
'protocol': protocol,
'pg': masking_view_dict[utils.PORT_GROUP_LABEL]})
masking_view_dict[utils.SG_NAME] = child_sg_name
@ -2159,7 +2207,8 @@ class PowerMaxCommon(object):
"""
metro_wwns = []
host = connector['host']
short_host_name = self.utils.get_host_short_name(host)
short_host_name = self.utils.get_host_name_label(
host, self.powermax_short_host_name_template) if host else None
extra_specs = self._initial_setup(volume)
rep_extra_specs = self._get_replication_extra_specs(
extra_specs, self.rep_config)
@ -3443,8 +3492,12 @@ class PowerMaxCommon(object):
{'volume_name': device_id})
return False, None
target_sg_name, __, __, __ = self.utils.get_child_sg_name(
attached_host, target_extra_specs)
port_group_label = self.utils.get_port_name_label(
target_extra_specs[utils.PORTGROUPNAME],
self.powermax_port_group_name_template)
target_sg_name, __, __ = self.utils.get_child_sg_name(
attached_host, target_extra_specs, port_group_label)
target_sg = self.rest.get_storage_group(array, target_sg_name)
if not target_sg:

View File

@ -117,6 +117,8 @@ class PowerMaxFCDriver(san.SanDriver, driver.FibreChannelDriver):
- Volume/Snapshot backed metadata inclusion
- Debug metadata compression and service level info fix
4.2.0 - Support of Unisphere storage group and array tags
- User defined override for short host name and port group name
(bp powermax-user-defined-hostname-portgroup)
"""
VERSION = "4.2.0"
@ -332,7 +334,8 @@ class PowerMaxFCDriver(san.SanDriver, driver.FibreChannelDriver):
"""
loc = volume.provider_location
name = ast.literal_eval(loc)
host = self.common.utils.get_host_short_name(connector['host'])
host_label = self.common.utils.get_host_name_label(
connector['host'], self.common.powermax_short_host_name_template)
zoning_mappings = {}
try:
array = name['array']
@ -345,7 +348,14 @@ class PowerMaxFCDriver(san.SanDriver, driver.FibreChannelDriver):
masking_views, is_metro = (
self.common.get_masking_views_from_volume(
array, volume, device_id, host))
array, volume, device_id, host_label))
if not masking_views:
# Backward compatibility with pre Ussuri short host name.
host_label = self.common.utils.get_host_short_name(
connector['host'])
masking_views, is_metro = (
self.common.get_masking_views_from_volume(
array, volume, device_id, host_label))
if masking_views:
portgroup = (
self.common.get_port_group_from_masking_view(
@ -379,7 +389,7 @@ class PowerMaxFCDriver(san.SanDriver, driver.FibreChannelDriver):
else:
masking_views, __ = (
self.common.get_masking_views_from_volume(
metro_array, volume, metro_device_id, host))
metro_array, volume, metro_device_id, host_label))
if masking_views:
metro_portgroup = (
self.common.get_port_group_from_masking_view(

View File

@ -122,6 +122,8 @@ class PowerMaxISCSIDriver(san.SanISCSIDriver):
- Volume/Snapshot backed metadata inclusion
- Debug metadata compression and service level info fix
4.2.0 - Support of Unisphere storage group and array tags
- User defined override for short host name and port group name
(bp powermax-user-defined-hostname-portgroup)
"""
VERSION = "4.2.0"

View File

@ -240,8 +240,10 @@ class PowerMaxMasking(object):
storagegroup_name = masking_view_dict[utils.SG_NAME]
connector = masking_view_dict[utils.CONNECTOR]
port_group_name = masking_view_dict[utils.PORTGROUPNAME]
LOG.info("Port Group in masking view operation: %(port_group_name)s.",
{'port_group_name': port_group_name})
LOG.info("Port Group in masking view operation: %(pg_name)s. "
"The port group labels is %(pg_label)s.",
{'pg_name': masking_view_dict[utils.PORTGROUPNAME],
'pg_label': masking_view_dict[utils.PORT_GROUP_LABEL]})
init_group_name, error_message = (self._get_or_create_initiator_group(
serial_number, init_group_name, connector, extra_specs))
@ -1082,7 +1084,8 @@ class PowerMaxMasking(object):
@coordination.synchronized("emc-vol-{device_id}")
def remove_and_reset_members(
self, serial_number, volume, device_id, volume_name,
extra_specs, reset=True, connector=None, async_grp=None):
extra_specs, reset=True, connector=None, async_grp=None,
host_template=None):
"""This is called on a delete, unmap device or rollback.
:param serial_number: the array serial number
@ -1093,14 +1096,16 @@ class PowerMaxMasking(object):
:param reset: reset, return to original SG (optional)
:param connector: the connector object (optional)
:param async_grp: the async rep group (optional)
:param host_template: the host template (optional)
"""
self._cleanup_deletion(
serial_number, volume, device_id, volume_name,
extra_specs, connector, reset, async_grp)
extra_specs, connector, reset, async_grp,
host_template=host_template)
def _cleanup_deletion(
self, serial_number, volume, device_id, volume_name,
extra_specs, connector, reset, async_grp):
extra_specs, connector, reset, async_grp, host_template=None):
"""Prepare a volume for a delete operation.
:param serial_number: the array serial number
@ -1111,6 +1116,7 @@ class PowerMaxMasking(object):
:param connector: the connector object
:param reset: flag to indicate if reset is required -- bool
:param async_grp: the async rep group
:param host_template: the host template (if it exists)
"""
move = False
short_host_name = None
@ -1124,21 +1130,24 @@ class PowerMaxMasking(object):
if len(storagegroup_names) == 1 and reset is True:
move = True
elif connector is not None:
short_host_name = self.utils.get_host_short_name(
connector['host'])
short_host_name = self.utils.get_host_name_label(
connector.get('host'),
host_template) if connector.get('host') else None
move = reset
if short_host_name:
for sg_name in storagegroup_names:
if short_host_name in sg_name:
self.remove_volume_from_sg(
serial_number, device_id, volume_name, sg_name,
extra_specs, connector, move)
extra_specs, connector, move,
host_template=host_template)
break
else:
for sg_name in storagegroup_names:
self.remove_volume_from_sg(
serial_number, device_id, volume_name, sg_name,
extra_specs, connector, move)
extra_specs, connector, move,
host_template=host_template)
if reset is True and move is False:
self.add_volume_to_default_storage_group(
serial_number, device_id, volume_name,
@ -1146,7 +1155,7 @@ class PowerMaxMasking(object):
def remove_volume_from_sg(
self, serial_number, device_id, vol_name, storagegroup_name,
extra_specs, connector=None, move=False):
extra_specs, connector=None, move=False, host_template=None):
"""Remove a volume from a storage group.
:param serial_number: the array serial number
@ -1156,6 +1165,7 @@ class PowerMaxMasking(object):
:param extra_specs: the extra specifications
:param connector: the connector object
:param move: flag to indicate if move should be used instead of remove
:param host_template: the host template (if it exists)
"""
masking_list = self.rest.get_masking_views_from_storage_group(
serial_number, storagegroup_name)
@ -1179,7 +1189,7 @@ class PowerMaxMasking(object):
# Last volume in the storage group - delete sg.
self._last_vol_in_sg(
serial_number, device_id, vol_name, sg_name,
extra_specs, move)
extra_specs, move, host_template=host_template)
else:
# Not the last volume so remove it from storage group
self._multiple_vols_in_sg(
@ -1221,7 +1231,8 @@ class PowerMaxMasking(object):
# Last volume in the storage group - delete sg.
self._last_vol_in_sg(
serial_number, device_id, vol_name, sg_name,
extra_specs, move, connector)
extra_specs, move, connector,
host_template=host_template)
else:
# Not the last volume so remove it from storage group
self._multiple_vols_in_sg(
@ -1235,8 +1246,9 @@ class PowerMaxMasking(object):
return do_remove_volume_from_sg(masking_name, storagegroup_name,
parent_sg_name, serial_number)
def _last_vol_in_sg(self, serial_number, device_id, volume_name,
storagegroup_name, extra_specs, move, connector=None):
def _last_vol_in_sg(
self, serial_number, device_id, volume_name, storagegroup_name,
extra_specs, move, connector=None, host_template=None):
"""Steps if the volume is the last in a storage group.
1. Check if the volume is in a masking view.
@ -1255,6 +1267,7 @@ class PowerMaxMasking(object):
:param extra_specs: extra specifications
:param move: flag to indicate a move instead of remove
:param connector: the connector object
:param host_template: the host template (if it exists)
:returns: status -- bool
"""
LOG.debug("Only one volume remains in storage group "
@ -1269,7 +1282,8 @@ class PowerMaxMasking(object):
else:
status = self._last_vol_masking_views(
serial_number, storagegroup_name, maskingview_list,
device_id, volume_name, extra_specs, connector, move)
device_id, volume_name, extra_specs, connector, move,
host_template)
return status
def _last_vol_no_masking_views(self, serial_number, storagegroup_name,
@ -1314,7 +1328,8 @@ class PowerMaxMasking(object):
def _last_vol_masking_views(
self, serial_number, storagegroup_name, maskingview_list,
device_id, volume_name, extra_specs, connector, move):
device_id, volume_name, extra_specs, connector, move,
host_template=None):
"""Remove the last vol from an sg associated with masking views.
Helper function for removing the last vol from a storage group
@ -1326,6 +1341,7 @@ class PowerMaxMasking(object):
:param volume_name: the volume name
:param extra_specs: the extra specifications
:param move: flag to indicate a move instead of remove
:param host_template: the host template (if it exists)
:returns: status -- bool
"""
status = False
@ -1336,7 +1352,8 @@ class PowerMaxMasking(object):
if num_vols_in_mv == 1:
self._delete_mv_ig_and_sg(
serial_number, device_id, mv, storagegroup_name,
parent_sg_name, connector, move, extra_specs)
parent_sg_name, connector, move, extra_specs,
host_template=host_template)
else:
self._remove_last_vol_and_delete_sg(
serial_number, device_id, volume_name,
@ -1435,7 +1452,7 @@ class PowerMaxMasking(object):
def _delete_mv_ig_and_sg(
self, serial_number, device_id, masking_view, storagegroup_name,
parent_sg_name, connector, move, extra_specs):
parent_sg_name, connector, move, extra_specs, host_template=None):
"""Delete the masking view, storage groups and initiator group.
:param serial_number: array serial number
@ -1446,15 +1463,15 @@ class PowerMaxMasking(object):
:param connector: the connector object
:param move: flag to indicate if the volume should be moved
:param extra_specs: the extra specifications
:param host_template: the host template (if it exists)
"""
host = (self.utils.get_host_short_name(connector['host'])
if connector else None)
initiatorgroup = self.rest.get_element_from_masking_view(
serial_number, masking_view, host=True)
self._last_volume_delete_masking_view(serial_number, masking_view)
self._last_volume_delete_initiator_group(
serial_number, initiatorgroup, host)
serial_number, initiatorgroup,
connector.get('host') if connector else None,
host_template)
self._delete_cascaded_storage_groups(
serial_number, storagegroup_name, parent_sg_name,
extra_specs, device_id, move)
@ -1643,7 +1660,8 @@ class PowerMaxMasking(object):
self.rest.delete_storage_group(serial_number, storagegroup_name)
def _last_volume_delete_initiator_group(
self, serial_number, initiatorgroup_name, host):
self, serial_number, initiatorgroup_name, host,
host_template=None):
"""Delete the initiator group.
Delete the Initiator group if it has been created by the PowerMax
@ -1651,40 +1669,58 @@ class PowerMaxMasking(object):
:param serial_number: the array serial number
:param initiatorgroup_name: initiator group name
:param host: the short name of the host
:param host_template: the host template (if it exists)
"""
def _do_delete_initiator_group(array, init_group_name):
is_deleted = False
maskingview_names = (
self.rest.get_masking_views_by_initiator_group(
array, init_group_name))
if not maskingview_names:
@coordination.synchronized(
"emc-ig-{ig_name}-{array}")
def _delete_ig(ig_name, array):
# Check initiator group hasn't been recently deleted
ig_details = self.rest.get_initiator_group(
serial_number, ig_name)
if ig_details:
LOG.debug(
"Last volume associated with the initiator "
"group - deleting the associated initiator "
"group %(initiatorgroup_name)s.",
{'initiatorgroup_name': ig_name})
self.rest.delete_initiator_group(
array, ig_name)
return True
else:
return False
is_deleted = _delete_ig(init_group_name, array)
else:
LOG.warning("Initiator group %(ig_name)s is associated "
"with masking views and can't be deleted. "
"Number of associated masking view is: "
"%(nmv)d.",
{'ig_name': init_group_name,
'nmv': len(maskingview_names)})
return is_deleted
if host is not None:
protocol = self.utils.get_short_protocol_type(self.protocol)
default_ig_name = ("OS-%(shortHostName)s-%(protocol)s-IG"
% {'shortHostName': host,
'protocol': protocol})
host_label = (self.utils.get_host_name_label(
host, host_template) if host else None)
default_ig_name = self.utils.get_possible_initiator_name(
host_label, self.protocol)
if initiatorgroup_name == default_ig_name:
maskingview_names = (
self.rest.get_masking_views_by_initiator_group(
serial_number, initiatorgroup_name))
if not maskingview_names:
@coordination.synchronized(
"emc-ig-{ig_name}-{serial_number}")
def _delete_ig(ig_name, serial_number):
# Check initiator group hasn't been recently deleted
ig_details = self.rest.get_initiator_group(
serial_number, ig_name)
if ig_details:
LOG.debug(
"Last volume associated with the initiator "
"group - deleting the associated initiator "
"group %(initiatorgroup_name)s.",
{'initiatorgroup_name': initiatorgroup_name})
self.rest.delete_initiator_group(
serial_number, initiatorgroup_name)
_delete_ig(initiatorgroup_name, serial_number)
else:
LOG.warning("Initiator group %(ig_name)s is associated "
"with masking views and can't be deleted. "
"Number of associated masking view is: "
"%(nmv)d.",
{'ig_name': initiatorgroup_name,
'nmv': len(maskingview_names)})
is_deleted = _do_delete_initiator_group(
serial_number, initiatorgroup_name)
if not is_deleted:
host_label = (self.utils.get_host_short_name(
host) if host else None)
default_ig_name = self.utils.get_possible_initiator_name(
host_label, self.protocol)
if initiatorgroup_name == default_ig_name:
_do_delete_initiator_group(
serial_number, initiatorgroup_name)
else:
LOG.warning("Initiator group %(ig_name)s was "
"not created by the PowerMax driver so will "
@ -1715,19 +1751,13 @@ class PowerMaxMasking(object):
for sg in sg_list.get('storageGroupId', []):
if slo_wl_combo in sg:
fast_source_sg_name = sg
masking_view_name = (
self.rest.get_masking_views_from_storage_group(
serial_number, fast_source_sg_name))[0]
port_group_name = self.rest.get_element_from_masking_view(
serial_number, masking_view_name, portgroup=True)
short_pg_name = self.utils.get_pg_short_name(port_group_name)
short_host_name = masking_view_name.lstrip('OS-').rstrip(
'-%s-MV' % short_pg_name)[:-2]
extra_specs[utils.PORTGROUPNAME] = short_pg_name
short_host_name, port_group_label = (
self._get_host_and_port_group_labels(
serial_number, fast_source_sg_name))
no_slo_extra_specs = deepcopy(extra_specs)
no_slo_extra_specs[utils.SLO] = None
no_slo_sg_name, __, __, __ = self.utils.get_child_sg_name(
short_host_name, no_slo_extra_specs)
no_slo_sg_name, __, __ = self.utils.get_child_sg_name(
short_host_name, no_slo_extra_specs, port_group_label)
source_sg_details = self.rest.get_storage_group(
serial_number, fast_source_sg_name)
parent_sg_name = source_sg_details[
@ -1763,6 +1793,34 @@ class PowerMaxMasking(object):
message=exception_message)
return mv_dict
def _get_host_and_port_group_labels(
self, serial_number, storage_group):
"""Get the host and port group labels
:param serial_number: the array serial number
:param storage_group: the storage group
:returns: short_host_name, port_group_label
"""
masking_view_name = (
self.rest.get_masking_views_from_storage_group(
serial_number, storage_group))[0]
object_dict = self.get_components_from_masking_view_name(
masking_view_name)
return object_dict['host'], object_dict['portgroup']
def get_components_from_masking_view_name(self, masking_view_name):
"""Get the host and port group labels
:param masking_view_name: the masking view name
:returns: object dict
"""
regex_str = (r'^(?P<prefix>OS)-(?P<host>.+?)(?P<protocol>I|F)-'
r'(?P<portgroup>(?!CD|RE|CD-RE).+)-(?P<postfix>MV)$')
object_dict = self.utils.get_object_components_and_correct_host(
regex_str, masking_view_name)
return object_dict
def return_volume_to_fast_managed_group(
self, serial_number, device_id, extra_specs):
"""Return a volume to a fast managed group if slo is set.
@ -1782,18 +1840,11 @@ class PowerMaxMasking(object):
for sg in sg_list.get('storageGroupId', []):
if slo_wl_combo in sg:
no_slo_sg_name = sg
masking_view_name = (
self.rest.get_masking_views_from_storage_group(
serial_number, no_slo_sg_name))[0]
port_group_name = self.rest.get_element_from_masking_view(
serial_number, masking_view_name, portgroup=True)
short_pg_name = self.utils.get_pg_short_name(
port_group_name)
short_host_name = masking_view_name.lstrip('OS-').rstrip(
'-%s-MV' % short_pg_name)[:-2]
extra_specs[utils.PORTGROUPNAME] = short_pg_name
fast_sg_name, _, _, _ = self.utils.get_child_sg_name(
short_host_name, extra_specs)
short_host_name, port_group_label = (
self._get_host_and_port_group_labels(
serial_number, no_slo_sg_name))
fast_sg_name, _, _ = self.utils.get_child_sg_name(
short_host_name, extra_specs, port_group_label)
source_sg_details = self.rest.get_storage_group(
serial_number, no_slo_sg_name)
parent_sg_name = source_sg_details[
@ -1863,20 +1914,24 @@ class PowerMaxMasking(object):
self.rest.delete_storage_group(
serial_number, child_sg_name)
def attempt_ig_cleanup(self, connector, protocol, serial_number, force):
def attempt_ig_cleanup(
self, connector, protocol, serial_number, force,
host_template=None):
"""Attempt to cleanup an orphan initiator group
:param connector: connector object
:param protocol: iscsi or fc
:param serial_number: extra the array serial number
:param force: flag to indicate if operation should be forced
:param host_template: the host template (if it exists)
"""
protocol = self.utils.get_short_protocol_type(protocol)
host_name = connector['host']
short_host_name = self.utils.get_host_short_name(host_name)
init_group = (
("OS-%(shortHostName)s-%(protocol)s-IG"
% {'shortHostName': short_host_name,
'protocol': protocol}))
host_name = connector.get('host')
host_label = self.utils.get_host_name_label(
host_name, host_template=host_template)
initiator_group_name = self.utils.get_possible_initiator_name(
host_label, protocol)
self._check_ig_rollback(
serial_number, init_group, connector, force)
serial_number, initiator_group_name, connector, force)

View File

@ -319,7 +319,8 @@ class PowerMaxVolumeMetadata(object):
parent_storage_group=parent_storage_group,
initiator_group=initiator_group,
port_group=port_group,
host=host, is_multipath=is_multipath,
host=host, used_host_name=masking_view_dict[utils.USED_HOST_NAME],
is_multipath=is_multipath,
identifier_name=self.utils.get_volume_element_name(volume.id),
openstack_name=volume.display_name,
mv_list=mv_list, sg_list=sg_list,

View File

@ -44,6 +44,8 @@ TRUNCATE_5 = 5
TRUNCATE_27 = 27
UCODE_5978_ELMSR = 221
UCODE_5978 = 5978
UPPER_HOST_CHARS = 16
UPPER_PORT_GROUP_CHARS = 12
ARRAY = 'array'
SLO = 'slo'
@ -80,6 +82,7 @@ DEFAULT_PORT = 8443
CLONE_SNAPSHOT_NAME = "snapshot_for_clone"
STORAGE_GROUP_TAGS = 'storagetype:storagegrouptags'
TAG_LIST = 'tag_list'
USED_HOST_NAME = "used_host_name"
# Multiattach constants
IS_MULTIATTACH = 'multiattach'
@ -112,6 +115,9 @@ POWERMAX_SERVICE_LEVEL = 'powermax_service_level'
POWERMAX_PORT_GROUPS = 'powermax_port_groups'
POWERMAX_SNAPVX_UNLINK_LIMIT = 'powermax_snapvx_unlink_limit'
POWERMAX_ARRAY_TAG_LIST = 'powermax_array_tag_list'
POWERMAX_SHORT_HOST_NAME_TEMPLATE = 'powermax_short_host_name_template'
POWERMAX_PORT_GROUP_NAME_TEMPLATE = 'powermax_port_group_name_template'
PORT_GROUP_LABEL = 'port_group_label'
class PowerMaxUtils(object):
@ -127,6 +133,20 @@ class PowerMaxUtils(object):
def get_host_short_name(self, host_name):
"""Returns the short name for a given qualified host name.
Checks the host name to see if it is the fully qualified host name
and returns part before the dot. If there is no dot in the host name
the full host name is returned.
:param host_name: the fully qualified host name
:returns: string -- the short host_name
"""
short_host_name = self.get_host_short_name_from_fqn(host_name)
return self.generate_unique_trunc_host(short_host_name)
@staticmethod
def get_host_short_name_from_fqn(host_name):
"""Returns the short name for a given qualified host name.
Checks the host name to see if it is the fully qualified host name
and returns part before the dot. If there is no dot in the host name
the full host name is returned.
@ -139,7 +159,7 @@ class PowerMaxUtils(object):
else:
short_host_name = host_name
return self.generate_unique_trunc_host(short_host_name)
return short_host_name
@staticmethod
def get_volumetype_extra_specs(volume, volume_type_id=None):
@ -286,15 +306,12 @@ class PowerMaxUtils(object):
:param host_name: long host name
:returns: truncated host name
"""
if host_name and len(host_name) > 16:
host_name = host_name.lower()
m = hashlib.md5()
m.update(host_name.encode('utf-8'))
uuid = m.hexdigest()
if host_name and len(host_name) > UPPER_HOST_CHARS:
uuid = self.get_uuid_of_input(host_name)
new_name = ("%(host)s%(uuid)s"
% {'host': host_name[-6:],
'uuid': uuid})
host_name = self.truncate_string(new_name, 16)
host_name = self.truncate_string(new_name, UPPER_HOST_CHARS)
return host_name
def get_pg_short_name(self, portgroup_name):
@ -303,17 +320,27 @@ class PowerMaxUtils(object):
:param portgroup_name: long portgroup_name
:returns: truncated portgroup_name
"""
if portgroup_name and len(portgroup_name) > 12:
portgroup_name = portgroup_name.lower()
m = hashlib.md5()
m.update(portgroup_name.encode('utf-8'))
uuid = m.hexdigest()
if portgroup_name and len(portgroup_name) > UPPER_PORT_GROUP_CHARS:
uuid = self.get_uuid_of_input(portgroup_name)
new_name = ("%(pg)s%(uuid)s"
% {'pg': portgroup_name[-6:],
'uuid': uuid})
portgroup_name = self.truncate_string(new_name, 12)
portgroup_name = self.truncate_string(
new_name, UPPER_PORT_GROUP_CHARS)
return portgroup_name
@staticmethod
def get_uuid_of_input(input_str):
"""Get the uuid of the input string
:param input_str: input string
:returns: uuid
"""
input_str = input_str.lower()
m = hashlib.md5()
m.update(input_str.encode('utf-8'))
return m.hexdigest()
@staticmethod
def get_default_oversubscription_ratio(max_over_sub_ratio):
"""Override ratio if necessary.
@ -732,6 +759,7 @@ class PowerMaxUtils(object):
"""Get the name of the default sg from the extra specs.
:param extra_specs: extra specs
:param rep_mode: replication mode
:returns: default sg - string
"""
do_disable_compression = self.is_compression_disabled(
@ -763,7 +791,7 @@ class PowerMaxUtils(object):
"""Get the temporary group name used for failover.
:param rep_config: the replication config
:return: temp_grp_name
:returns: temp_grp_name
"""
temp_grp_name = ("OS-%(rdf)s-temp-rdf-sg"
% {'rdf': rep_config['rdf_group_label']})
@ -771,15 +799,15 @@ class PowerMaxUtils(object):
{'name': temp_grp_name})
return temp_grp_name
def get_child_sg_name(self, host_name, extra_specs):
def get_child_sg_name(self, host_name, extra_specs, port_group_label):
"""Get the child storage group name for a masking view.
:param host_name: the short host name
:param extra_specs: the extra specifications
:return: child sg name, compression flag, rep flag, short pg name
:param port_group_label: the port group label
:returns: child sg name, compression flag, rep flag, short pg name
"""
do_disable_compression = False
pg_name = self.get_pg_short_name(extra_specs[PORTGROUPNAME])
rep_enabled = self.is_replication_enabled(extra_specs)
if extra_specs[SLO]:
slo_wl_combo = self.truncate_string(
@ -790,7 +818,7 @@ class PowerMaxUtils(object):
% {'shortHostName': host_name,
'srpName': unique_name,
'combo': slo_wl_combo,
'pg': pg_name})
'pg': port_group_label})
do_disable_compression = self.is_compression_disabled(
extra_specs)
if do_disable_compression:
@ -799,11 +827,11 @@ class PowerMaxUtils(object):
else:
child_sg_name = (
"OS-%(shortHostName)s-No_SLO-%(pg)s"
% {'shortHostName': host_name, 'pg': pg_name})
% {'shortHostName': host_name, 'pg': port_group_label})
if rep_enabled:
rep_mode = extra_specs.get(REP_MODE, None)
child_sg_name += self.get_replication_prefix(rep_mode)
return child_sg_name, do_disable_compression, rep_enabled, pg_name
return child_sg_name, do_disable_compression, rep_enabled
@staticmethod
def change_multiattach(extra_specs, new_type_extra_specs):
@ -811,7 +839,7 @@ class PowerMaxUtils(object):
:param extra_specs: the source type extra specs
:param new_type_extra_specs: the target type extra specs
:return: bool
:returns: bool
"""
is_src_multiattach = volume_utils.is_boolean_str(
extra_specs.get('multiattach'))
@ -824,7 +852,7 @@ class PowerMaxUtils(object):
"""Check if a volume with verbose description is valid for management.
:param source_vol: the verbose volume dict
:return: bool True/False
:returns: bool True/False
"""
vol_head = source_vol['volumeHeader']
@ -865,7 +893,7 @@ class PowerMaxUtils(object):
"""Check if a volume with snapshot description is valid for management.
:param source_vol: the verbose volume dict
:return: bool True/False
:returns: bool True/False
"""
vol_head = source_vol['volumeHeader']
@ -902,7 +930,7 @@ class PowerMaxUtils(object):
"""Parse a hostname from a storage group ID.
:param device_info: the device info dict
:return: str -- the attached hostname
:returns: str -- the attached hostname
"""
try:
sg_id = device_info.get("storageGroupId")[0]
@ -962,7 +990,7 @@ class PowerMaxUtils(object):
"""Compare number of cylinders of source and target.
:param cylinders_source: number of cylinders on source
:param cylinders_target: number of cylinders on target
:param cylinder_target: number of cylinders on target
"""
if float(cylinders_source) > float(cylinder_target):
exception_message = (
@ -1054,3 +1082,330 @@ class PowerMaxUtils(object):
"""
return ','.join(map(str, list_input)) if isinstance(
list_input, list) else list_input
def validate_short_host_name_from_template(
self, short_host_template, short_host_name):
"""Validate that the short host name is in a format we can use.
Can be one of
shortHostName - where shortHostName is what the driver specifies
it to be, default
shortHostName[:x]uuid[:x] - where first x characters of the short
host name and x uuid characters created from md5 hash of
short host name
shortHostName[:x]userdef - where first x characters of the short
host name and a user defined name
shortHostName[-x:]uuid[:x] - where last x characters of short host
name and x uuid characters created from md5 hash of short host
name
shortHostName[-x:]suserdef - where last x characters of the short
host name and a user defined name
:param short_host_template: short host name template
:param short_host_name: short host name
:raises: VolumeBackendAPIException
:returns: new short host name -- string
"""
new_short_host_name = None
is_ok, case = self.regex_check(short_host_template, True)
if is_ok:
new_short_host_name = (
self.generate_entity_string(
case, short_host_template, short_host_name, True))
if not new_short_host_name:
error_message = (_('Unable to generate string from short '
'host template %(template)s. Please refer to '
'the online documentation for correct '
'template format(s) for short host name.') %
{'template': short_host_template})
LOG.error(error_message)
raise exception.VolumeBackendAPIException(
message=error_message)
return new_short_host_name
def validate_port_group_name_from_template(
self, port_group_template, port_group_name):
"""Validate that the port group name is in a format we can use.
Can be one of
portGroupName - where portGroupName is what the driver specifies
it to be, default
portGroupName[:x]uuid[:x] - where first x characters of the short
host name and x uuid characters created from md5 hash of
short host name
portGroupName[:x]userdef - where first x characters of the short
host name and a user defined name
portGroupName[-x:]uuid[:x] - where last x characters of short host
name and x uuid characters created from md5 hash of short host
name
portGroupName[-x:]userdef - where last x characters of the short
host name and a user defined name
:param port_group_template: port group name template
:param port_group_name: port group name
:raises: VolumeBackendAPIException
:returns: new port group name -- string
"""
new_port_group_name = None
is_ok, case = self.regex_check(port_group_template, False)
if is_ok:
new_port_group_name = (
self.generate_entity_string(
case, port_group_template, port_group_name, False))
if not new_port_group_name:
error_message = (_('Unable to generate string from port group '
'template %(template)s. Please refer to '
'the online documentation for correct '
'template format(s) for port groups.') %
{'template': port_group_template})
LOG.error(error_message)
raise exception.VolumeBackendAPIException(
message=error_message)
return new_port_group_name
def generate_entity_string(
self, case, entity_template, entity_name, entity_flag):
"""Generate the entity string if the template checks out
:param case: one of five cases
:param entity_template: entity template
:param entity_name: entity name
:param entity_flag: storage group or port group flag
:returns: new entity name -- string
"""
new_entity_name = None
override_rule_warning = False
try:
if case == '1':
new_entity_name = self.get_name_if_default_template(
entity_name, entity_flag)
elif case == '2':
pass_two, uuid = self.prepare_string_with_uuid(
entity_template, entity_name, entity_flag)
m = re.match(r'^' + entity_name +
r'\[:(\d+)\]' + uuid + r'\[:(\d+)\]$', pass_two)
if m:
num_1 = m.group(1)
num_2 = m.group(2)
self.check_upper_limit(
int(num_1), int(num_2), entity_flag)
new_entity_name = (
entity_name[:int(num_1)] + uuid[:int(num_2)])
override_rule_warning = True
elif case == '3':
pass_two, uuid = self.prepare_string_with_uuid(
entity_template, entity_name, entity_flag)
m = re.match(r'^' + entity_name +
r'\[-(\d+):\]' + uuid + r'\[:(\d+)\]$', pass_two)
if m:
num_1 = m.group(1)
num_2 = m.group(2)
self.check_upper_limit(
int(num_1), int(num_2), entity_flag)
new_entity_name = (
entity_name[-int(num_1):] + uuid[:int(num_2)])
override_rule_warning = True
elif case == '4':
pass_two = self.prepare_string_entity(
entity_template, entity_name, entity_flag)
m = re.match(r'^' + entity_name +
r'\[:(\d+)\]' + r'([a-zA-Z0-9_\\-]+)$', pass_two)
if m:
num_1 = m.group(1)
user_defined = m.group(2)
self.check_upper_limit(
int(num_1), len(user_defined), entity_flag)
new_entity_name = entity_name[:int(num_1)] + user_defined
override_rule_warning = True
elif case == '5':
pass_two = self.prepare_string_entity(
entity_template, entity_name, entity_flag)
m = re.match(r'^' + entity_name +
r'\[-(\d+):\]' + r'([a-zA-Z0-9_\\-]+)$', pass_two)
if m:
num_1 = m.group(1)
user_defined = m.group(2)
self.check_upper_limit(
int(num_1), len(user_defined), entity_flag)
new_entity_name = entity_name[-int(num_1):] + user_defined
override_rule_warning = True
if override_rule_warning:
LOG.warning(
"You have opted to override the %(entity)s naming format. "
"Once changed and you have attached volumes or created "
"new instances, you cannot revert to default or change to "
"another format.",
{'entity': 'storage group'
if entity_flag else 'port group'})
except Exception:
new_entity_name = None
return new_entity_name
def get_name_if_default_template(self, entity_name, is_short_host_flag):
"""Get the entity name if it is the default template
:param entity_name: the first number
:param is_short_host_flag: the second number
:returns: entity name -- string
"""
if is_short_host_flag:
return self.get_host_short_name(entity_name)
else:
return self.get_pg_short_name(entity_name)
@staticmethod
def check_upper_limit(num_1, num_2, is_host_flag):
"""Check that the sum of number is less than upper limit.
:param num_1: the first number
:param num_2: the second number
:param is_host_flag: is short host boolean
:raises: VolumeBackendAPIException
"""
if is_host_flag:
if (num_1 + num_2) > UPPER_HOST_CHARS:
error_message = (_("Host name exceeds the character upper "
"limit of %(upper)d. Please check your "
"short host template.") %
{'upper': UPPER_HOST_CHARS})
LOG.error(error_message)
raise exception.VolumeBackendAPIException(
message=error_message)
else:
if (num_1 + num_2) > UPPER_PORT_GROUP_CHARS:
error_message = (_("Port group name exceeds the character "
"upper limit of %(upper)d. Please check "
"your port group template") %
{'upper': UPPER_PORT_GROUP_CHARS})
LOG.error(error_message)
raise exception.VolumeBackendAPIException(
message=error_message)
def prepare_string_with_uuid(
self, template, entity_str, is_short_host_flag):
"""Prepare string for pass three
:param template: the template
:param entity_str: the entity string
:param is_short_host_flag: is short host
:returns: pass_two -- string
uuid -- string
"""
pass_one = self.prepare_string_entity(
template, entity_str, is_short_host_flag)
uuid = self.get_uuid_of_input(entity_str)
pass_two = pass_one.replace('uuid', uuid)
return pass_two, uuid
@staticmethod
def prepare_string_entity(template, entity_str, is_host_flag):
"""Prepare string for pass two
:param template: the template
:param entity_str: the entity string
:param is_host_flag: is host boolean
:returns: pass_one -- string
"""
entity_type = 'shortHostName' if is_host_flag else 'portGroupName'
# Replace entity type with variable
return template.replace(
entity_type, entity_str)
@staticmethod
def regex_check(template, is_short_host_flag):
"""Check the template is in a validate format.
:param template: short host name template
:param is_short_host_flag: short host boolean
:returns: boolean,
case -- string
"""
if is_short_host_flag:
entity = 'shortHostName'
else:
entity = 'portGroupName'
if re.match(r'^' + entity + r'$', template):
return True, '1'
elif re.match(r'^' + entity + r'\[:\d+\]uuid\[:\d+\]$', template):
return True, '2'
elif re.match(r'^' + entity + r'\[-\d+:\]uuid\[:\d+\]$', template):
return True, '3'
elif re.match(r'^' + entity + r'\[:\d+\][a-zA-Z0-9_\\-]+$', template):
return True, '4'
elif re.match(r'^' + entity + r'\[-\d+:\][a-zA-Z0-9_\\-]+$',
template):
return True, '5'
return False, '0'
def get_host_name_label(self, host_name_in, host_template):
"""Get the host name label that will be used in PowerMax Objects
:param host_name_in: host name as portrayed in connector object
:param host_template:
:returns: host_name_out
"""
host_name_out = self.get_host_short_name(
host_name_in)
if host_template:
short_host_name = self.get_host_short_name_from_fqn(
host_name_in)
host_name_out = (
self.validate_short_host_name_from_template(
host_template, short_host_name))
return host_name_out
def get_port_name_label(self, port_name_in, port_group_template):
"""Get the port name label that will be used in PowerMax Objects
:rtype: object
:param host_name_in: host name as portrayed in connector object
:param port_group_template: port group template
:returns: port_name_out
"""
port_name_out = self.get_pg_short_name(port_name_in)
if port_group_template:
port_name_out = (
self.validate_port_group_name_from_template(
port_group_template, port_name_in))
return port_name_out
@staticmethod
def get_object_components(regex_str, input_str):
"""Get components from input string.
:param regex_str: the regex -- str
:param input_str: the input string -- str
:returns: dict
"""
full_str = re.compile(regex_str)
match = full_str.match(input_str)
return match.groupdict() if match else None
def get_object_components_and_correct_host(self, regex_str, input_str):
"""Get components from input string.
:param regex_str: the regex -- str
:param input_str: the input string -- str
:returns: object components -- dict
"""
object_dict = self.get_object_components(regex_str, input_str)
if object_dict and 'host' in object_dict:
if object_dict['host'].endswith('-'):
object_dict['host'] = object_dict['host'][:-1]
return object_dict
def get_possible_initiator_name(self, host_label, protocol):
"""Get possible initiator name based on the host
:param host_label: the host label -- str
:param protocol: the protocol -- str
:returns: initiator_group_name -- str
"""
protocol = self.get_short_protocol_type(protocol)
return ("OS-%(shortHostName)s-%(protocol)s-IG"
% {'shortHostName': host_label,
'protocol': protocol})

View File

@ -0,0 +1,9 @@
---
features:
- |
Dell EMC PowerMax driver now faciliates the user to override the
short host name and port group name seen in PowerMax masking view
and storage view terminology. This means the user can give more
meaningful names, especially when the short host name exceeds 16
characters and the port group name exceeds 12 characters, which
is the condition where the driver truncates these values.