Huawei: Raise if no FC port found

Currently we not handle the case that no FC port
can be used when initialize_connection(). Now we
will raise if no FC port can be used.

Closes-Bug: #1560796
Change-Id: Ic57cf1ba6b86c8aa3fcdaedb0cd31366c87cb9ca
This commit is contained in:
Wilson Liu 2016-03-23 12:52:04 +08:00
parent 2d7d81df5d
commit e084a38d81
3 changed files with 239 additions and 34 deletions

View File

@ -247,6 +247,20 @@ fake_fabric_mapping = {
}
}
fake_fabric_mapping_no_ports = {
'swd1': {
'target_port_wwn_list': [],
'initiator_port_wwn_list': ['10000090fa0d6754']
}
}
fake_fabric_mapping_no_wwn = {
'swd1': {
'target_port_wwn_list': ['2000643e8c4c5f66'],
'initiator_port_wwn_list': []
}
}
CHANGE_OPTS = {'policy': ('1', '2'),
'partitionid': (['1', 'partition001'], ['2', 'partition002']),
'cacheid': (['1', 'cache001'], ['2', 'cache002']),
@ -269,6 +283,17 @@ FAKE_CREATE_HOST_RESPONSE = """
}
"""
FAKE_GET_HOST_RESPONSE = """
{
"error": {
"code": 0
},
"data":{"NAME": "ubuntuc001",
"ID": "1",
"ISADD2HOSTGROUP": "true"}
}
"""
# A fake response of success response storage
FAKE_COMMON_SUCCESS_RESPONSE = """
{
@ -1025,7 +1050,7 @@ FAKE_GET_FC_PORT_RESPONSE = """
},
{
"RUNNINGSTATUS":"10",
"WWN":"2009643e8c4c5f67",
"WWN":"2000643e8c4c5f67",
"PARENTID":"0A.1",
"ID": "1114369",
"RUNSPEED": "16000"
@ -1232,6 +1257,10 @@ MAP_COMMAND_TO_FAKE_RESPONSE['/iscsi_initiator/POST'] = (
MAP_COMMAND_TO_FAKE_RESPONSE['/iscsi_initiator/PUT'] = (
FAKE_ISCSI_INITIATOR_RESPONSE)
MAP_COMMAND_TO_FAKE_RESPONSE['/iscsi_initiator?PARENTTYPE=21&PARENTID'
'=1/GET'] = (
FAKE_ISCSI_INITIATOR_RESPONSE)
MAP_COMMAND_TO_FAKE_RESPONSE['/iscsi_initiator/remove_iscsi_from_host/PUT'] = (
FAKE_COMMON_SUCCESS_RESPONSE)
@ -1245,6 +1274,9 @@ MAP_COMMAND_TO_FAKE_RESPONSE['/host?range=[0-65535]/GET'] = (
MAP_COMMAND_TO_FAKE_RESPONSE['/host/1/DELETE'] = (
FAKE_COMMON_SUCCESS_RESPONSE)
MAP_COMMAND_TO_FAKE_RESPONSE['/host/1/GET'] = (
FAKE_GET_HOST_RESPONSE)
MAP_COMMAND_TO_FAKE_RESPONSE['/host'] = (
FAKE_CREATE_HOST_RESPONSE)
@ -1307,6 +1339,9 @@ MAP_COMMAND_TO_FAKE_RESPONSE['/MAPPINGVIEW/1/GET'] = (
MAP_COMMAND_TO_FAKE_RESPONSE['/mappingview/1/DELETE'] = (
FAKE_COMMON_SUCCESS_RESPONSE)
MAP_COMMAND_TO_FAKE_RESPONSE['/mappingview/REMOVE_ASSOCIATE/PUT'] = (
FAKE_COMMON_SUCCESS_RESPONSE)
MAP_COMMAND_TO_FAKE_RESPONSE['/mappingview/associate/lungroup?TYPE=256&'
'ASSOCIATEOBJTYPE=245&ASSOCIATEOBJID=1/GET'] = (
FAKE_GET_MAPPING_VIEW_RESPONSE)
@ -1319,10 +1354,29 @@ MAP_COMMAND_TO_FAKE_RESPONSE['/mappingview/associate?TYPE=245&'
'ASSOCIATEOBJTYPE=256&ASSOCIATEOBJID=11/GET'] = (
FAKE_GET_MAPPING_VIEW_RESPONSE)
MAP_COMMAND_TO_FAKE_RESPONSE['/mappingview/associate?TYPE=245&'
'ASSOCIATEOBJTYPE=257&ASSOCIATEOBJID=0/GET'] = (
FAKE_GET_MAPPING_VIEW_RESPONSE)
MAP_COMMAND_TO_FAKE_RESPONSE['/mappingview/associate?TYPE=245&'
'ASSOCIATEOBJTYPE=257&ASSOCIATEOBJID=11/GET'] = (
FAKE_GET_MAPPING_VIEW_RESPONSE)
FAKE_GET_ENGINES_RESPONSE = """
{
"error":{
"code": 0
},
"data":[{
"NODELIST": "[]",
"ID": "0"
}]
}
"""
MAP_COMMAND_TO_FAKE_RESPONSE['/storageengine/GET'] = (
FAKE_GET_ENGINES_RESPONSE)
MAP_COMMAND_TO_FAKE_RESPONSE['/portgroup/associate?ASSOCIATEOBJTYPE=245&'
'ASSOCIATEOBJID=1&range=[0-8191]/GET'] = (
FAKE_GET_MAPPING_VIEW_RESPONSE)
@ -1498,6 +1552,8 @@ FAKE_CREATE_PORTG = """
MAP_COMMAND_TO_FAKE_RESPONSE['/PortGroup/POST'] = FAKE_CREATE_PORTG
MAP_COMMAND_TO_FAKE_RESPONSE['/PortGroup/1/DELETE'] = (
FAKE_COMMON_SUCCESS_RESPONSE)
FAKE_GET_PORTG_FROM_PORT = """
{
@ -1743,6 +1799,39 @@ FAKE_SWITCH_PAIR_RESPONSE = """
MAP_COMMAND_TO_FAKE_RESPONSE['/REPLICATIONPAIR/switch/PUT'] = (
FAKE_SWITCH_PAIR_RESPONSE)
FAKE_PORTS_IN_PG_RESPONSE = """
{
"data": [{
"ID": "1114114",
"WWN": "2002643e8c4c5f66"
},
{
"ID": "1114113",
"WWN": "2001643e8c4c5f66"
}],
"error": {
"code": 0,
"description": "0"
}
}
"""
MAP_COMMAND_TO_FAKE_RESPONSE['/fc_port/associate?TYPE=213&ASSOCIATEOBJTYPE='
'257&ASSOCIATEOBJID=0/GET'] = (
FAKE_PORTS_IN_PG_RESPONSE)
MAP_COMMAND_TO_FAKE_RESPONSE['/portgroup/associate/fc_port?TYPE=257&ASSOCIA'
'TEOBJTYPE=212&ASSOCIATEOBJID=1114369/GET'] = (
FAKE_PORTS_IN_PG_RESPONSE)
MAP_COMMAND_TO_FAKE_RESPONSE['/mappingview/associate/portgroup?TYPE=245&ASSOC'
'IATEOBJTYPE=257&ASSOCIATEOBJID=1114114/GET'] = (
FAKE_SWITCH_PAIR_RESPONSE)
MAP_COMMAND_TO_FAKE_RESPONSE['/mappingview/associate/portgroup?TYPE=245&ASSOC'
'IATEOBJTYPE=257&ASSOCIATEOBJID=1114113/GET'] = (
FAKE_COMMON_SUCCESS_RESPONSE)
def Fake_sleep(time):
pass
@ -3342,6 +3431,7 @@ class FCSanLookupService(object):
return fake_fabric_mapping
@ddt.ddt
class HuaweiFCDriverTestCase(test.TestCase):
def setUp(self):
@ -3391,6 +3481,27 @@ class HuaweiFCDriverTestCase(test.TestCase):
FakeConnector)
self.assertEqual(1, iscsi_properties['data']['target_lun'])
def test_initialize_connection_fail_no_online_wwns_in_host(self):
self.mock_object(rest_client.RestClient, 'get_online_free_wwns',
mock.Mock(return_value=[]))
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.initialize_connection,
test_volume, FakeConnector)
def test_initialize_connection_no_local_ini_tgt_map(self):
self.mock_object(rest_client.RestClient, 'get_init_targ_map',
mock.Mock(return_value=('', '')))
self.mock_object(huawei_driver.HuaweiFCDriver, '_get_same_hostid',
mock.Mock(return_value=''))
self.mock_object(rest_client.RestClient, 'change_hostlun_id',
mock.Mock(return_value=None))
self.mock_object(rest_client.RestClient, 'do_mapping',
mock.Mock(return_value={'lun_id': '1',
'view_id': '1',
'aval_luns': '[1]'}))
self.driver.initialize_connection(hyper_volume, FakeConnector)
def test_hypermetro_connection_success(self):
self.mock_object(rest_client.RestClient, 'find_array_version',
mock.Mock(return_value='V300R003C00'))
@ -3403,6 +3514,21 @@ class HuaweiFCDriverTestCase(test.TestCase):
self.driver.terminate_connection(test_volume, FakeConnector)
self.assertTrue(self.driver.client.terminateFlag)
def test_terminate_connection_portgroup_associated(self):
self.mock_object(rest_client.RestClient,
'is_portgroup_associated_to_view',
mock.Mock(return_value=True))
self.mock_object(huawei_driver.HuaweiFCDriver,
'_delete_zone_and_remove_fc_initiators',
mock.Mock(return_value=({}, 1)))
self.driver.terminate_connection(test_volume, FakeConnector)
def test_terminate_connection_fc_initiators_exist_in_host(self):
self.mock_object(rest_client.RestClient,
'check_fc_initiators_exist_in_host',
mock.Mock(return_value=True))
self.driver.terminate_connection(test_volume, FakeConnector)
def test_terminate_connection_hypermetro_in_metadata(self):
self.driver.terminate_connection(hyper_volume, FakeConnector)
@ -3664,18 +3790,32 @@ class HuaweiFCDriverTestCase(test.TestCase):
self.assertEqual(target_port_wwns, tgt_wwns)
self.assertEqual({}, init_targ_map)
@ddt.data(fake_fabric_mapping_no_ports, fake_fabric_mapping_no_wwn)
def test_filter_by_fabric_fail(self, ddt_map):
self.mock_object(
FCSanLookupService, 'get_device_mapping_from_network',
mock.Mock(return_value=ddt_map))
fake_lookup_service = FCSanLookupService()
zone_helper = fc_zone_helper.FCZoneHelper(
fake_lookup_service, self.driver.client)
self.assertRaises(exception.VolumeBackendAPIException,
zone_helper._filter_by_fabric, ['10000090fa0d6754'],
None)
@mock.patch.object(rest_client.RestClient, 'get_all_engines',
return_value=[{'NODELIST': '["0A"]', 'ID': '0'},
{'NODELIST': '["0B"]', 'ID': '1'}])
def test_build_ini_targ_map_engie_not_recorded(self, mock_engines):
@mock.patch.object(fc_zone_helper.FCZoneHelper, '_build_contr_port_map',
return_value={'0B': ['2000643e8c4c5f67']})
def test_build_ini_targ_map_engie_not_recorded(self, mock_engines, map):
fake_lookup_service = FCSanLookupService()
zone_helper = fc_zone_helper.FCZoneHelper(
fake_lookup_service, self.driver.client)
(tgt_wwns, portg_id, init_targ_map) = zone_helper.build_ini_targ_map(
['10000090fa0d6754'], '1', '11')
expected_wwns = ['2000643e8c4c5f66']
expected_map = {'10000090fa0d6754': ['2000643e8c4c5f66']}
expected_wwns = ['2000643e8c4c5f67', '2000643e8c4c5f66']
expected_map = {'10000090fa0d6754': expected_wwns}
self.assertEqual(expected_wwns, tgt_wwns)
self.assertEqual(expected_map, init_targ_map)
@ -3694,6 +3834,25 @@ class HuaweiFCDriverTestCase(test.TestCase):
self.assertEqual(expected_wwns, tgt_wwns)
self.assertEqual(expected_map, init_targ_map)
@mock.patch.object(rest_client.RestClient, 'get_all_engines',
return_value=[{'NODELIST': '["0A", "0B"]', 'ID': '0'}])
@mock.patch.object(rest_client.RestClient, 'get_tgt_port_group',
return_value='0')
@mock.patch.object(rest_client.RestClient, 'delete_portgroup')
def test_build_ini_targ_map_exist_portg(self, delete, engines, portg):
fake_lookup_service = FCSanLookupService()
zone_helper = fc_zone_helper.FCZoneHelper(
fake_lookup_service, self.driver.client)
# Host with id '5' has no map on the array.
(tgt_wwns, portg_id, init_targ_map) = zone_helper.build_ini_targ_map(
['10000090fa0d6754'], '5', '11')
expected_wwns = ['2000643e8c4c5f66']
expected_map = {'10000090fa0d6754': ['2000643e8c4c5f66']}
self.assertEqual(expected_wwns, tgt_wwns)
self.assertEqual(expected_map, init_targ_map)
self.assertEqual(1, delete.call_count)
def test_get_init_targ_map(self):
fake_lookup_service = FCSanLookupService()
@ -3706,6 +3865,16 @@ class HuaweiFCDriverTestCase(test.TestCase):
self.assertEqual(expected_wwns, tgt_wwns)
self.assertEqual(expected_map, init_targ_map)
def test_get_init_targ_map_no_host(self):
fake_lookup_service = FCSanLookupService()
zone_helper = fc_zone_helper.FCZoneHelper(
fake_lookup_service, self.driver.client)
ret = zone_helper.get_init_targ_map(
['10000090fa0d6754'], None)
expected_ret = ([], None, {})
self.assertEqual(expected_ret, ret)
def test_multi_resturls_success(self):
self.driver.client.test_multi_url_flag = True
lun_info = self.driver.create_volume(test_volume)

View File

@ -55,7 +55,7 @@ class FCZoneHelper(object):
views = self.client.get_views_by_portg(portg)
if not views:
LOG.debug("PortGroup %s doesn't belong to any view.", portg)
break
continue
LOG.debug("PortGroup %(portg)s belongs to view %(views)s.",
{"portg": portg, "views": views[0]})
@ -96,6 +96,8 @@ class FCZoneHelper(object):
return weighted_ports
def _get_weighted_ports(self, contr_port_map, ports_info, contrs):
LOG.debug("_get_weighted_ports, we only select ports from "
"controllers: %s", contrs)
weighted_ports = []
for contr in contrs:
if contr in contr_port_map:
@ -131,20 +133,51 @@ class FCZoneHelper(object):
'initiators': fabric_connected_initiators})
return fabric_connected_ports, fabric_connected_initiators
def build_ini_targ_map(self, wwns, host_id, lun_id):
def _get_lun_engine_contrs(self, engines, lun_id):
contrs = []
engine_id = None
lun_info = self.client.get_lun_info(lun_id)
lun_contr_id = lun_info['OWNINGCONTROLLER']
engines = self.client.get_all_engines()
LOG.debug("Get array engines: %s", engines)
for engine in engines:
contrs = json.loads(engine['NODELIST'])
engine_id = engine['ID']
if lun_contr_id in contrs:
LOG.debug("LUN %(lun_id)s belongs to engine %(engine_id)s.",
{"lun_id": lun_id, "engine_id": engine_id})
break
LOG.debug("LUN %(lun_id)s belongs to engine %(engine_id)s. Engine "
"%(engine_id)s has controllers: %(contrs)s.",
{"lun_id": lun_id, "engine_id": engine_id, "contrs": contrs})
return contrs, engine_id
def _build_contr_port_map(self, fabric_connected_ports, ports_info):
contr_port_map = {}
for port in fabric_connected_ports:
contr = ports_info[port]['contr']
if not contr_port_map.get(contr):
contr_port_map[contr] = []
contr_port_map[contr].append(port)
LOG.debug("Controller port map: %s.", contr_port_map)
return contr_port_map
def _create_new_portg(self, portg_name, engine_id):
portg_id = self.client.get_tgt_port_group(portg_name)
if portg_id:
LOG.debug("Found port group %s not belonged to any view, "
"deleting it.", portg_name)
ports = self.client.get_fc_ports_by_portgroup(portg_id)
for port_id in ports.values():
self.client.remove_port_from_portgroup(portg_id, port_id)
self.client.delete_portgroup(portg_id)
description = constants.PORTGROUP_DESCRIP_PREFIX + engine_id
new_portg_id = self.client.create_portg(portg_name, description)
return new_portg_id
def build_ini_targ_map(self, wwns, host_id, lun_id):
engines = self.client.get_all_engines()
LOG.debug("Get array engines: %s", engines)
contrs, engine_id = self._get_lun_engine_contrs(engines, lun_id)
# Check if there is already a port group in the view.
# If yes and have already considered the engine,
# we won't change anything about the port group and zone.
@ -172,17 +205,15 @@ class FCZoneHelper(object):
self._filter_by_fabric(wwns, ports_info.keys()))
# Build a controller->ports map for convenience.
contr_port_map = {}
for port in fabric_connected_ports:
contr = ports_info[port]['contr']
if not contr_port_map.get(contr):
contr_port_map[contr] = []
contr_port_map[contr].append(port)
LOG.debug("Controller port map: %s.", contr_port_map)
contr_port_map = self._build_contr_port_map(fabric_connected_ports,
ports_info)
# Get the 'best' ports for the given controllers.
weighted_ports = self._get_weighted_ports(contr_port_map, ports_info,
contrs)
if not weighted_ports:
msg = _("No FC port can be used for LUN %s.") % lun_id
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
# Handle port group.
port_list = [ports_info[port]['id'] for port in weighted_ports]
@ -194,16 +225,7 @@ class FCZoneHelper(object):
# port group.
weighted_ports.extend(list(ports.keys()))
else:
portg_id = self.client.get_tgt_port_group(portg_name)
if portg_id:
LOG.debug("Found port group %s not belonged to any view, "
"deleting it.", portg_name)
ports = self.client.get_fc_ports_by_portgroup(portg_id)
for port_id in ports.values():
self.client.remove_port_from_portgroup(portg_id, port_id)
self.client.delete_portgroup(portg_id)
description = constants.PORTGROUP_DESCRIP_PREFIX + engine_id
portg_id = self.client.create_portg(portg_name, description)
portg_id = self._create_new_portg(portg_name, engine_id)
for port in port_list:
self.client.add_port_to_portg(portg_id, port)

View File

@ -1472,6 +1472,15 @@ class HuaweiBaseDriver(driver.VolumeDriver):
{'snapshot_id': snapshot['id'],
'snapshot_name': snapshot_name})
def remove_host_with_check(self, host_id):
wwns_in_host = (
self.client.get_host_fc_initiators(host_id))
iqns_in_host = (
self.client.get_host_iscsi_initiators(host_id))
if not (wwns_in_host or iqns_in_host or
self.client.is_host_associated_to_hostgroup(host_id)):
self.client.remove_host(host_id)
def _classify_volume(self, volumes):
normal_volumes = []
replica_volumes = []
@ -1834,8 +1843,14 @@ class HuaweiFCDriver(HuaweiBaseDriver, driver.FibreChannelDriver):
if self.fcsan:
# Use FC switch.
zone_helper = fc_zone_helper.FCZoneHelper(self.fcsan, self.client)
(tgt_port_wwns, portg_id, init_targ_map) = (
zone_helper.build_ini_targ_map(wwns, host_id, lun_id))
try:
(tgt_port_wwns, portg_id, init_targ_map) = (
zone_helper.build_ini_targ_map(wwns, host_id, lun_id))
except Exception as err:
self.remove_host_with_check(host_id)
msg = _('build_ini_targ_map fails. %s') % err
raise exception.VolumeBackendAPIException(data=msg)
for ini in init_targ_map:
self.client.ensure_fc_initiator_added(ini, host_id)
else:
@ -1880,13 +1895,12 @@ class HuaweiFCDriver(HuaweiBaseDriver, driver.FibreChannelDriver):
'initiator_target_map': init_targ_map,
'map_info': map_info}, }
loc_tgt_wwn = fc_info['data']['target_wwn']
local_ini_tgt_map = fc_info['data']['initiator_target_map']
# Deal with hypermetro connection.
metadata = huawei_utils.get_volume_metadata(volume)
LOG.info(_LI("initialize_connection, metadata is: %s."), metadata)
if 'hypermetro_id' in metadata:
loc_tgt_wwn = fc_info['data']['target_wwn']
local_ini_tgt_map = fc_info['data']['initiator_target_map']
hyperm = hypermetro.HuaweiHyperMetro(self.client,
self.rmt_client,
self.configuration)