diff --git a/cinder/tests/unit/volume/drivers/test_pure.py b/cinder/tests/unit/volume/drivers/test_pure.py index f9be06e7e78..c8213612233 100644 --- a/cinder/tests/unit/volume/drivers/test_pure.py +++ b/cinder/tests/unit/volume/drivers/test_pure.py @@ -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, diff --git a/cinder/volume/drivers/pure.py b/cinder/volume/drivers/pure.py index df6cf973351..c17c4b2ec4e 100644 --- a/cinder/volume/drivers/pure.py +++ b/cinder/volume/drivers/pure.py @@ -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.") % diff --git a/releasenotes/notes/pure_lacp_iscsi-34678bdb98fa6bab.yaml b/releasenotes/notes/pure_lacp_iscsi-34678bdb98fa6bab.yaml new file mode 100644 index 00000000000..bdfea7ba8f7 --- /dev/null +++ b/releasenotes/notes/pure_lacp_iscsi-34678bdb98fa6bab.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + Pure Storage `bug #2101859 + `_: Fixed issue where + LACP bonds were not been correctly identified as iSCSI and NVMe + targets.