Skip to content
Draft
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
30 changes: 30 additions & 0 deletions repos/system_upgrade/common/actors/checknvme/actor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from leapp.actors import Actor
from leapp.libraries.actor import checknvme
from leapp.models import NVMEInfo
from leapp.reporting import Report
from leapp.tags import ChecksPhaseTag, IPUWorkflowTag


class CheckLuks(Actor):
"""
Check if NVMe devices are used and possibly register additional actions.

To be able to boot correctly the storage with NVMe devices, the initramfs
needs to contain nvmf dracut module with additional possibly required data.
Register all required actions that should happen to handle correctly systems
with NVMe devices.

Currently covered:
* NVMe (PCIe)
* NVMe-FC
"""
# FIXME(pstodulk): update the description once the actor is fully
# implemented

name = 'check_nvme'
consumes = (NVMEInfo,)
produces = (Report, TargetUserSpaceUpgradeTasks, UpgradeInitramfsTasks)
tags = (ChecksPhaseTag, IPUWorkflowTag)

def process(self):
checknvme.process()
112 changes: 112 additions & 0 deletions repos/system_upgrade/common/actors/checknvme/libraries/checknvme.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
from leapp import reporting
from leapp.libraries.stdlib import api
from leapp.models import (
CopyFile,
DracutModule,
NVMEInfo,
TargetUserSpaceUpgradeTasks,
UpgradeInitramfsTasks
)
from leapp.reporting import create_report

FMT_LIST_SEPARATOR = '\n - '
BROKEN_TRANSPORT_TYPES = ['tcp', 'rdma']
SAFE_TRANSPORT_TYPES = ['pcie', 'fc']
RQ_RPMS_CONTAINER = [
'iproute',
'jq',
'nvme-cli',
'sed',
]
RQ_CONFIG_FILES = [
'/etc/nvme/hostid',
'/etc/nvme/hostnqn',
]
"""
These config files seems to be required, but potentially they could be missing
on the source system. Tracking them explicitely.

Check failure on line 27 in repos/system_upgrade/common/actors/checknvme/libraries/checknvme.py

View workflow job for this annotation

GitHub Actions / Check for spelling errors

explicitely ==> explicitly
"""


def _format_list(data, sep=FMT_LIST_SEPARATOR, callback_sort=sorted, limit=0):
# NOTE(pstodulk): Teaser O:-> https://issues.redhat.com/browse/RHEL-126447
if callback_sort is None:
callback_sort = lambda x: x
res = ['{}{}'.format(sep, item) for item in callback_sort(data)]
if limit:
return ''.join(res[:limit])
return ''.join(res)


def _register_upgrade_tasks():
"""
Register tasks that should happen during IPU to handle NVMe devices
successfully.
"""
api.produce(TargetUserSpaceUpgradeTasks(
copy_files=[CopyFile(src='/etc/nvme/')],
install_rpms=RQ_RPMS_CONTAINER)
)

api.produce(UpgradeInitramfsTasks(
# TODO(pstodulk): the module should take all files as it needs them.
# Drop the comment when verified, uncomment the line below otherwise
# include_files=RQ_CONFIG_FILES,
include_dracut_modules=[DracutModule(name='nvmf')])
)


def report_missing_configs(nvmeinfo, nvmeof_devices):
# TODO(pstodulk)
pass


def check_nvme(nvmeinfo):
"""
Check the system can be configured with discovered NVMe configuration.

In case the discovered configuration is considered

Return True if upgrade can continue, False otherwise.
"""
upgrade_can_continue = True
unhandled_devices = []
safe_devices = []
nvmeof_devices = []
for device in nvmeinfo.devices:
if device.transport in BROKEN_TRANSPORT_TYPES:
unhandled_devices.append(device)
elif device.transport in SAFE_TRANSPORT_TYPES:
safe_devices.append(device)
else:
# TODO(pstodulk): hm? loop, apple-..., etc. types
if device.transport != 'pcie':
nvmeof_devices.append(device)


if unhandled_devices:
# TODO(pstodulk): what we will do here? It's not clear whether to stop
# the upgrade or not, as it could be about devices used for a data
# storage - unrelated to the system partitions / filesystems.
# maybe just report potential risk?

if nvmeof_devices and not (nvmeinfo.hostid and nvmeinfo.hostnqn):
# NOTE(pstodulk): hostid and hostnqn are mandatory for NVMe-oF devices.
# That means practically FC, RDMA, TCP. Let's inform user the upgrade
# is blocked and they must cofigure the system properly to be able to

Check failure on line 96 in repos/system_upgrade/common/actors/checknvme/libraries/checknvme.py

View workflow job for this annotation

GitHub Actions / Check for spelling errors

cofigure ==> configure
# upgrade
upgrade_can_continue = False
report_missing_configs(nvmeinfo, nvmeof_devices)

return upgrade_can_continue


def process():
nvmeinfo = next(api.consume(NVMEInfo), None)
if not nvmeinfo or not nvmeinfo.devices:
# Nothing to do
return

if check_nvme(nvmeinfo):
_register_upgrade_tasks()

Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""
Unit tests for checknvme actor

Skip isort as it's kind of broken when mixing grid import and one line imports

isort:skip_file
"""

# from leapp.models import (
# )
# from leapp.reporting import Report
# from leapp.utils.report import is_inhibitor

def test_missing():
# FIXME(pstodulk): add the missint tests. Created just an empty boilerplate
pass
26 changes: 26 additions & 0 deletions repos/system_upgrade/common/actors/scannvme/actor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from leapp.actors import Actor
from leapp.libraries.actor import scannvme
from leapp.models import NVMEInfo
from leapp.reporting import Report
from leapp.tags import FactsCollectionPhaseTag, IPUWorkflowTag


class ScanNVMe(Actor):
"""
Detect existing NVMe devices.

The detection is performed by checking content under /sys/class/nvme/
directory where all NVMe devices should be listed. Additional information
is collected from the present files under each specific device.

Namely the NVMe transport type and the device name is collected at this
moment.
"""

name = 'scan_nvme'
consumes = ()
produces = (NVMEInfo)
tags = (FactsCollectionPhaseTag, IPUWorkflowTag)

def process(self):
scannvme.process()
93 changes: 93 additions & 0 deletions repos/system_upgrade/common/actors/scannvme/libraries/scannvme.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import os

from leapp import reporting
from leapp.libraries.stdlib import api
from leapp.libraries.utils import read_file
from leapp.models import (
NVMEDevice,
NVMEInfo,
)
from leapp.reporting import create_report

NVME_CLASS_DIR = '/sys/class/nvme'
NVME_CONF_DIR = '/etc/nvme'
NVME_CONF_HOSTID = '/etc/nvme/hostid',
NVME_CONF_HOSTNQN = '/etc/nvme/hostnqn',


class NVMEMissingTransport(Exception):
def __init__(self, message):
super().__init__(message)
self.message = message


def _get_transport_type(device_path):
tpath = os.path.join(device_path, 'transport')
if not os.path.exists(tpath):
raise NVMEMissingTransport(f'The {tpath} file is missing.')

transport = read_file(tpath).strip()
if not transport:
raise NVMEMissingTransport(f'The transport type is not defined.')

return transport


def scan_device(device_name):
device_path = os.path.join(NVME_CLASS_DIR, device_name)
if not os.path.isdir(device_path):
api.current_logger().warning(
f'Cannot scan NVMe device: Following path is not dir: {device_path}'
)
return

try:
transport = _get_transport_type(device_path)
except NVMEMissingTransport as e:
# unexpected; seatbelt - skipping tests
api.current_logger().warning(
f'Skipping {device_name} NVMe device: Cannot detect transport type: {e.message}'
)
return

return NVMEDevice(
sys_class_path=device_path,
name=device_name,
transport=transport
)


def get_hostid(fpath=NVME_CONF_HOSTID):
if not os.path.exists(fpath):
api.current_logger().debug('NVMe hostid config file is missing.')
return
return read_file(fpath).strip()


def get_hostnqn(fpath=NVME_CONF_HOSTNQN):
if not os.path.exists(fpath):
api.current_logger().debug('NVMe hostnqn config file is missing.')
return
return read_file(fpath).strip()


def process():
if not os.path.path.isdir(NVME_CLASS_DIR):
api.current_logger().debug(
f'NVMe is not active: {NVME_CLASS_DIR} does not exist.'
)
return

devices = [scan_device(device_name) for device_name in os.listdir(NVME_CLASS_DIR)]
# drop possible None values from the list
devices = [dev for dev in devices if dev is None]
if not devices:
# NOTE(pstodulk): This could be suspicious possibly.
api.current_logger().warning('No NVMe device detected but NVMe seems active.')
return

api.produce(NVMEInfo(
devices=devices,
hostnqn=get_hostnqn(),
hostid=get_hostid(),
))
16 changes: 16 additions & 0 deletions repos/system_upgrade/common/actors/scannvme/tests/test_scannvme.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""
Unit tests for checknvme actor

Skip isort as it's kind of broken when mixing grid import and one line imports

isort:skip_file
"""

# from leapp.models import (
# )
# from leapp.reporting import Report
# from leapp.utils.report import is_inhibitor

def test_missing():
# FIXME(pstodulk): add the missint tests. Created just an empty boilerplate
pass
68 changes: 68 additions & 0 deletions repos/system_upgrade/common/models/nvme.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
from leapp.models import fields, Model
from leapp.topics import SystemInfoTopic


class NVMEDevice(Model):
"""
Provide information about particular detected NVMe device.

This model is not expected to be produced or consumed by any actors as
a standalone message. It is always part of NVMEInfo message.
"""

sys_class_path = fields.String()
"""
Path to the NVMe device used for the detection.

Usually: "/sys/class/nvme/<dev-name>".
"""

name = fields.String()
"""
The NVMe device name.
"""

transport = fields.String()
"""
The NVMe transport type of the NVMe device.

Expected usual values:
* pcie
* tcp
* rdma
* fc

NOTE: Based on the used kernel, additional values are possible and in future
the list could be even extended. As just specific values are important for
us, I am keeping it as a string to allow any possible value that appears.
"""


class NVMEInfo(Model):
"""
Provide basic information about detected NVMe devices and setup.

Contains information just in scope required for the proper handling during
the IPU.
"""

devices = fields.List(fields.Model(NVMEDevice), default=[])
"""
List of detected NVMe devices.
"""

hostnqn = fields.Nullable(fields.String())
"""
Human-readable host identifier in NVMe Qualified Name format

Alias NVMe-oF host NQN. It's mandatory for RDMA, FC, and TCP transport
types, but optional for PCIe. If not defined, the value is None
"""

hostid = fields.Nullable(fields.String())
"""
Persistent UUID for the NVMe host.

Alias NVMe-oF host NQN. It's mandatory for RDMA, FC, and TCP transport
types, but optional for PCIe. If not defined, the value is None
"""
Loading