Fix Infinidat driver to return all iSCSI portals

Infinidat Cinder driver truncates the list of configured iSCSI
portals and returns only the first IP for a given network space.
And in case of network path failure we lose access to the data.

To fix this issue, we need to return all configured and enabled
iSCSI portals for a given configured network space.

Closes-bug: #1981354
Change-Id: Ie2b7a163ee3a83121c04a21808ef437d740426d5
Co-authored-by: Alexander Deiter <adeiter@infinidat.com>
Signed-off-by: Alexander Deiter <adeiter@infinidat.com>
(cherry picked from commit a25dcc8518)
This commit is contained in:
Alon Zeltser 2021-02-04 15:16:14 +02:00 committed by Alexander Deiter
parent 1fa65ac7e2
commit bb510b64a5
3 changed files with 197 additions and 52 deletions

View File

@ -1,4 +1,4 @@
# Copyright 2016 Infinidat Ltd.
# Copyright 2022 Infinidat Ltd.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@ -28,14 +28,25 @@ from cinder.volume import configuration
from cinder.volume.drivers import infinidat
TEST_LUN = 1
TEST_WWN_1 = '00:11:22:33:44:55:66:77'
TEST_WWN_2 = '11:11:22:33:44:55:66:77'
TEST_IP_ADDRESS = '1.1.1.1'
TEST_IQN = 'iqn.2012-07.org.fake:01'
TEST_ISCSI_TCP_PORT = 3260
TEST_TARGET_PORTAL = '{}:{}'.format(TEST_IP_ADDRESS, TEST_ISCSI_TCP_PORT)
TEST_IP_ADDRESS1 = '1.1.1.1'
TEST_IP_ADDRESS2 = '2.2.2.2'
TEST_IP_ADDRESS3 = '3.3.3.3'
TEST_IP_ADDRESS4 = '4.4.4.4'
TEST_INITIATOR_IQN = 'iqn.2012-07.org.initiator:01'
TEST_TARGET_IQN = 'iqn.2012-07.org.target:01'
TEST_ISCSI_TCP_PORT1 = 3261
TEST_ISCSI_TCP_PORT2 = 3262
TEST_ISCSI_NAMESPACE1 = 'netspace1'
TEST_ISCSI_NAMESPACE2 = 'netspace2'
TEST_TARGET_PORTAL1 = '{}:{}'.format(TEST_IP_ADDRESS1, TEST_ISCSI_TCP_PORT1)
TEST_TARGET_PORTAL2 = '{}:{}'.format(TEST_IP_ADDRESS2, TEST_ISCSI_TCP_PORT1)
TEST_TARGET_PORTAL3 = '{}:{}'.format(TEST_IP_ADDRESS3, TEST_ISCSI_TCP_PORT2)
TEST_TARGET_PORTAL4 = '{}:{}'.format(TEST_IP_ADDRESS4, TEST_ISCSI_TCP_PORT2)
TEST_FC_PROTOCOL = 'fc'
TEST_ISCSI_PROTOCOL = 'iscsi'
test_volume = mock.Mock(id=1, size=1, volume_type_id=1)
test_snapshot = mock.Mock(id=2, volume=test_volume, volume_id='1')
@ -43,7 +54,7 @@ test_clone = mock.Mock(id=3, size=1)
test_group = mock.Mock(id=4)
test_snapgroup = mock.Mock(id=5, group=test_group)
test_connector = dict(wwpns=[TEST_WWN_1],
initiator=TEST_IQN)
initiator=TEST_INITIATOR_IQN)
def skip_driver_setup(func):
@ -69,7 +80,7 @@ class InfiniboxDriverTestCaseBase(test.TestCase):
# create mock configuration
self.configuration = mock.Mock(spec=configuration.Configuration)
self.configuration.infinidat_storage_protocol = 'fc'
self.configuration.infinidat_storage_protocol = TEST_FC_PROTOCOL
self.configuration.san_ip = 'mockbox'
self.configuration.infinidat_pool_name = 'mockpool'
self.configuration.san_thin_provision = True
@ -113,15 +124,20 @@ class InfiniboxDriverTestCaseBase(test.TestCase):
self._mock_volume.create_snapshot.return_value = self._mock_volume
self._mock_host = mock.Mock()
self._mock_host.get_luns.return_value = []
self._mock_host.map_volume().get_lun.return_value = 1
self._mock_host.map_volume().get_lun.return_value = TEST_LUN
self._mock_pool = mock.Mock()
self._mock_pool.get_free_physical_capacity.return_value = units.Gi
self._mock_pool.get_physical_capacity.return_value = units.Gi
self._mock_ns = mock.Mock()
self._mock_ns.get_ips.return_value = [
mock.Mock(ip_address=TEST_IP_ADDRESS, enabled=True)]
self._mock_ns.get_properties.return_value = mock.Mock(
iscsi_iqn=TEST_IQN, iscsi_tcp_port=TEST_ISCSI_TCP_PORT)
self._mock_name_space1 = mock.Mock()
self._mock_name_space2 = mock.Mock()
self._mock_name_space1.get_ips.return_value = [
mock.Mock(ip_address=TEST_IP_ADDRESS1, enabled=True)]
self._mock_name_space2.get_ips.return_value = [
mock.Mock(ip_address=TEST_IP_ADDRESS3, enabled=True)]
self._mock_name_space1.get_properties.return_value = mock.Mock(
iscsi_iqn=TEST_TARGET_IQN, iscsi_tcp_port=TEST_ISCSI_TCP_PORT1)
self._mock_name_space2.get_properties.return_value = mock.Mock(
iscsi_iqn=TEST_TARGET_IQN, iscsi_tcp_port=TEST_ISCSI_TCP_PORT2)
self._mock_group = mock.Mock()
self._mock_qos_policy = mock.Mock()
result.volumes.safe_get.return_value = self._mock_volume
@ -131,7 +147,7 @@ class InfiniboxDriverTestCaseBase(test.TestCase):
result.cons_groups.safe_get.return_value = self._mock_group
result.cons_groups.create.return_value = self._mock_group
result.hosts.create.return_value = self._mock_host
result.network_spaces.safe_get.return_value = self._mock_ns
result.network_spaces.safe_get.return_value = self._mock_name_space1
result.components.nodes.get_all.return_value = []
result.qos_policies.create.return_value = self._mock_qos_policy
result.qos_policies.safe_get.return_value = None
@ -590,7 +606,7 @@ class InfiniboxDriverTestCaseFC(InfiniboxDriverTestCaseBase):
def test_validate_connector(self):
fc_connector = {'wwpns': [TEST_WWN_1, TEST_WWN_2]}
iscsi_connector = {'initiator': TEST_IQN}
iscsi_connector = {'initiator': TEST_INITIATOR_IQN}
self.driver.validate_connector(fc_connector)
self.assertRaises(exception.InvalidConnectorException,
self.driver.validate_connector, iscsi_connector)
@ -599,8 +615,8 @@ class InfiniboxDriverTestCaseFC(InfiniboxDriverTestCaseBase):
class InfiniboxDriverTestCaseISCSI(InfiniboxDriverTestCaseBase):
def setUp(self):
super(InfiniboxDriverTestCaseISCSI, self).setUp()
self.configuration.infinidat_storage_protocol = 'iscsi'
self.configuration.infinidat_iscsi_netspaces = ['netspace1']
self.configuration.infinidat_storage_protocol = TEST_ISCSI_PROTOCOL
self.configuration.infinidat_iscsi_netspaces = [TEST_ISCSI_NAMESPACE1]
self.configuration.use_chap_auth = False
self.driver.do_setup(None)
@ -609,23 +625,27 @@ class InfiniboxDriverTestCaseISCSI(InfiniboxDriverTestCaseBase):
self.assertRaises(exception.VolumeDriverException,
self.driver.do_setup, None)
def _assert_plurals(self, result, expected_length):
self.assertEqual(expected_length, len(result['data']['target_luns']))
self.assertEqual(expected_length, len(result['data']['target_iqns']))
self.assertEqual(expected_length,
len(result['data']['target_portals']))
self.assertTrue(all(lun == 1 for lun in result['data']['target_luns']))
self.assertTrue(
all(iqn == test_connector['initiator'] for
iqn in result['data']['target_iqns']))
self.assertTrue(all(target_portal == TEST_TARGET_PORTAL for
target_portal in result['data']['target_portals']))
def test_initialize_connection(self):
result = self.driver.initialize_connection(test_volume, test_connector)
self.assertEqual(1, result['data']['target_lun'])
self._assert_plurals(result, 1)
expected = {
'driver_volume_type': TEST_ISCSI_PROTOCOL,
'data': {
'target_discovered': True,
'target_portal': TEST_TARGET_PORTAL1,
'target_iqn': TEST_TARGET_IQN,
'target_lun': TEST_LUN,
'target_portals': [
TEST_TARGET_PORTAL1
],
'target_iqns': [
TEST_TARGET_IQN
],
'target_luns': [
TEST_LUN
]
}
}
self.assertEqual(expected, result)
def test_initialize_netspace_does_not_exist(self):
self._system.network_spaces.safe_get.return_value = None
@ -634,7 +654,7 @@ class InfiniboxDriverTestCaseISCSI(InfiniboxDriverTestCaseBase):
test_volume, test_connector)
def test_initialize_netspace_has_no_ips(self):
self._mock_ns.get_ips.return_value = []
self._mock_name_space1.get_ips.return_value = []
self.assertRaises(exception.VolumeDriverException,
self.driver.initialize_connection,
test_volume, test_connector)
@ -648,22 +668,136 @@ class InfiniboxDriverTestCaseISCSI(InfiniboxDriverTestCaseBase):
self.assertIn('auth_password', result['data'])
def test_initialize_connection_multiple_netspaces(self):
self.configuration.infinidat_iscsi_netspaces = ['netspace1',
'netspace2']
self.configuration.infinidat_iscsi_netspaces = [
TEST_ISCSI_NAMESPACE1, TEST_ISCSI_NAMESPACE2]
self._system.network_spaces.safe_get.side_effect = [
self._mock_name_space1, self._mock_name_space2]
result = self.driver.initialize_connection(test_volume, test_connector)
self.assertEqual(1, result['data']['target_lun'])
self._assert_plurals(result, 2)
expected = {
'driver_volume_type': TEST_ISCSI_PROTOCOL,
'data': {
'target_discovered': True,
'target_portal': TEST_TARGET_PORTAL1,
'target_iqn': TEST_TARGET_IQN,
'target_lun': TEST_LUN,
'target_portals': [
TEST_TARGET_PORTAL1,
TEST_TARGET_PORTAL3
],
'target_iqns': [
TEST_TARGET_IQN,
TEST_TARGET_IQN
],
'target_luns': [
TEST_LUN,
TEST_LUN
]
}
}
self.assertEqual(expected, result)
def test_initialize_connection_plurals(self):
def test_initialize_connection_multiple_netspaces_multipath(self):
self.configuration.infinidat_iscsi_netspaces = [
TEST_ISCSI_NAMESPACE1, TEST_ISCSI_NAMESPACE2]
self._system.network_spaces.safe_get.side_effect = [
self._mock_name_space1, self._mock_name_space2]
self._mock_name_space1.get_ips.return_value = [
mock.Mock(ip_address=TEST_IP_ADDRESS1, enabled=True),
mock.Mock(ip_address=TEST_IP_ADDRESS2, enabled=True)]
self._mock_name_space2.get_ips.return_value = [
mock.Mock(ip_address=TEST_IP_ADDRESS3, enabled=True),
mock.Mock(ip_address=TEST_IP_ADDRESS4, enabled=True)]
result = self.driver.initialize_connection(test_volume, test_connector)
self._assert_plurals(result, 1)
expected = {
'driver_volume_type': TEST_ISCSI_PROTOCOL,
'data': {
'target_discovered': True,
'target_portal': TEST_TARGET_PORTAL1,
'target_iqn': TEST_TARGET_IQN,
'target_lun': TEST_LUN,
'target_portals': [
TEST_TARGET_PORTAL1,
TEST_TARGET_PORTAL2,
TEST_TARGET_PORTAL3,
TEST_TARGET_PORTAL4
],
'target_iqns': [
TEST_TARGET_IQN,
TEST_TARGET_IQN,
TEST_TARGET_IQN,
TEST_TARGET_IQN
],
'target_luns': [
TEST_LUN,
TEST_LUN,
TEST_LUN,
TEST_LUN
]
}
}
self.assertEqual(expected, result)
def test_initialize_connection_disabled_interface(self):
self._mock_name_space1.get_ips.return_value = [
mock.Mock(ip_address=TEST_IP_ADDRESS1, enabled=False),
mock.Mock(ip_address=TEST_IP_ADDRESS2, enabled=True)]
result = self.driver.initialize_connection(test_volume, test_connector)
expected = {
'driver_volume_type': TEST_ISCSI_PROTOCOL,
'data': {
'target_discovered': True,
'target_portal': TEST_TARGET_PORTAL2,
'target_iqn': TEST_TARGET_IQN,
'target_lun': TEST_LUN,
'target_portals': [
TEST_TARGET_PORTAL2
],
'target_iqns': [
TEST_TARGET_IQN
],
'target_luns': [
TEST_LUN
]
}
}
self.assertEqual(expected, result)
def test_initialize_connection_multiple_interfaces(self):
self._mock_name_space1.get_ips.return_value = [
mock.Mock(ip_address=TEST_IP_ADDRESS1, enabled=True),
mock.Mock(ip_address=TEST_IP_ADDRESS2, enabled=True)]
self._mock_name_space1.get_properties.return_value = mock.Mock(
iscsi_iqn=TEST_TARGET_IQN, iscsi_tcp_port=TEST_ISCSI_TCP_PORT1)
result = self.driver.initialize_connection(test_volume, test_connector)
expected = {
'driver_volume_type': TEST_ISCSI_PROTOCOL,
'data': {
'target_discovered': True,
'target_portal': TEST_TARGET_PORTAL1,
'target_iqn': TEST_TARGET_IQN,
'target_lun': TEST_LUN,
'target_portals': [
TEST_TARGET_PORTAL1,
TEST_TARGET_PORTAL2
],
'target_iqns': [
TEST_TARGET_IQN,
TEST_TARGET_IQN
],
'target_luns': [
TEST_LUN,
TEST_LUN
]
}
}
self.assertEqual(expected, result)
def test_terminate_connection(self):
self.driver.terminate_connection(test_volume, test_connector)
def test_validate_connector(self):
fc_connector = {'wwpns': [TEST_WWN_1, TEST_WWN_2]}
iscsi_connector = {'initiator': TEST_IQN}
iscsi_connector = {'initiator': TEST_INITIATOR_IQN}
self.driver.validate_connector(iscsi_connector)
self.assertRaises(exception.InvalidConnectorException,
self.driver.validate_connector, fc_connector)

View File

@ -1,4 +1,4 @@
# Copyright 2016 Infinidat Ltd.
# Copyright 2022 Infinidat Ltd.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@ -116,10 +116,11 @@ class InfiniboxVolumeDriver(san.SanISCSIDriver):
1.4 - added support for QoS
1.5 - added support for volume compression
1.6 - added support for volume multi-attach
1.7 - fixed iSCSI to return all portals
"""
VERSION = '1.6'
VERSION = '1.7'
# ThirdPartySystems wiki page
CI_WIKI_NAME = "INFINIDAT_CI"
@ -364,11 +365,12 @@ class InfiniboxVolumeDriver(san.SanISCSIDriver):
raise exception.VolumeDriverException(message=msg)
return netspace
def _get_iscsi_portal(self, netspace):
for netpsace_interface in netspace.get_ips():
if netpsace_interface.enabled:
port = netspace.get_properties().iscsi_tcp_port
return "%s:%s" % (netpsace_interface.ip_address, port)
def _get_iscsi_portals(self, netspace):
port = netspace.get_properties().iscsi_tcp_port
portals = ["%s:%s" % (interface.ip_address, port) for interface
in netspace.get_ips() if interface.enabled]
if portals:
return portals
# if we get here it means there are no enabled ports
msg = (_('No available interfaces in iSCSI network space %s') %
netspace.get_name())
@ -397,9 +399,11 @@ class InfiniboxVolumeDriver(san.SanISCSIDriver):
target_luns = []
for netspace_name in netspace_names:
netspace = self._get_iscsi_network_space(netspace_name)
target_portals.append(self._get_iscsi_portal(netspace))
target_iqns.append(netspace.get_properties().iscsi_iqn)
target_luns.append(lun)
netspace_portals = self._get_iscsi_portals(netspace)
target_portals.extend(netspace_portals)
target_iqns.extend([netspace.get_properties().iscsi_iqn] *
len(netspace_portals))
target_luns.extend([lun] * len(netspace_portals))
result_data = dict(target_discovered=True,
target_portal=target_portals[0],

View File

@ -0,0 +1,7 @@
---
fixes:
- |
Infinidat Driver `bug #1981354
<https://bugs.launchpad.net/cinder/+bug/1981354>`_:
Fixed Infinidat driver to return all configured and
enabled iSCSI portals for a given network space.