Olivier Gayot has proposed merging ~ogayot/curtin:nvme-stas-of-only into curtin:master.
Commit message: do not squash Requested reviews: curtin developers (curtin-dev) Related bugs: Bug #2056730 in curtin: "noble-daily (20240310) cannot be installed offline" https://bugs.launchpad.net/curtin/+bug/2056730 For more details, see: https://code.launchpad.net/~ogayot/curtin/+git/curtin/+merge/462257 Only install nvme-stas if there is a NVMe-oF controller If a NVMe controller is present on the system, the storage configuration will contain an object of type nvme_controller. Currently, the presence of such object in the configuration leads to the installation of nvme-cli and nvme-stas. However, the packages are not needed when the controller uses the PCIe transport. We now inspect the nvme_controller instances and install nvme-stas & nvme-cli only if we detect a NVMe-oF controller. Also we had too many functions parsing the storage configuration to find the NVMe controllers, so I moved to a single implementation in curtin/block/nvme.py (this is patch number #2). -- Your team curtin developers is requested to review the proposed merge of ~ogayot/curtin:nvme-stas-of-only into curtin:master.
diff --git a/curtin/block/deps.py b/curtin/block/deps.py index e5370b6..d1d9d21 100644 --- a/curtin/block/deps.py +++ b/curtin/block/deps.py @@ -1,7 +1,7 @@ # This file is part of curtin. See LICENSE file for copyright and license info. from curtin.distro import DISTROS -from curtin.block import iscsi +from curtin.block import iscsi, nvme def storage_config_required_packages(storage_config, mapping): @@ -35,6 +35,11 @@ def storage_config_required_packages(storage_config, mapping): if len(iscsi_vols) > 0: needed_packages.extend(mapping['iscsi']) + # for NVMe controllers with transport != pcie, we need NVMe-oF tools + if nvme.get_nvme_controllers_from_config(storage_config, + exclude_pcie=True): + needed_packages.extend(mapping['nvme_of_controller']) + # for any format operations, check the fstype and # determine if we need any mkfs tools as well. format_configs = set([operation['fstype'] @@ -69,7 +74,8 @@ def detect_required_packages_mapping(osfamily=DISTROS.debian): 'lvm_partition': ['lvm2'], 'lvm_volgroup': ['lvm2'], 'ntfs': ['ntfs-3g'], - 'nvme_controller': ['nvme-cli', 'nvme-stas'], + 'nvme_controller': [], + 'nvme_of_controller': ['nvme-cli', 'nvme-stas'], 'raid': ['mdadm'], 'reiserfs': ['reiserfsprogs'], 'xfs': ['xfsprogs'], @@ -91,6 +97,7 @@ def detect_required_packages_mapping(osfamily=DISTROS.debian): 'lvm_volgroup': ['lvm2'], 'ntfs': [], 'nvme_controller': [], + 'nvme_of_controller': [], 'raid': ['mdadm'], 'reiserfs': [], 'xfs': ['xfsprogs'], @@ -112,6 +119,7 @@ def detect_required_packages_mapping(osfamily=DISTROS.debian): 'lvm_volgroup': ['lvm2'], 'ntfs': [], 'nvme_controller': [], + 'nvme_of_controller': [], 'raid': ['mdadm'], 'reiserfs': [], 'xfs': ['xfsprogs'], diff --git a/curtin/block/nvme.py b/curtin/block/nvme.py new file mode 100644 index 0000000..d1e0e59 --- /dev/null +++ b/curtin/block/nvme.py @@ -0,0 +1,40 @@ +# This file is part of curtin. See LICENSE file for copyright and license info. + +from typing import Any, Iterator + +from curtin.log import LOG + + +def _iter_nvme_controllers(cfg) -> Iterator[dict[str, Any]]: + if not cfg: + cfg = {} + + if 'storage' in cfg: + if not isinstance(cfg['storage'], dict): + sconfig = {} + else: + sconfig = cfg['storage'].get('config', []) + else: + sconfig = cfg.get('config', []) + + if not sconfig or not isinstance(sconfig, list): + LOG.warning('Configuration dictionary did not contain' + ' a storage configuration.') + return + + for item in sconfig: + if item['type'] == 'nvme_controller': + yield item + + +def get_nvme_controllers_from_config( + cfg, *, exclude_pcie=False) -> list[dict[str, Any]]: + '''Parse a curtin storage config and return a list of + NVMe controllers. If exclude_pcie is True, only return controllers that do + not use PCIe transport.''' + controllers = _iter_nvme_controllers(cfg) + + if not exclude_pcie: + return list(controllers) + + return [ctrler for ctrler in controllers if ctrler['transport'] != 'pcie'] diff --git a/curtin/commands/curthooks.py b/curtin/commands/curthooks.py index 63a24fe..0175447 100644 --- a/curtin/commands/curthooks.py +++ b/curtin/commands/curthooks.py @@ -20,6 +20,7 @@ from curtin import block from curtin import distro from curtin.block import iscsi from curtin.block import lvm +from curtin.block import nvme from curtin import net from curtin import futil from curtin.log import LOG @@ -1508,21 +1509,13 @@ def get_nvme_stas_controller_directives(cfg) -> Set[str]: directives to write in the [Controllers] section of a nvme-stas configuration file.""" directives = set() - if 'storage' not in cfg or not isinstance(cfg['storage'], dict): - return directives - storage = cfg['storage'] - if 'config' not in storage or storage['config'] == 'disabled': - return directives - config = storage['config'] - for item in config: - if item['type'] != 'nvme_controller': - continue - if item['transport'] != 'tcp': + for controller in nvme.get_nvme_controllers_from_config(cfg): + if controller['transport'] != 'tcp': continue controller_props = { 'transport': 'tcp', - 'traddr': item["tcp_addr"], - 'trsvcid': item["tcp_port"], + 'traddr': controller["tcp_addr"], + 'trsvcid': controller["tcp_port"], } props_str = ';'.join([f'{k}={v}' for k, v in controller_props.items()]) @@ -1535,23 +1528,15 @@ def nvmeotcp_get_nvme_commands(cfg) -> List[Tuple[str]]: """Parse the storage configuration and return a set of commands to run to bring up the NVMe over TCP block devices.""" commands: Set[Tuple[str]] = set() - if 'storage' not in cfg or not isinstance(cfg['storage'], dict): - return sorted(commands) - storage = cfg['storage'] - if 'config' not in storage or storage['config'] == 'disabled': - return sorted(commands) - config = storage['config'] - for item in config: - if item['type'] != 'nvme_controller': - continue - if item['transport'] != 'tcp': + for controller in nvme.get_nvme_controllers_from_config(cfg): + if controller['transport'] != 'tcp': continue commands.add(( 'nvme', 'connect-all', '--transport', 'tcp', - '--traddr', item['tcp_addr'], - '--trsvcid', str(item['tcp_port']), + '--traddr', controller['tcp_addr'], + '--trsvcid', str(controller['tcp_port']), )) return sorted(commands) diff --git a/tests/unittests/test_block_nvme.py b/tests/unittests/test_block_nvme.py new file mode 100644 index 0000000..070e28f --- /dev/null +++ b/tests/unittests/test_block_nvme.py @@ -0,0 +1,86 @@ +# This file is part of curtin. See LICENSE file for copyright and license info. + +import curtin.block.nvme as nvme +from .helpers import CiTestCase + + +class TestGetNvmeControllersFromConfig(CiTestCase): + def test_no_controller(self): + self.assertFalse(nvme.get_nvme_controllers_from_config({})) + self.assertFalse(nvme.get_nvme_controllers_from_config( + {"storage": False})) + self.assertFalse(nvme.get_nvme_controllers_from_config( + {"storage": {}})) + self.assertFalse(nvme.get_nvme_controllers_from_config({ + "storage": { + "config": "disabled", + }, + })) + self.assertFalse(nvme.get_nvme_controllers_from_config({ + "storage": { + "config": [ + {"type": "partition"}, + {"type": "mount"}, + {"type": "disk"}, + ], + }, + })) + + def test_one_controller(self): + expected = [{"type": "nvme_controller", "transport": "pcie"}] + + self.assertEqual(expected, nvme.get_nvme_controllers_from_config({ + "storage": { + "config": [ + {"type": "partition"}, + {"type": "mount"}, + {"type": "disk"}, + {"type": "nvme_controller", "transport": "pcie"}, + ], + }, + })) + + def test_multiple_controllers(self): + cfg = { + "storage": { + "config": [ + {"type": "partition"}, + { + "type": "nvme_controller", + "id": "nvme-controller-nvme0", + "transport": "tcp", + "tcp_addr": "1.2.3.4", + "tcp_port": "1111", + }, { + "type": "nvme_controller", + "id": "nvme-controller-nvme1", + "transport": "tcp", + "tcp_addr": "4.5.6.7", + "tcp_port": "1212", + }, { + "type": "nvme_controller", + "id": "nvme-controller-nvme2", + "transport": "pcie", + }, + ], + }, + } + ctrlers_id: list[str] = [] + for ctrler in nvme.get_nvme_controllers_from_config(cfg): + ctrlers_id.append(ctrler["id"]) + + self.assertEqual([ + "nvme-controller-nvme0", + "nvme-controller-nvme1", + "nvme-controller-nvme2", + ], ctrlers_id) + + ctrlers_id: list[str] = [] + for ctrler in nvme.get_nvme_controllers_from_config(cfg, + exclude_pcie=True): + ctrlers_id.append(ctrler["id"]) + + self.assertEqual([ + "nvme-controller-nvme0", + "nvme-controller-nvme1", + ], ctrlers_id) diff --git a/tests/unittests/test_curthooks.py b/tests/unittests/test_curthooks.py index 61cad9f..526a3fe 100644 --- a/tests/unittests/test_curthooks.py +++ b/tests/unittests/test_curthooks.py @@ -1413,7 +1413,13 @@ class TestDetectRequiredPackages(CiTestCase): 'btrfs': { 'id': 'format3', 'fstype': 'btrfs', 'type': 'format'}, 'xfs': { - 'id': 'format4', 'fstype': 'xfs', 'type': 'format'}} + 'id': 'format4', 'fstype': 'xfs', 'type': 'format'}, + 'nvme_controller_pcie': { + 'id': 'nvme_controller0', 'transport': 'pcie', + 'type': 'nvme_controller'}, + 'nvme_controller_tcp': { + 'id': 'nvme_controller1', 'transport': 'tcp', + 'type': 'nvme_controller'}} }, 'network': { 1: { @@ -1506,6 +1512,14 @@ class TestDetectRequiredPackages(CiTestCase): 'version': 1, 'items': ('bcache', 'lvm_volgroup', 'lvm_partition', 'ext2')}}, ('bcache-tools', 'lvm2', 'e2fsprogs')), + ({'storage': { + 'version': 1, + 'items': ('nvme_controller_pcie',)}}, + ()), + ({'storage': { + 'version': 1, + 'items': ('nvme_controller_tcp',)}}, + ('nvme-cli', 'nvme-stas')), )) def test_network_v1_detect(self): @@ -2018,126 +2032,52 @@ class TestCurthooksGrubDebconf(CiTestCase): class TestCurthooksNVMeOverTCP(CiTestCase): - def test_get_nvme_stas_controller_directives__no_nvme_controller(self): - self.assertFalse(curthooks.get_nvme_stas_controller_directives({ - "storage": { - "config": [ - {"type": "partition"}, - {"type": "mount"}, - {"type": "disk"}, - ], - }, - })) - - def test_get_nvme_stas_controller_directives__pcie_controller(self): - self.assertFalse(curthooks.get_nvme_stas_controller_directives({ - "storage": { - "config": [ - {"type": "nvme_controller", "transport": "pcie"}, - ], - }, - })) - - def test_get_nvme_stas_controller_directives__tcp_controller(self): - expected = {"controller = transport=tcp;traddr=1.2.3.4;trsvcid=1111"} - - result = curthooks.get_nvme_stas_controller_directives({ - "storage": { - "config": [ - { - "type": "nvme_controller", - "transport": "tcp", - "tcp_addr": "1.2.3.4", - "tcp_port": "1111", - }, - ], - }, - }) - self.assertEqual(expected, result) - - def test_get_nvme_stas_controller_directives__three_nvme_controllers(self): - expected = {"controller = transport=tcp;traddr=1.2.3.4;trsvcid=1111", - "controller = transport=tcp;traddr=4.5.6.7;trsvcid=1212"} - - result = curthooks.get_nvme_stas_controller_directives({ - "storage": { - "config": [ - { - "type": "nvme_controller", - "transport": "tcp", - "tcp_addr": "1.2.3.4", - "tcp_port": "1111", - }, { - "type": "nvme_controller", - "transport": "tcp", - "tcp_addr": "4.5.6.7", - "tcp_port": "1212", - }, { - "type": "nvme_controller", - "transport": "pcie", - }, - ], - }, - }) - self.assertEqual(expected, result) - - def test_get_nvme_stas_controller_directives__empty_conf(self): - self.assertFalse(curthooks.get_nvme_stas_controller_directives({})) - self.assertFalse(curthooks.get_nvme_stas_controller_directives( - {"storage": False})) - self.assertFalse(curthooks.get_nvme_stas_controller_directives( - {"storage": {}})) - self.assertFalse(curthooks.get_nvme_stas_controller_directives({ - "storage": { - "config": "disabled", - }, - })) - - def test_nvmeotcp_get_nvme_commands__no_nvme_controller(self): - self.assertFalse(curthooks.nvmeotcp_get_nvme_commands({ - "storage": { - "config": [ - {"type": "partition"}, - {"type": "mount"}, - {"type": "disk"}, - ], - }, - })) - - def test_nvmeotcp_get_nvme_commands__pcie_controller(self): - self.assertFalse(curthooks.nvmeotcp_get_nvme_commands({ - "storage": { - "config": [ - {"type": "nvme_controller", "transport": "pcie"}, - ], - }, - })) - - def test_nvmeotcp_get_nvme_commands__tcp_controller(self): - expected = [( + def test_no_nvme_controller(self): + with patch('curtin.block.nvme.get_nvme_controllers_from_config', + return_value=[]): + self.assertFalse( + curthooks.get_nvme_stas_controller_directives(None)) + self.assertFalse(curthooks.nvmeotcp_get_nvme_commands(None)) + + def test_pcie_controller(self): + controllers = [{'type': 'nvme_controller', 'transport': 'pcie'}] + with patch('curtin.block.nvme.get_nvme_controllers_from_config', + return_value=controllers): + self.assertFalse( + curthooks.get_nvme_stas_controller_directives(None)) + self.assertFalse(curthooks.nvmeotcp_get_nvme_commands(None)) + + def test_tcp_controller(self): + stas_expected = { + 'controller = transport=tcp;traddr=1.2.3.4;trsvcid=1111', + } + cmds_expected = [( "nvme", "connect-all", "--transport", "tcp", "--traddr", "1.2.3.4", "--trsvcid", "1111", ), ] - - result = curthooks.nvmeotcp_get_nvme_commands({ - "storage": { - "config": [ - { - "type": "nvme_controller", - "transport": "tcp", - "tcp_addr": "1.2.3.4", - "tcp_port": "1111", - }, - ], - }, - }) - self.assertEqual(expected, result) - - def test_nvmeotcp_get_nvme_commands__three_nvme_controllers(self): - expected = [( + controllers = [{ + "type": "nvme_controller", + "transport": "tcp", + "tcp_addr": "1.2.3.4", + "tcp_port": "1111", + }] + + with patch('curtin.block.nvme.get_nvme_controllers_from_config', + return_value=controllers): + stas_result = curthooks.get_nvme_stas_controller_directives(None) + cmds_result = curthooks.nvmeotcp_get_nvme_commands(None) + self.assertEqual(stas_expected, stas_result) + self.assertEqual(cmds_expected, cmds_result) + + def test_three_nvme_controllers(self): + stas_expected = { + "controller = transport=tcp;traddr=1.2.3.4;trsvcid=1111", + "controller = transport=tcp;traddr=4.5.6.7;trsvcid=1212", + } + cmds_expected = [( "nvme", "connect-all", "--transport", "tcp", "--traddr", "1.2.3.4", @@ -2149,40 +2089,29 @@ class TestCurthooksNVMeOverTCP(CiTestCase): "--trsvcid", "1212", ), ] - - result = curthooks.nvmeotcp_get_nvme_commands({ - "storage": { - "config": [ - { - "type": "nvme_controller", - "transport": "tcp", - "tcp_addr": "1.2.3.4", - "tcp_port": "1111", - }, { - "type": "nvme_controller", - "transport": "tcp", - "tcp_addr": "4.5.6.7", - "tcp_port": "1212", - }, { - "type": "nvme_controller", - "transport": "pcie", - }, - ], + controllers = [ + { + "type": "nvme_controller", + "transport": "tcp", + "tcp_addr": "1.2.3.4", + "tcp_port": "1111", + }, { + "type": "nvme_controller", + "transport": "tcp", + "tcp_addr": "4.5.6.7", + "tcp_port": "1212", + }, { + "type": "nvme_controller", + "transport": "pcie", }, - }) - self.assertEqual(expected, result) + ] - def test_nvmeotcp_get_nvme_commands__empty_conf(self): - self.assertFalse(curthooks.nvmeotcp_get_nvme_commands({})) - self.assertFalse(curthooks.nvmeotcp_get_nvme_commands( - {"storage": False})) - self.assertFalse(curthooks.nvmeotcp_get_nvme_commands( - {"storage": {}})) - self.assertFalse(curthooks.nvmeotcp_get_nvme_commands({ - "storage": { - "config": "disabled", - }, - })) + with patch('curtin.block.nvme.get_nvme_controllers_from_config', + return_value=controllers): + stas_result = curthooks.get_nvme_stas_controller_directives(None) + cmds_result = curthooks.nvmeotcp_get_nvme_commands(None) + self.assertEqual(stas_expected, stas_result) + self.assertEqual(cmds_expected, cmds_result) def test_nvmeotcp_get_ip_commands__ethernet_static(self): netcfg = """\
-- Mailing list: https://launchpad.net/~curtin-dev Post to : curtin-dev@lists.launchpad.net Unsubscribe : https://launchpad.net/~curtin-dev More help : https://help.launchpad.net/ListHelp