huawei-iscsi-multipath-support

Adding support for iSCSI multipath in huawei cinder driver. If enable
iSCSI multipath, when attaching volume, cinder driver will get all
useable eth ports from target storage, and return them to nova. The
params like DefaultTargetIP and TargetPortGroup in configure file will
not take effect. If iSCSI multipath is not enabled, cinder driver will
use the info like DefaultTargetIP or TargetPortGroup in config file to
get corresponding ports from target storage.

DocImpact
Implements: blueprint huawei-iscsi-multipath-support

Change-Id: I9d94c2b56073c007a7b71b46ed163326c748f0b4
This commit is contained in:
GaoZqiang 2016-06-03 10:58:16 +08:00
parent 42db20e90a
commit a80119a533
3 changed files with 218 additions and 27 deletions

View File

@ -225,6 +225,7 @@ FAKE_CREATE_VOLUME_RESPONSE = {"ID": "1",
"WWN": '6643e8c1004c5f6723e9f454003'}
FakeConnector = {'initiator': 'iqn.1993-08.debian:01:ec2bff7ac3a3',
'multipath': False,
'wwpns': ['10000090fa0d6754'],
'wwnns': ['10000090fa0d6755'],
'host': 'ubuntuc',
@ -522,17 +523,18 @@ FAKE_GET_SNAPSHOT_INFO_RESPONSE = """
"""
# A fake response of get iscsi response
FAKE_GET_ISCSI_INFO_RESPONSE = """
{
"data": [{
"ETHPORTID": "139267",
"ID": "iqn.oceanstor:21004846fb8ca15f::22003:192.0.2.244",
"TPGT": "8196",
"ID": "0+iqn.oceanstor:21004846fb8ca15f::22004:192.0.2.1,t,0x2005",
"TPGT": "8197",
"TYPE": 249
},
{
"ETHPORTID": "139268",
"ID": "iqn.oceanstor:21004846fb8ca15f::22003:192.0.2.244",
"ID": "1+iqn.oceanstor:21004846fb8ca15f::22003:192.0.2.2,t,0x2004",
"TPGT": "8196",
"TYPE": 249
}
@ -2126,10 +2128,134 @@ class HuaweiISCSIDriverTestCase(test.TestCase):
self.assertEqual(driver_data, model_update['replication_driver_data'])
self.assertEqual('available', model_update['replication_status'])
def test_initialize_connection_success(self):
def test_initialize_connection_success_multipath_portgroup(self):
temp_connector = copy.deepcopy(FakeConnector)
temp_connector['multipath'] = True
self.mock_object(rest_client.RestClient, 'get_tgt_port_group',
mock.Mock(return_value = '11'))
iscsi_properties = self.driver.initialize_connection(test_volume,
FakeConnector)
self.assertEqual(1, iscsi_properties['data']['target_lun'])
temp_connector)
self.assertEqual([1, 1], iscsi_properties['data']['target_luns'])
def test_initialize_connection_fail_multipath_portgroup(self):
temp_connector = copy.deepcopy(FakeConnector)
temp_connector['multipath'] = True
self.mock_object(rest_client.RestClient, 'get_tgt_port_group',
mock.Mock(return_value = '12'))
self.mock_object(rest_client.RestClient, '_get_tgt_ip_from_portgroup',
mock.Mock(return_value = []))
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.initialize_connection,
test_volume, temp_connector)
def test_initialize_connection_success_multipath_targetip(self):
iscsi_info = [{'Name': 'iqn.1993-08.debian:01:ec2bff7ac3a3',
'TargetIP': '192.0.2.2',
'CHAPinfo': 'mm-user;mm-user@storage',
'ALUA': '1'}]
configuration = mock.Mock(spec = conf.Configuration)
configuration.hypermetro_devices = hypermetro_devices
self.mock_object(time, 'sleep', Fake_sleep)
driver = FakeISCSIStorage(configuration = self.configuration)
driver.do_setup()
driver.configuration.iscsi_info = iscsi_info
driver.client.iscsi_info = iscsi_info
temp_connector = copy.deepcopy(FakeConnector)
temp_connector['multipath'] = True
iscsi_properties = driver.initialize_connection(test_volume,
temp_connector)
self.assertEqual([1], iscsi_properties['data']['target_luns'])
def test_initialize_connection_fail_multipath_targetip(self):
iscsi_info = [{'Name': 'iqn.1993-08.debian:01:ec2bff7ac3a3',
'TargetIP': '192.0.2.6',
'CHAPinfo': 'mm-user;mm-user@storage',
'ALUA': '1'}]
configuration = mock.Mock(spec = conf.Configuration)
configuration.hypermetro_devices = hypermetro_devices
self.mock_object(time, 'sleep', Fake_sleep)
driver = FakeISCSIStorage(configuration = self.configuration)
driver.do_setup()
driver.configuration.iscsi_info = iscsi_info
driver.client.iscsi_info = iscsi_info
temp_connector = copy.deepcopy(FakeConnector)
temp_connector['multipath'] = True
self.assertRaises(exception.VolumeBackendAPIException,
driver.initialize_connection,
test_volume, temp_connector)
def test_initialize_connection_success_multipath_defaultip(self):
iscsi_info = [{'Name': 'iqn.1993-08.debian:01:ec2bff7ac3a3',
'CHAPinfo': 'mm-user;mm-user@storage',
'ALUA': '1'}]
default_target_ip = ['192.0.2.2']
configuration = mock.Mock(spec = conf.Configuration)
configuration.hypermetro_devices = hypermetro_devices
self.mock_object(time, 'sleep', Fake_sleep)
driver = FakeISCSIStorage(configuration = self.configuration)
driver.do_setup()
driver.configuration.iscsi_info = iscsi_info
driver.client.iscsi_info = iscsi_info
driver.configuration.iscsi_default_target_ip = default_target_ip
driver.client.iscsi_default_target_ip = default_target_ip
temp_connector = copy.deepcopy(FakeConnector)
temp_connector['multipath'] = True
iscsi_properties = driver.initialize_connection(test_volume,
temp_connector)
self.assertEqual([1], iscsi_properties['data']['target_luns'])
def test_initialize_connection_fail_multipath_defaultip(self):
iscsi_info = [{'Name': 'iqn.1993-08.debian:01:ec2bff7ac3a3',
'CHAPinfo': 'mm-user;mm-user@storage',
'ALUA': '1'}]
default_target_ip = ['192.0.2.6']
configuration = mock.Mock(spec = conf.Configuration)
configuration.hypermetro_devices = hypermetro_devices
self.mock_object(time, 'sleep', Fake_sleep)
driver = FakeISCSIStorage(configuration = self.configuration)
driver.do_setup()
driver.configuration.iscsi_info = iscsi_info
driver.client.iscsi_info = iscsi_info
driver.configuration.iscsi_default_target_ip = default_target_ip
driver.client.iscsi_default_target_ip = default_target_ip
temp_connector = copy.deepcopy(FakeConnector)
temp_connector['multipath'] = True
self.assertRaises(exception.VolumeBackendAPIException,
driver.initialize_connection,
test_volume, temp_connector)
def test_initialize_connection_fail_no_port_in_portgroup(self):
temp_connector = copy.deepcopy(FakeConnector)
temp_connector['multipath'] = True
self.mock_object(rest_client.RestClient, 'get_tgt_port_group',
mock.Mock(return_value = '11'))
self.mock_object(rest_client.RestClient, '_get_tgt_ip_from_portgroup',
mock.Mock(return_value = []))
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.initialize_connection,
test_volume, temp_connector)
def test_initialize_connection_fail_multipath_no_ip(self):
iscsi_info = [{'Name': 'iqn.1993-08.debian:01:ec2bff7ac3a3',
'CHAPinfo': 'mm-user;mm-user@storage',
'ALUA': '1'}]
configuration = mock.Mock(spec = conf.Configuration)
configuration.hypermetro_devices = hypermetro_devices
self.mock_object(time, 'sleep', Fake_sleep)
driver = FakeISCSIStorage(configuration = self.configuration)
driver.do_setup()
driver.configuration.iscsi_info = iscsi_info
driver.client.iscsi_info = iscsi_info
driver.configuration.iscsi_default_target_ip = None
driver.client.iscsi_default_target_ip = None
temp_connector = copy.deepcopy(FakeConnector)
temp_connector['multipath'] = True
self.assertRaises(exception.VolumeBackendAPIException,
driver.initialize_connection,
test_volume, temp_connector)
def test_terminate_connection_success(self):
self.driver.terminate_connection(test_volume, FakeConnector)

View File

@ -1213,21 +1213,61 @@ class RestClient(object):
def get_iscsi_params(self, connector):
"""Get target iSCSI params, including iqn, IP."""
initiator = connector['initiator']
multipath = connector['multipath']
target_ips = []
target_iqns = []
temp_tgt_ips = []
portgroup = None
portgroup_id = None
if multipath:
for ini in self.iscsi_info:
if ini['Name'] == initiator:
for key in ini:
if key == 'TargetPortGroup':
portgroup = ini['TargetPortGroup']
elif key == 'TargetIP':
target_ips.append(ini['TargetIP'])
portgroup = ini.get('TargetPortGroup')
if portgroup:
portgroup_id = self.get_tgt_port_group(portgroup)
target_ips = self._get_tgt_ip_from_portgroup(portgroup_id)
temp_tgt_ips = self._get_tgt_ip_from_portgroup(portgroup_id)
valid_port_info = self._get_tgt_port_ip_from_rest()
valid_tgt_ips = valid_port_info
for ip in temp_tgt_ips:
if ip in valid_tgt_ips:
target_ips.append(ip)
if not target_ips:
msg = (_(
'get_iscsi_params: No valid port in portgroup. '
'portgroup_id: %(id)s, please check it on storage.')
% {'id': portgroup_id})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
else:
target_ips = self._get_target_ip(initiator)
else:
target_ips = self._get_target_ip(initiator)
# Deal with the remote tgt ip.
if 'remote_target_ip' in connector:
target_ips.append(connector['remote_target_ip'])
LOG.info(_LI('Get the default ip: %s.'), target_ips)
for ip in target_ips:
target_iqn = self._get_tgt_iqn_from_rest(ip)
if not target_iqn:
target_iqn = self._get_tgt_iqn(ip)
if target_iqn:
target_iqns.append(target_iqn)
return (target_iqns, target_ips, portgroup_id)
def _get_target_ip(self, initiator):
target_ips = []
for ini in self.iscsi_info:
if ini['Name'] == initiator:
if ini.get('TargetIP'):
target_ips.append(ini.get('TargetIP'))
# If not specify target IP for some initiators, use default IP.
if not target_ips:
@ -1241,20 +1281,42 @@ class RestClient(object):
'for initiator %(ini)s, please check config file.')
% {'ini': initiator})
LOG.error(msg)
raise exception.InvalidInput(reason=msg)
raise exception.VolumeBackendAPIException(data=msg)
# Deal with the remote tgt ip.
if 'remote_target_ip' in connector:
target_ips.append(connector['remote_target_ip'])
LOG.info(_LI('Get the default ip: %s.'), target_ips)
for ip in target_ips:
target_iqn = self._get_tgt_iqn_from_rest(ip)
if not target_iqn:
target_iqn = self._get_tgt_iqn(ip)
if target_iqn:
target_iqns.append(target_iqn)
return target_ips
return (target_iqns, target_ips, portgroup_id)
def _get_tgt_port_ip_from_rest(self):
url = "/iscsi_tgt_port"
result = self.call(url, None, "GET")
info_list = []
target_ips = []
if result['error']['code'] != 0:
LOG.warning(_LW("Can't find target port info from rest."))
return target_ips
elif not result['data']:
msg = (_(
"Can't find valid IP from rest, please check it on storage."))
LOG.error(msg)
raise exception.VolumeBackendAPIException(data = msg)
if 'data' in result:
for item in result['data']:
info_list.append(item['ID'])
if not info_list:
LOG.warning(_LW("Can't find target port info from rest."))
return target_ips
for info in info_list:
split_list = info.split(",")
info_before = split_list[0]
iqn_info = info_before.split("+")
target_iqn = iqn_info[1]
ip_info = target_iqn.split(":")
target_ip = ip_info[-1]
target_ips.append(target_ip)
return target_ips
def _get_tgt_iqn_from_rest(self, target_ip):
url = "/iscsi_tgt_port"

View File

@ -0,0 +1,3 @@
---
upgrade:
- huawei iscsi multipath support