diff options
author | Olivier Gayot <[email protected]> | 2024-02-08 19:14:48 +0100 |
---|---|---|
committer | Olivier Gayot <[email protected]> | 2024-02-08 19:15:19 +0100 |
commit | 237053d9d18916dd72cf861280474d4df0e9fd24 (patch) | |
tree | 588052bee880a985b390404b9c6fec2711676e66 | |
parent | 6b297fe6226d79dfce90c08c4086497d143d30de (diff) | |
parent | e1e633c603dae27ce1d81c897a95b7a23196d0d0 (diff) |
Merge branch 'nvme-o-tcp-storageconfig'
https://code.launchpad.net/~ogayot/curtin/+git/curtin/+merge/458446
-rw-r--r-- | curtin/block/deps.py | 3 | ||||
-rw-r--r-- | curtin/block/schemas.py | 18 | ||||
-rw-r--r-- | curtin/commands/block_meta.py | 6 | ||||
-rw-r--r-- | curtin/commands/curthooks.py | 62 | ||||
-rw-r--r-- | curtin/storage_config.py | 53 | ||||
-rw-r--r-- | doc/topics/storage.rst | 42 | ||||
-rw-r--r-- | tests/data/probert_storage_bogus_wwn.json | 43 | ||||
-rw-r--r-- | tests/data/probert_storage_nvme_multipath.json | 43 | ||||
-rw-r--r-- | tests/data/probert_storage_nvme_uuid.json | 43 | ||||
-rw-r--r-- | tests/unittests/test_curthooks.py | 77 | ||||
-rw-r--r-- | tests/unittests/test_storage_config.py | 2 |
11 files changed, 387 insertions, 5 deletions
diff --git a/curtin/block/deps.py b/curtin/block/deps.py index 8a310b6a..e5370b69 100644 --- a/curtin/block/deps.py +++ b/curtin/block/deps.py @@ -69,6 +69,7 @@ def detect_required_packages_mapping(osfamily=DISTROS.debian): 'lvm_partition': ['lvm2'], 'lvm_volgroup': ['lvm2'], 'ntfs': ['ntfs-3g'], + 'nvme_controller': ['nvme-cli', 'nvme-stas'], 'raid': ['mdadm'], 'reiserfs': ['reiserfsprogs'], 'xfs': ['xfsprogs'], @@ -89,6 +90,7 @@ def detect_required_packages_mapping(osfamily=DISTROS.debian): 'lvm_partition': ['lvm2'], 'lvm_volgroup': ['lvm2'], 'ntfs': [], + 'nvme_controller': [], 'raid': ['mdadm'], 'reiserfs': [], 'xfs': ['xfsprogs'], @@ -109,6 +111,7 @@ def detect_required_packages_mapping(osfamily=DISTROS.debian): 'lvm_partition': ['lvm2'], 'lvm_volgroup': ['lvm2'], 'ntfs': [], + 'nvme_controller': [], 'raid': ['mdadm'], 'reiserfs': [], 'xfs': ['xfsprogs'], diff --git a/curtin/block/schemas.py b/curtin/block/schemas.py index 6a5c5b41..503e8703 100644 --- a/curtin/block/schemas.py +++ b/curtin/block/schemas.py @@ -144,6 +144,7 @@ DISK = { 'minimum': 0, 'maximum': 1 }, + 'nvme_controller': {'$ref': '#/definitions/ref_id'}, }, } DM_CRYPT = { @@ -275,6 +276,23 @@ MOUNT = { 'pattern': r'[0-9]'}, }, } +NVME = { + '$schema': 'http://json-schema.org/draft-07/schema#', + 'name': 'CURTIN-NVME', + 'title': 'curtin storage configuration for NVMe controllers', + 'description': ('Declarative syntax for specifying NVMe controllers.'), + 'definitions': definitions, + 'required': ['id', 'type', 'transport'], + 'type': 'object', + 'additionalProperties': False, + 'properties': { + 'id': {'$ref': '#/definitions/id'}, + 'type': {'const': 'nvme_controller'}, + 'transport': {'type': 'string'}, + 'tcp_port': {'type': 'integer'}, + 'tcp_addr': {'type': 'string'}, + }, +} PARTITION = { '$schema': 'http://json-schema.org/draft-07/schema#', 'name': 'CURTIN-PARTITION', diff --git a/curtin/commands/block_meta.py b/curtin/commands/block_meta.py index 8ba7a555..9fde9c65 100644 --- a/curtin/commands/block_meta.py +++ b/curtin/commands/block_meta.py @@ -2026,6 +2026,11 @@ def zpool_handler(info, storage_config, context): zfs_properties=fs_properties) +def nvme_controller_handler(info, storage_config, context): + '''Handle the NVMe Controller storage section. This is currently a no-op, + the section is handled in curthooks.''' + + def zfs_handler(info, storage_config, context): """ Create a zfs filesystem @@ -2215,6 +2220,7 @@ def meta_custom(args): 'bcache': bcache_handler, 'zfs': zfs_handler, 'zpool': zpool_handler, + 'nvme_controller': nvme_controller_handler, } if args.testmode: diff --git a/curtin/commands/curthooks.py b/curtin/commands/curthooks.py index 4be2cb47..d84e9ece 100644 --- a/curtin/commands/curthooks.py +++ b/curtin/commands/curthooks.py @@ -1,14 +1,16 @@ # This file is part of curtin. See LICENSE file for copyright and license info. import copy +import contextlib import glob import os +import pathlib import platform import re import sys import shutil import textwrap -from typing import List, Tuple +from typing import List, Set, Tuple from curtin import config from curtin import block @@ -1498,6 +1500,58 @@ def configure_mdadm(cfg, state_etcd, target, osfamily=DISTROS.debian): data=None, target=target) +def get_nvme_stas_controller_directives(cfg) -> Set[str]: + """Parse the storage configuration and return a set of "controller =" + 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': + continue + controller_props = { + 'transport': 'tcp', + 'traddr': item["tcp_addr"], + 'trsvcid': item["tcp_port"], + } + + props_str = ';'.join([f'{k}={v}' for k, v in controller_props.items()]) + directives.add(f'controller = {props_str}') + + return directives + + +def configure_nvme_stas(cfg, target): + """If any NVMe controller using the TCP transport is present in the storage + configuration, create a nvme-stas configuration so that the remote drives + can be made available at boot.""" + controllers = get_nvme_stas_controller_directives(cfg) + + if not controllers: + return + + LOG.info('NVMe-over-TCP configuration found' + ' , writing nvme-stas configuration') + target = pathlib.Path(target) + stas_dir = target / 'etc' / 'stas' + stas_dir.mkdir(parents=True, exist_ok=True) + with (stas_dir / 'stafd-curtin.conf').open('w', encoding='utf-8') as fh: + print('[Controllers]', file=fh) + for controller in controllers: + print(controller, file=fh) + + with contextlib.suppress(FileNotFoundError): + (stas_dir / 'stafd.conf').replace(stas_dir / '.stafd.conf.bak') + (stas_dir / 'stafd.conf').symlink_to('stafd-curtin.conf') + + def handle_cloudconfig(cfg, base_dir=None): """write cloud-init configuration files into base_dir. @@ -1760,6 +1814,12 @@ def builtin_curthooks(cfg, target, state): description="configuring raid (mdadm) service"): configure_mdadm(cfg, state_etcd, target, osfamily=osfamily) + with events.ReportEventStack( + name=stack_prefix + '/configuring-nvme-stas-service', + reporting_enabled=True, level="INFO", + description="configuring NVMe STorage Appliance Services"): + configure_nvme_stas(cfg, target) + if osfamily == DISTROS.debian: with events.ReportEventStack( name=stack_prefix + '/installing-kernel', diff --git a/curtin/storage_config.py b/curtin/storage_config.py index af7b6f3a..dae89f4c 100644 --- a/curtin/storage_config.py +++ b/curtin/storage_config.py @@ -50,6 +50,8 @@ STORAGE_CONFIG_TYPES = { 'bcache': StorageConfig(type='bcache', schema=schemas.BCACHE), 'dasd': StorageConfig(type='dasd', schema=schemas.DASD), 'disk': StorageConfig(type='disk', schema=schemas.DISK), + 'nvme_controller': StorageConfig(type='nvme_controller', + schema=schemas.NVME), 'dm_crypt': StorageConfig(type='dm_crypt', schema=schemas.DM_CRYPT), 'format': StorageConfig(type='format', schema=schemas.FORMAT), 'lvm_partition': StorageConfig(type='lvm_partition', @@ -159,12 +161,13 @@ def _stype_to_deps(stype): depends_keys = { 'bcache': {'backing_device', 'cache_device'}, 'dasd': set(), - 'disk': set(), + 'disk': {'nvme_controller'}, 'dm_crypt': {'volume'}, 'format': {'volume'}, 'lvm_partition': {'volgroup'}, 'lvm_volgroup': {'devices'}, 'mount': {'device'}, + 'nvme_controller': set(), 'partition': {'device'}, 'raid': {'devices', 'spare_devices', 'container'}, 'zfs': {'pool'}, @@ -184,6 +187,7 @@ def _stype_to_order_key(stype): 'lvm_partition': {'name'}, 'lvm_volgroup': {'name'}, 'mount': {'path'}, + 'nvme_controller': default_sort, 'partition': {'number'}, 'raid': default_sort, 'zfs': {'volume'}, @@ -204,7 +208,7 @@ def _validate_dep_type(source_id, dep_key, dep_id, sconfig): 'bcache': {'bcache', 'disk', 'dm_crypt', 'lvm_partition', 'partition', 'raid'}, 'dasd': {}, - 'disk': {'dasd'}, + 'disk': {'dasd', 'nvme_controller'}, 'dm_crypt': {'bcache', 'disk', 'dm_crypt', 'lvm_partition', 'partition', 'raid'}, 'format': {'bcache', 'disk', 'dm_crypt', 'lvm_partition', @@ -212,6 +216,7 @@ def _validate_dep_type(source_id, dep_key, dep_id, sconfig): 'lvm_partition': {'lvm_volgroup'}, 'lvm_volgroup': {'bcache', 'disk', 'dm_crypt', 'partition', 'raid'}, 'mount': {'format'}, + 'nvme_controller': {}, 'partition': {'bcache', 'disk', 'raid', 'partition'}, 'raid': {'bcache', 'disk', 'dm_crypt', 'lvm_partition', 'partition', 'raid'}, @@ -231,7 +236,7 @@ def _validate_dep_type(source_id, dep_key, dep_id, sconfig): if source_type not in depends: raise ValueError('Invalid source_type: %s' % source_type) if dep_type not in depends: - raise ValueError('Invalid type in depedency: %s' % dep_type) + raise ValueError('Invalid type in dependency: %s' % dep_type) source_deps = depends[source_type] result = dep_type in source_deps @@ -753,6 +758,11 @@ class BlockdevParser(ProbertParser): entry['ptable'] = ptype else: entry['ptable'] = schemas._ptable_unsupported + + match = re.fullmatch(r'/dev/(?P<ctrler>nvme\d+)n\d', devname) + if match is not None: + entry['nvme_controller'] = f'nvme-controller-{match["ctrler"]}' + return entry if entry['type'] == 'partition': @@ -1174,6 +1184,39 @@ class MountParser(ProbertParser): return (configs, errors) +class NVMeParser(ProbertParser): + + probe_data_key = 'nvme' + + def asdict(self, ctrler_id: str, ctrler_props): + action = { + 'type': 'nvme_controller', + 'id': f'nvme-controller-{ctrler_id}', + 'transport': ctrler_props['NVME_TRTYPE'], + } + if action['transport'] == 'tcp': + action['tcp_addr'] = ctrler_props['NVME_TRADDR'] + action['tcp_port'] = int(ctrler_props['NVME_TRSVCID']) + + return action + + def parse(self): + """ parse probert 'nvme' data format """ + + errors = [] + configs = [] + for ctrler_id, ctrler_props in self.class_data.items(): + entry = self.asdict(ctrler_id, ctrler_props) + if entry: + try: + validate_config(entry) + except ValueError as e: + errors.append(e) + continue + configs.append(entry) + return configs, errors + + class ZfsParser(ProbertParser): probe_data_key = 'zfs' @@ -1318,6 +1361,7 @@ def extract_storage_config(probe_data, strict=False): 'lvm': LvmParser, 'raid': RaidParser, 'mount': MountParser, + 'nvme': NVMeParser, 'zfs': ZfsParser, } configs = [] @@ -1339,11 +1383,12 @@ def extract_storage_config(probe_data, strict=False): raids = [cfg for cfg in configs if cfg.get('type') == 'raid'] dmcrypts = [cfg for cfg in configs if cfg.get('type') == 'dm_crypt'] mounts = [cfg for cfg in configs if cfg.get('type') == 'mount'] + nvmes = [cfg for cfg in configs if cfg.get('type') == 'nvme_controller'] bcache = [cfg for cfg in configs if cfg.get('type') == 'bcache'] zpool = [cfg for cfg in configs if cfg.get('type') == 'zpool'] zfs = [cfg for cfg in configs if cfg.get('type') == 'zfs'] - ordered = (dasd + disk + part + format + lvols + lparts + raids + + ordered = (nvmes + dasd + disk + part + format + lvols + lparts + raids + dmcrypts + mounts + bcache + zpool + zfs) final_config = {'storage': {'version': 2, 'config': ordered}} diff --git a/doc/topics/storage.rst b/doc/topics/storage.rst index 97e900d7..7650c4dd 100644 --- a/doc/topics/storage.rst +++ b/doc/topics/storage.rst @@ -71,6 +71,7 @@ commands include: - Bcache Command (``bcache``) - Zpool Command (``zpool``) **Experimental** - ZFS Command (``zfs``)) **Experimental** +- NVMe Controller Command (``nvme_controller``) **Experimental** - Device "Command" (``device``) Any action that refers to a block device (so things like ``partition`` @@ -331,6 +332,11 @@ configuration dictionary. Currently the value is informational only. Curtin already detects whether disks are part of a multipath and selects one member path to operate upon. +**nvme_controller**: *<NVMe controller id>* + +If the disk is a NVMe SSD, the ``nvme_controller`` key can be set to the +identifier of a ``nvme_controller`` object. This will help to determine the +type of transport used (e.g., PCIe vs TCP). **Config Example**:: @@ -1205,6 +1211,42 @@ passed to the ZFS dataset creation command. canmount: noauto mountpoint: / +NVMe Controller Command +~~~~~~~~~~~~~~~~~~~~~~~ +NVMe Controller Commands (and NVMe over TCP support in general) are +**experimental**. + +The nvme_controller command describes how to communicate with a given NVMe +controller. + +**transport**: *pcie, tcp* + +The ``transport`` key specifies whether the communication with the NVMe +controller operates over PCIe or over TCP. Other transports like RDMA and FC +(aka. Fiber Channel) are not supported at the moment. + +**tcp_addr**: *<ip address>* + +The ``tcp_addr`` key specifies the IP where the NVMe controller can be reached. +This key is only meaningful in conjunction with ``transport: tcp``. + +**tcp_port**: *port* + +The ``tcp_port`` key specifies the TCP port where the NVMe controller can be +reached. This key is only meaningful in conjunction with ``transport: tcp``. + +**Config Example**:: + + - type: nvme_controller + id: nvme-controller-nvme0 + transport: pcie + + - type: nvme_controller + id: nvme-controller-nvme1 + transport: tcp + tcp_addr: 172.16.82.78 + tcp_port: 4420 + Device "Command" ~~~~~~~~~~~~~~~~ diff --git a/tests/data/probert_storage_bogus_wwn.json b/tests/data/probert_storage_bogus_wwn.json index b3211fd9..d8175156 100644 --- a/tests/data/probert_storage_bogus_wwn.json +++ b/tests/data/probert_storage_bogus_wwn.json @@ -1254,5 +1254,48 @@ "bcache": { "backing": {}, "caching": {} + }, + "nvme": { + "nvme0": { + "DEVNAME": "/dev/nvme0", + "DEVPATH": "/devices/pci0000:00/0000:00:1c.4/0000:04:00.0/nvme/nvme0", + "MAJOR": "238", + "MINOR": "0", + "NVME_TRTYPE": "pcie", + "SUBSYSTEM": "nvme", + "attrs": { + "address": "0000:04:00.0", + "cntlid": "5", + "cntrltype": "io", + "dctype": "none", + "dev": "238:0", + "device": null, + "firmware_rev": "2B2QEXM7", + "hmb": "1", + "kato": "0", + "model": "SAMSUNG SSD 970 EVO Plus 500GB", + "numa_node": "0", + "power/async": "disabled", + "power/autosuspend_delay_ms": null, + "power/control": "auto", + "power/pm_qos_latency_tolerance_us": "100000", + "power/runtime_active_kids": "0", + "power/runtime_active_time": "0", + "power/runtime_enabled": "disabled", + "power/runtime_status": "unsupported", + "power/runtime_suspended_time": "0", + "power/runtime_usage": "0", + "queue_count": "9", + "rescan_controller": null, + "reset_controller": null, + "serial": "S4EVNJ0N203359W", + "sqsize": "1023", + "state": "live", + "subsysnqn": "nqn.1994-11.com.samsung:nvme:970M.2:S4EVNJ0N203359W", + "subsystem": "nvme", + "transport": "pcie", + "uevent": "MAJOR=238\nMINOR=0\nDEVNAME=nvme0\nNVME_TRTYPE=pcie" + } + } } } diff --git a/tests/data/probert_storage_nvme_multipath.json b/tests/data/probert_storage_nvme_multipath.json index 56a761d9..97183689 100644 --- a/tests/data/probert_storage_nvme_multipath.json +++ b/tests/data/probert_storage_nvme_multipath.json @@ -306,5 +306,48 @@ "uevent": "MAJOR=259\nMINOR=4\nDEVNAME=nvme0n1p3\nDEVTYPE=partition\nPARTN=3" } } + }, + "nvme": { + "nvme0": { + "DEVNAME": "/dev/nvme0", + "DEVPATH": "/devices/pci0000:00/0000:00:1d.0/0000:03:00.0/nvme/nvme0", + "MAJOR": "238", + "MINOR": "0", + "NVME_TRTYPE": "pcie", + "SUBSYSTEM": "nvme", + "attrs": { + "address": "0000:03:00.0", + "cntlid": "5", + "cntrltype": "io", + "dctype": "none", + "dev": "238:0", + "device": null, + "firmware_rev": "GPJA0B3Q", + "hmb": "1", + "kato": "0", + "model": "SAMSUNG MZPLL3T2HAJQ-00005", + "numa_node": "0", + "power/async": "disabled", + "power/autosuspend_delay_ms": null, + "power/control": "auto", + "power/pm_qos_latency_tolerance_us": "100000", + "power/runtime_active_kids": "0", + "power/runtime_active_time": "0", + "power/runtime_enabled": "disabled", + "power/runtime_status": "unsupported", + "power/runtime_suspended_time": "0", + "power/runtime_usage": "0", + "queue_count": "9", + "rescan_controller": null, + "reset_controller": null, + "serial": "S4CCNE0M300015", + "sqsize": "1023", + "state": "live", + "subsysnqn": "nqn.1994-11.com.samsung:nvme:MZPLL3T2HAJQ-00005M.2:S64DMZ0T351601T ", + "subsystem": "nvme", + "transport": "pcie", + "uevent": "MAJOR=238\nMINOR=0\nDEVNAME=nvme0\nNVME_TRTYPE=pcie" + } + } } } diff --git a/tests/data/probert_storage_nvme_uuid.json b/tests/data/probert_storage_nvme_uuid.json index c54239bf..d93dffcb 100644 --- a/tests/data/probert_storage_nvme_uuid.json +++ b/tests/data/probert_storage_nvme_uuid.json @@ -306,5 +306,48 @@ "uevent": "MAJOR=259\nMINOR=4\nDEVNAME=nvme0n1p3\nDEVTYPE=partition\nPARTN=3" } } + }, + "nvme": { + "nvme0": { + "DEVNAME": "/dev/nvme0", + "DEVPATH": "/devices/pci0000:00/0000:00:1d.0/0000:03:00.0/nvme/nvme0", + "MAJOR": "238", + "MINOR": "0", + "NVME_TRTYPE": "pcie", + "SUBSYSTEM": "nvme", + "attrs": { + "address": "0000:03:00.0", + "cntlid": "5", + "cntrltype": "io", + "dctype": "none", + "dev": "238:0", + "device": null, + "firmware_rev": "GPJA0B3Q", + "hmb": "1", + "kato": "0", + "model": "SAMSUNG MZPLL3T2HAJQ-00005", + "numa_node": "0", + "power/async": "disabled", + "power/autosuspend_delay_ms": null, + "power/control": "auto", + "power/pm_qos_latency_tolerance_us": "100000", + "power/runtime_active_kids": "0", + "power/runtime_active_time": "0", + "power/runtime_enabled": "disabled", + "power/runtime_status": "unsupported", + "power/runtime_suspended_time": "0", + "power/runtime_usage": "0", + "queue_count": "9", + "rescan_controller": null, + "reset_controller": null, + "serial": "S4CCNE0M300015", + "sqsize": "1023", + "state": "live", + "subsysnqn": "nqn.1994-11.com.samsung:nvme:MZPLL3T2HAJQ-00005M.2:S64DMZ0T351601T ", + "subsystem": "nvme", + "transport": "pcie", + "uevent": "MAJOR=238\nMINOR=0\nDEVNAME=nvme0\nNVME_TRTYPE=pcie" + } + } } } diff --git a/tests/unittests/test_curthooks.py b/tests/unittests/test_curthooks.py index 0728260b..e615b38a 100644 --- a/tests/unittests/test_curthooks.py +++ b/tests/unittests/test_curthooks.py @@ -2017,6 +2017,83 @@ class TestCurthooksGrubDebconf(CiTestCase): self.m_debconf.assert_called_with(expectedcfg, target) +class TestCurthooksNVMeStas(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", + }, + })) + + class TestUefiFindGrubDeviceIds(CiTestCase): def _sconfig(self, cfg): diff --git a/tests/unittests/test_storage_config.py b/tests/unittests/test_storage_config.py index caaac29b..7b0f68ca 100644 --- a/tests/unittests/test_storage_config.py +++ b/tests/unittests/test_storage_config.py @@ -1087,6 +1087,7 @@ class TestExtractStorageConfig(CiTestCase): 'serial': 'SAMSUNG MZPLL3T2HAJQ-00005_S4CCNE0M300015', 'type': 'disk', 'wwn': 'eui.344343304d3000150025384500000004', + 'nvme_controller': 'nvme-controller-nvme0', } self.assertEqual(1, len(disks)) self.assertEqual(expected_dict, disks[0]) @@ -1104,6 +1105,7 @@ class TestExtractStorageConfig(CiTestCase): 'serial': 'SAMSUNG MZPLL3T2HAJQ-00005_S4CCNE0M300015', 'type': 'disk', 'wwn': 'uuid.344343304d3000150025384500000004', + 'nvme_controller': 'nvme-controller-nvme0', } self.assertEqual(1, len(disks)) self.assertEqual(expected_dict, disks[0]) |