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:
parent
42db20e90a
commit
a80119a533
@ -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)
|
||||
|
@ -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"
|
||||
|
@ -0,0 +1,3 @@
|
||||
---
|
||||
upgrade:
|
||||
- huawei iscsi multipath support
|
Loading…
Reference in New Issue
Block a user