[Pure Storage] Fix issue with LACP ports not being identified

When using iSCSI or NVMe as dataplanes target ports are identified
directly from the backend.

When an LACP bond is created this was not being correctly identified
as valid.

This patch resolves this issue.

Closes-Bug: #2101859
Change-Id: I5b56c590a6c9b82ab2e08e4211bfd3d187afdf8e
This commit is contained in:
Simon Dodsley 2025-03-10 13:31:56 -04:00
parent 79928cd6a9
commit 5070eeaecf
3 changed files with 154 additions and 22 deletions

View File

@ -590,7 +590,34 @@ NON_ISCSI_PORT = {
"portal": None,
"wwn": "5001500150015081",
}
NVME_PORTS_WITH = NVME_PORTS + [NON_ISCSI_PORT]
ISCSI_LACP_PORTS = [
{
"name": "lacp2",
"iqn": TARGET_IQN,
"nqn": None,
"portal": None,
"wwn": None,
},
]
NVME_LACP_PORTS = [
{
"name": "lacp0",
"iqn": None,
"nqn": TARGET_NQN,
"portal": None,
"wwn": None,
},
{
"name": "lacp1",
"iqn": None,
"nqn": TARGET_NQN,
"portal": None,
"wwn": None,
},
]
NVME_PORTS_WITH = NVME_PORTS + [NON_ISCSI_PORT] + NVME_LACP_PORTS
ISCSI_PORTS_WITH = ISCSI_PORTS + ISCSI_LACP_PORTS
PORTS_WITH = ISCSI_PORTS + [NON_ISCSI_PORT] + ISCSI_LACP_PORTS
PORTS_WITH = ISCSI_PORTS + [NON_ISCSI_PORT]
PORTS_WITHOUT = [NON_ISCSI_PORT]
TOTAL_CAPACITY = 50.0
@ -1175,6 +1202,56 @@ QOS_BWS = {"maxBWS": "1"}
ARRAY_RESPONSE = {
'status_code': 200
}
INTERFACES = [
{
'name': 'ct0.eth4',
'services': ['nvme-tcp'],
'eth': {'address': '1.1.1.1',
'subtype': 'physical'},
},
{
'name': 'ct0.eth5',
'services': ['iscsi'],
'eth': {'address': '2.2.2.2',
'subtype': 'physical'},
},
{
'name': 'ct0.eth20',
'services': ['nvme-roce'],
'eth': {'address': '3.3.3.3',
'subtype': 'physical'}
},
{
'name': 'ct0.fc4',
'services': ['nvme-fc'],
'eth': {'address': None,
'subtype': 'physical'},
},
{
'name': 'lacp0',
'services': ['nvme-roce'],
'eth': {'address': '4.4.4.4',
'subtype': 'lacp_bond'},
},
{
'name': 'lacp1',
'services': ['nvme-tcp'],
'eth': {'address': '5.5.5.5',
'subtype': 'lacp_bond'},
},
{
'name': 'lacp2',
'services': ['iscsi'],
'eth': {'address': '6.6.6.6',
'subtype': 'lacp_bond'},
},
{
'name': 'ct0.fc1',
'services': ['scsi-fc'],
'eth': {'address': None,
'subtype': 'physical'},
}
]
class PureDriverTestCase(test.TestCase):
@ -4501,21 +4578,27 @@ class PureISCSIDriverTestCase(PureBaseSharedDriverTestCase):
def test_get_target_iscsi_ports(self):
self.array.get_controllers.return_value = CTRL_OBJ
self.array.get_ports.return_value = VALID_ISCSI_PORTS
self.array.get_network_interfaces.return_value = ValidResponse(
200, None, 1, [DotNotation(INTERFACES[1])], {})
ret = self.driver._get_target_iscsi_ports(self.array)
self.assertEqual(ISCSI_PORTS[0:4], ret)
self.assertEqual(ISCSI_PORTS[0:4], ret[0:4])
def test_get_target_iscsi_ports_with_iscsi_and_fc(self):
self.array.get_controllers.return_value = CTRL_OBJ
PORTS_DATA = [DotNotation(i) for i in PORTS_WITH]
PORTS_DATA = [DotNotation(i) for i in ISCSI_PORTS_WITH]
ifc_ports = ValidResponse(200, None, 1, PORTS_DATA, {})
self.array.get_ports.return_value = ifc_ports
self.array.get_network_interfaces.return_value = ValidResponse(
200, None, 1, [DotNotation(INTERFACES[0])], {})
ret = self.driver._get_target_iscsi_ports(self.array)
self.assertEqual(ISCSI_PORTS, ret)
self.assertEqual(ISCSI_PORTS_WITH[0:9], ret[0:9])
def test_get_target_iscsi_ports_with_no_ports(self):
# Should raise an exception if there are no ports
self.array.get_controllers.return_value = CTRL_OBJ
no_ports = ValidResponse(200, None, 1, [], {})
self.array.get_network_interfaces.return_value = ValidResponse(
200, None, 1, [], {})
self.array.get_ports.return_value = no_ports
self.assertRaises(pure.PureDriverException,
self.driver._get_target_iscsi_ports,
@ -4525,6 +4608,8 @@ class PureISCSIDriverTestCase(PureBaseSharedDriverTestCase):
# Should raise an exception of there are no iscsi ports
self.array.get_controllers.return_value = CTRL_OBJ
PORTS_NOISCSI = [DotNotation(i) for i in PORTS_WITHOUT]
self.array.get_network_interfaces.return_value = ValidResponse(
200, None, 1, [DotNotation(INTERFACES[3])], {})
self.array.get_ports.\
return_value = ValidResponse(200, None, 1, PORTS_NOISCSI, {})
self.assertRaises(pure.PureDriverException,
@ -5677,18 +5762,20 @@ class PureNVMEDriverTestCase(PureBaseSharedDriverTestCase):
{'name': 'CT0.FC4',
'wwn': TARGET_WWN,
'iqn': None,
'nqn': TARGET_NQN},
{'name': 'LACP0',
'wwn': None,
'iqn': None,
'nqn': TARGET_NQN},
{'name': 'LACP1',
'wwn': None,
'iqn': None,
'nqn': TARGET_NQN}]
interfaces = [
{'name': 'ct0.eth4', 'services': ['nvme-tcp']},
{'name': 'ct0.eth5', 'services': ['iscsi']},
{'name': 'ct0.eth20', 'services': ['nvme-roce']},
{'name': 'ct0.fc4', 'services': ['nvme-fc']}
]
# Test for the nvme-tcp port
self.driver.configuration.pure_nvme_transport = "tcp"
self.array.get_controllers.return_value = CTRL_OBJ
nvme_interfaces = ValidResponse(200, None, 4,
[DotNotation(interfaces[x])
[DotNotation(INTERFACES[x])
for x in range(4)], {})
self.array.get_network_interfaces.return_value = nvme_interfaces
nvme_ports = ValidResponse(200, None, 4,
@ -5711,17 +5798,37 @@ class PureNVMEDriverTestCase(PureBaseSharedDriverTestCase):
# Test for the nvme-roce port
self.driver.configuration.pure_nvme_transport = "roce"
nvme_roce_interface = ValidResponse(200, None, 1,
[DotNotation(interfaces[2])], {})
[DotNotation(INTERFACES[2])], {})
self.array.get_network_interfaces.return_value = nvme_roce_interface
nvme_roce_ports = ValidResponse(200, None, 1,
[DotNotation(ports[2])], {})
self.array.get_ports.return_value = nvme_roce_ports
ret = self.driver._get_target_nvme_ports(self.array)
self.assertEqual([ports[2]], ret)
self.assertEqual([ports[2]], [ret[0]])
# Test for the nvme-roce LACP port
self.driver.configuration.pure_nvme_transport = "roce"
nvme_roce_interface = ValidResponse(200, None, 1,
[DotNotation(INTERFACES[4])], {})
self.array.get_network_interfaces.return_value = nvme_roce_interface
nvme_roce_ports = ValidResponse(200, None, 1,
[DotNotation(ports[4])], {})
self.array.get_ports.return_value = nvme_roce_ports
ret = self.driver._get_target_nvme_ports(self.array)
self.assertEqual([ports[4]], [ret[0]])
# Test for the nvme-tcp LACP port
self.driver.configuration.pure_nvme_transport = "tcp"
nvme_roce_interface = ValidResponse(200, None, 1,
[DotNotation(INTERFACES[5])], {})
self.array.get_network_interfaces.return_value = nvme_roce_interface
nvme_roce_ports = ValidResponse(200, None, 1,
[DotNotation(ports[5])], {})
self.array.get_ports.return_value = nvme_roce_ports
ret = self.driver._get_target_nvme_ports(self.array)
self.assertEqual([ports[5]], [ret[0]])
# Test for empty dict if only nvme-fc port
self.driver.configuration.pure_nvme_transport = "roce"
nvme_fc_interface = ValidResponse(200, None, 1,
[DotNotation(interfaces[3])], {})
[DotNotation(INTERFACES[3])], {})
self.array.get_network_interfaces.return_value = nvme_fc_interface
nvme_fc_ports = ValidResponse(200, None, 1,
[DotNotation(ports[3])], {})
@ -5733,7 +5840,9 @@ class PureNVMEDriverTestCase(PureBaseSharedDriverTestCase):
# Should raise an exception if there are no ports
self.array.get_controllers.return_value = CTRL_OBJ
nvme_no_ports = ValidResponse(200, None, 1, [], {})
nvme_no_interfaces = ValidResponse(200, None, 1, [], {})
self.array.get_ports.return_value = nvme_no_ports
self.array.get_network_interfaces.return_value = nvme_no_interfaces
self.assertRaises(
pure.PureDriverException,
self.driver._get_target_nvme_ports,
@ -5743,8 +5852,12 @@ class PureNVMEDriverTestCase(PureBaseSharedDriverTestCase):
def test_get_target_nvme_ports_with_only_fc_ports(self):
# Should raise an exception of there are no nvme ports
self.array.get_controllers.return_value = CTRL_OBJ
nvme_noports = ValidResponse(200, None, 1, [PORTS_WITHOUT], {})
PORTS_NONVME = [DotNotation(i) for i in PORTS_WITHOUT]
nvme_noports = ValidResponse(200, None, 1, PORTS_NONVME, {})
nvme_nointerfaces = ValidResponse(200, None, 1,
[DotNotation(INTERFACES[3])], {})
self.array.get_ports.return_value = nvme_noports
self.array.get_network_interfaces.return_value = nvme_nointerfaces
self.assertRaises(
pure.PureDriverException,
self.driver._get_target_nvme_ports,

View File

@ -3081,6 +3081,18 @@ class PureBaseVolumeDriver(san.SanDriver):
ports += list(
array.get_ports(filter="name='" + controller + ".*'").items
)
lacps = list(
array.get_network_interfaces(
filter="eth.subtype='lacp_bond'"
).items
)
if lacps:
for lacp in range(0, len(lacps)):
ports += list(
array.get_ports(
names=[lacps[lacp].name.upper()]
).items
)
return ports
@ -3729,13 +3741,13 @@ class PureNVMEDriver(PureBaseVolumeDriver, driver.BaseVD):
valid_nvme_ports = []
nvme_ports = [port for port in ports if getattr(port, "nqn", None)]
for port in range(0, len(nvme_ports)):
if "ETH" in nvme_ports[port].name:
port_detail = list(array.get_network_interfaces(
names=[nvme_ports[port].name]
).items)[0]
if port_detail.services[0] == "nvme-" + \
self.configuration.pure_nvme_transport:
valid_nvme_ports.append(nvme_ports[port])
port_detail = list(array.get_network_interfaces(
names=[nvme_ports[port].name.lower()]
).items)[0]
if hasattr(port_detail.eth, "address") and (
port_detail.services[0] == "nvme-" +
self.configuration.pure_nvme_transport):
valid_nvme_ports.append(nvme_ports[port])
if not nvme_ports:
raise PureDriverException(
reason=_("No %(type)s enabled ports on target array.") %

View File

@ -0,0 +1,7 @@
---
fixes:
- |
Pure Storage `bug #2101859
<https://bugs.launchpad.net/cinder/+bug/2101859>`_: Fixed issue where
LACP bonds were not been correctly identified as iSCSI and NVMe
targets.