Skip to content

Commit

Permalink
Merge pull request #4514 from VladimirSlavik/master-port-nvme
Browse files Browse the repository at this point in the history
Port NVMe over fabrics GUI to Fedora
  • Loading branch information
vojtechtrefny authored Feb 28, 2024
2 parents e3a550d + c9686d6 commit 1cd55f4
Show file tree
Hide file tree
Showing 8 changed files with 976 additions and 238 deletions.
4 changes: 4 additions & 0 deletions anaconda.spec.in
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,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 @@ -262,6 +264,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

0 comments on commit 1cd55f4

Please sign in to comment.