Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backport NVMe-oF to RHEL-10 #5611

Merged
merged 5 commits into from
May 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions anaconda.spec.in
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,8 @@ Requires: f2fs-tools
Requires: xfsprogs
Requires: dosfstools
Requires: e2fsprogs
# External tooling for managing NVMe-FC devices in the installation environment
Recommends: nvme-cli

%description install-env-deps
The anaconda-install-env-deps metapackage lists all installation environment
Expand Down Expand Up @@ -264,6 +266,8 @@ Requires: rpm-ostree >= %{rpmostreever}
Requires: ostree
# used by ostree command for native containers
Requires: skopeo
# External tooling for managing NVMe-FC devices in the installation environment
Requires: nvme-cli

%description install-img-deps
The anaconda-install-img-deps metapackage lists all boot.iso installation
Expand Down
9 changes: 9 additions & 0 deletions docs/release-notes/nvme-over-fabrics.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
:Type: Storage
:Summary: NVMe Fabrics support

:Description:
Anaconda now recognizes NVMe Fabrics drives. These drives are now shown in the
Advanced Storage screen, together with further details.

:Links:
- https://github.com/rhinstaller/anaconda/pull/4514
9 changes: 9 additions & 0 deletions pyanaconda/modules/common/structures/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,15 @@ def attrs(self) -> Dict[Str, Str]:
target
path-id

Attributes for NVMe Fabrics:
nsid
eui64
nguid
controllers-id
transports-type
transports-address
subsystems-nqn

Attributes for ZFCP:
fcp-lun
wwpn
Expand Down
34 changes: 34 additions & 0 deletions pyanaconda/modules/storage/devicetree/viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
# Red Hat, Inc.
#
from abc import abstractmethod, ABC
from functools import partial

from blivet.formats import get_format
from blivet.size import Size
Expand Down Expand Up @@ -104,6 +105,8 @@ def get_device_data(self, name):
self._set_device_data_fcoe(device, data)
elif device.type == "iscsi":
self._set_device_data_iscsi(device, data)
elif device.type == "nvme-fabrics":
self._set_device_data_nvme_fabrics(device, data)
elif device.type == "zfcp":
self._set_device_data_zfcp(device, data)

Expand Down Expand Up @@ -150,6 +153,18 @@ def _set_device_data_iscsi(self, device, data):
data.attrs["target"] = self._get_attribute(device, "target")
data.attrs["path-id"] = self._get_attribute(device, "id_path")

def _set_device_data_nvme_fabrics(self, device, data):
"""Set data for an NVMe Fabrics device."""
data.attrs["nsid"] = self._get_attribute(device, "nsid")
data.attrs["eui64"] = self._get_attribute(device, "eui64")
data.attrs["nguid"] = self._get_attribute(device, "nguid")

get_attrs = partial(self._get_attribute_list, device.controllers)
data.attrs["controllers-id"] = get_attrs("id")
data.attrs["transports-type"] = get_attrs("transport")
data.attrs["transports-address"] = get_attrs("transport_address")
data.attrs["subsystems-nqn"] = get_attrs("subsysnqn")

def _set_device_data_zfcp(self, device, data):
"""Set data for a ZFCP device."""
data.attrs["fcp-lun"] = self._get_attribute(device, "fcp_lun")
Expand Down Expand Up @@ -271,6 +286,25 @@ def _get_attribute(self, obj, name):

return str(value)

def _get_attribute_list(self, iterable, name):
"""Get a list of attributes of the given objects.

Create a comma-separated list of sorted unique attribute values.
See the _get_attribute method for more info.

:param iterable: a list of objects
:param name: an attribute name
:return: a string or None
"""
# Collect values.
values = [self._get_attribute(obj, name) for obj in iterable]

# Skip duplicates and unset values.
values = set(filter(None, values))

# Format sorted values if any.
return ", ".join(sorted(values)) or None

def _prune_attributes(self, attrs):
"""Prune the unset values of attributes.

Expand Down
933 changes: 719 additions & 214 deletions pyanaconda/ui/gui/spokes/advanced_storage.glade

Large diffs are not rendered by default.

124 changes: 104 additions & 20 deletions pyanaconda/ui/gui/spokes/advanced_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,19 @@
PAGE_SEARCH = 0
PAGE_MULTIPATH = 1
PAGE_OTHER = 2
PAGE_Z = 3
PAGE_NVMEFABRICS = 3
PAGE_Z = 4
# The Z page must be last = highest number, because it is dynamically removed, which would reorder
# the items and invalidate the indices hardcoded here.

DiskStoreRow = namedtuple("DiskStoreRow", [
"visible", "selected", "mutable",
"name", "type", "model", "capacity",
"vendor", "interconnect", "serial",
"wwid", "paths", "port", "target",
"lun", "ccw", "wwpn", "namespace", "mode"
"lun", "ccw", "wwpn", "namespace", "mode",
"controllers", "transport", "transport_address",
"subsystem_nqn", "namespace_id"
])


Expand All @@ -68,26 +73,40 @@ def create_row(device_data, selected, mutable):
:param mutable: False if the device is protected, otherwise True
:return: an instance of DiskStoreRow
"""
device = device_data
attrs = device_data.attrs

controller_ids = attrs.get("controllers-id", "").split(", ")
transports_type = attrs.get("transports-type", "").split(", ")
transports_address = attrs.get("transports-address", "").split(", ")
subsystems_nqn = attrs.get("subsystems-nqn", "").split(", ")
namespace_ids = list(filter(None, map(attrs.get, ["eui64", "nguid", "uuid"])))

return DiskStoreRow(
visible=True,
selected=selected,
mutable=mutable and not device_data.protected,
name=device_data.name,
type=device_data.type,
model=device_data.attrs.get("model", ""),
capacity=str(Size(device_data.size)),
vendor=device_data.attrs.get("vendor", ""),
interconnect=device_data.attrs.get("bus", ""),
serial=device_data.attrs.get("serial", ""),
wwid=device_data.attrs.get("path-id", "") or device_data.attrs.get("wwn", ""),
paths="\n".join(device_data.parents),
port=device_data.attrs.get("port", ""),
target=device_data.attrs.get("target", ""),
lun=device_data.attrs.get("lun", "") or device_data.attrs.get("fcp-lun", ""),
ccw=device_data.attrs.get("hba-id", ""),
wwpn=device_data.attrs.get("wwpn", ""),
namespace=device_data.attrs.get("namespace", ""),
mode=device_data.attrs.get("mode", "")
mutable=mutable and not device.protected,
name=device.name,
type=device.type,
model=attrs.get("model", ""),
capacity=str(Size(device.size)),
vendor=attrs.get("vendor", ""),
interconnect=attrs.get("bus", ""),
serial=attrs.get("serial", ""),
wwid=attrs.get("path-id", ""),
paths="\n".join(device.parents),
port=attrs.get("port", ""),
target=attrs.get("target", ""),
lun=attrs.get("lun", "") or attrs.get("fcp-lun", ""),
ccw=attrs.get("hba-id", ""),
wwpn=attrs.get("wwpn", ""),
namespace=attrs.get("namespace", "") or attrs.get("nsid", ""),
mode=attrs.get("mode", ""),
controllers="\n".join(controller_ids),
transport="\n".join(transports_type),
transport_address="\n".join(transports_address),
subsystem_nqn="\n".join(subsystems_nqn),
namespace_id="\n".join(namespace_ids),
)


Expand Down Expand Up @@ -429,13 +448,73 @@ def _filter_func(self, filter_by, row):
return False


class NVMeFabricsPage(FilterPage):
# Match these to nvmefTypeCombo ids in glade
SEARCH_TYPE_CONTROLLER = 'Controller'
SEARCH_TYPE_TRANSPORT = 'Transport'
SEARCH_TYPE_SUBSYSTEM_NQN = 'Subsystem NQN'
SEARCH_TYPE_NAMESPACE_ID = 'Namespace ID'

def __init__(self, builder):
super().__init__(builder, "nvmefModel", "nvmefTypeCombo")
self._controller_entry = self._builder.get_object("nvmefControllerEntry")
self._transport_combo = self._builder.get_object("nvmefTransportCombo")
self._address_entry = self._builder.get_object("nvmefTransportAddressEntry")
self._subsystem_nqn_entry = self._builder.get_object("nvmefSubsystemNqnEntry")
self._namespace_id_entry = self._builder.get_object("nvmefNamespaceIdEntry")

def is_member(self, device_type):
return device_type == "nvme-fabrics"

def setup(self, store, disks, selected_names, protected_names):
transports = set()

for device_data in disks:
row = create_row(
device_data,
device_data.name in selected_names,
device_data.name not in protected_names,
)
store.append([*row])
transports.update(row.transport.split("\n"))

self._setup_combo(self._transport_combo, transports)
self._transport_combo.set_active(0)
self._setup_search_type()

def clear(self):
self._controller_entry.set_text("")
self._transport_combo.set_active(0)
self._address_entry.set_text("")
self._subsystem_nqn_entry.set_text("")
self._namespace_id_entry.set_text("")

def _filter_func(self, filter_by, row):
if filter_by == self.SEARCH_TYPE_CONTROLLER:
return self._controller_entry.get_text().strip() in row.controllers

if filter_by == self.SEARCH_TYPE_TRANSPORT:
transports = [""] + row.transport.split("\n")

return self._transport_combo.get_active_text() in transports \
and self._address_entry.get_text().strip() in row.transport_address

if filter_by == self.SEARCH_TYPE_SUBSYSTEM_NQN:
return self._subsystem_nqn_entry.get_text().strip() in row.subsystem_nqn

if filter_by == self.SEARCH_TYPE_NAMESPACE_ID:
return self._namespace_id_entry.get_text().strip() in row.namespace_id

return False


class FilterSpoke(NormalSpoke):
"""
.. inheritance-diagram:: FilterSpoke
:parts: 3
"""
builderObjects = ["diskStore", "filterWindow",
"searchModel", "multipathModel", "otherModel", "zModel"]
"searchModel", "multipathModel", "otherModel", "zModel", "nvmefModel"]
mainWidgetName = "filterWindow"
uiFile = "spokes/advanced_storage.glade"
category = SystemCategory
Expand Down Expand Up @@ -483,6 +562,7 @@ def initialize(self):
PAGE_SEARCH: SearchPage(self.builder),
PAGE_MULTIPATH: MultipathPage(self.builder),
PAGE_OTHER: OtherPage(self.builder),
PAGE_NVMEFABRICS: NVMeFabricsPage(self.builder),
PAGE_Z: ZPage(self.builder),
}

Expand Down Expand Up @@ -676,6 +756,10 @@ def on_z_type_combo_changed(self, combo):
self._set_notebook_page("zTypeNotebook", combo.get_active())
self._refilter_current_page()

def on_nvmef_type_combo_changed(self, combo):
self._set_notebook_page("nvmefTypeNotebook", combo.get_active())
self._refilter_current_page()

def _set_notebook_page(self, notebook_name, page_index):
notebook = self.builder.get_object(notebook_name)
notebook.set_current_page(page_index)
Expand Down
5 changes: 3 additions & 2 deletions pyanaconda/ui/lib/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,14 +322,15 @@ def is_local_disk(device_type):
While technically local disks, zFCP and NVDIMM devices are
advanced storage and should not be considered local.

:param device_type: a device type
:return: True or False
:param str device_type: a device type
:return bool: True or False
"""
return device_type not in (
"dm-multipath",
"iscsi",
"fcoe",
"zfcp",
"nvme-fabrics",
)


Expand Down
Loading
Loading