leapp-repository/0031-Add-possibility-to-add-kernel-drivers-to-initrd.patch
Petr Stodulka ee57901913 CTC2 build candidate
- Fix the calculation of the required free space on each partitions/volume for the upgrade transactions
- Create source overlay images with dynamic sizes to optimize disk space consumption
- Update GRUB2 when /boot resides on multiple devices aggregated in RAID
- Use new leapp CLI API which provides better report summary output
- Introduce possibility to add (custom) kernel drivers to initramfs
- Detect and report use of deprecated Xorg drivers
- Fix the generation of the report about hybrid images
- Inhibit the upgrade when unsupported x86-64 microarchitecture is detected
- Minor improvements and fixes of various reports
- Requires leapp-framework 4.0
- Update leapp data files
- Resolves: rhbz#2140011, rhbz#2144304, rhbz#2174095, rhbz#2219544, rhbz#2215997
2023-07-18 09:39:37 +02:00

1193 lines
50 KiB
Diff

From 030e1fccac6f15d4d5179a42ba43ba373cd727cf Mon Sep 17 00:00:00 2001
From: David Kubek <dkubek@redhat.com>
Date: Wed, 24 May 2023 10:40:23 +0200
Subject: [PATCH 31/42] Add possibility to add kernel drivers to initrd
Before this change there was no possibility for developers to specify what
kernel drivers should be included in the upgrade/target initramfs. This
includes third-party drivers, which are necessary for system upgrades in some
spefic use cases.
Changes include:
- A new model `KernelModule` (analogous to the `DracutModule` model) has been
created, to handle kernel drivers.
- Added an `include_kernel_drivers` field in the `UpgradeInitramfsTasks` model
to handle a list of these drivers.
- Data in the `include_kernel_drivers` field is processed correctly to detect
conflicting paths.
- Modified the `generate-iniram.sh` script to accept and process the new data
- Added checks for kernel drivers, in the `CheckInitramfsTasks` actor.
- Updated the unit-tests accordingly.
---
.../libraries/modscan.py | 1 +
.../libraries/checkinitramfstasks.py | 91 +++++----
.../tests/unit_test_checkinitramfstasks.py | 57 +++++-
.../libraries/targetinitramfsgenerator.py | 97 ++++++++--
.../tests/test_targetinitramfsgenerator.py | 174 +++++++++++-------
.../files/generate-initram.sh | 13 ++
.../libraries/upgradeinitramfsgenerator.py | 141 +++++++++++---
.../unit_test_upgradeinitramfsgenerator.py | 125 +++++++++----
.../system_upgrade/common/models/initramfs.py | 47 +++++
9 files changed, 563 insertions(+), 183 deletions(-)
diff --git a/repos/system_upgrade/common/actors/commonleappdracutmodules/libraries/modscan.py b/repos/system_upgrade/common/actors/commonleappdracutmodules/libraries/modscan.py
index 2b8d78a4..15150a50 100644
--- a/repos/system_upgrade/common/actors/commonleappdracutmodules/libraries/modscan.py
+++ b/repos/system_upgrade/common/actors/commonleappdracutmodules/libraries/modscan.py
@@ -31,6 +31,7 @@ _REQUIRED_PACKAGES = [
'kernel-core',
'kernel-modules',
'keyutils',
+ 'kmod',
'lldpad',
'lvm2',
'mdadm',
diff --git a/repos/system_upgrade/common/actors/initramfs/checkinitramfstasks/libraries/checkinitramfstasks.py b/repos/system_upgrade/common/actors/initramfs/checkinitramfstasks/libraries/checkinitramfstasks.py
index cd87f74d..0d7d8317 100644
--- a/repos/system_upgrade/common/actors/initramfs/checkinitramfstasks/libraries/checkinitramfstasks.py
+++ b/repos/system_upgrade/common/actors/initramfs/checkinitramfstasks/libraries/checkinitramfstasks.py
@@ -6,11 +6,11 @@ from leapp.libraries.stdlib import api
from leapp.models import TargetInitramfsTasks, UpgradeInitramfsTasks
DRACUT_MOD_DIR = '/usr/lib/dracut/modules.d/'
-SUMMARY_DRACUT_FMT = (
- 'The requested dracut modules for the initramfs are in conflict.'
- ' At least one dracut module is specified to be installed from'
- ' multiple paths. The list of conflicting dracut module names'
- ' with paths is listed below: {}'
+SUMMARY_FMT = (
+ 'The requested {kind} modules for the initramfs are in conflict.'
+ ' At least one {kind} module is specified to be installed from'
+ ' multiple paths. The list of conflicting {kind} module names'
+ ' with paths is listed below: {conflicts}'
)
@@ -22,51 +22,72 @@ def _printable_modules(conflicts):
return ''.join(output)
-def _treat_path(dmodule):
+def _treat_path_dracut(dmodule):
"""
In case the path is not set, set the expected path of the dracut module.
"""
+
if not dmodule.module_path:
return os.path.join(DRACUT_MOD_DIR, dmodule.name)
return dmodule.module_path
-def _detect_dracut_modules_conflicts(msgtype):
+def _treat_path_kernel(kmodule):
+ """
+ In case the path of a kernel module is not set, indicate that the module is
+ taken from the current system.
+ """
+
+ if not kmodule.module_path:
+ return kmodule.name + ' (system)'
+ return kmodule.module_path
+
+
+def _detect_modules_conflicts(msgtype, kind):
"""
Return dict of modules with conflicting tasks
- In this case when a dracut module should be applied but different
- sources are specified. E.g.:
- include dracut modules X where,
+ In this case when a module should be applied but different sources are
+ specified. E.g.:
+ include modules X where,
msg A) X
msg B) X from custom path
"""
- dracut_modules = defaultdict(set)
+
+ modules_map = {
+ 'dracut': {
+ 'msgattr': 'include_dracut_modules',
+ 'treat_path_fn': _treat_path_dracut,
+ },
+ 'kernel': {
+ 'msgattr': 'include_kernel_modules',
+ 'treat_path_fn': _treat_path_kernel
+ },
+ }
+
+ modules = defaultdict(set)
for msg in api.consume(msgtype):
- for dmodule in msg.include_dracut_modules:
- dracut_modules[dmodule.name].add(_treat_path(dmodule))
- return {key: val for key, val in dracut_modules.items() if len(val) > 1}
+ for module in getattr(msg, modules_map[kind]['msgattr']):
+ treat_path_fn = modules_map[kind]['treat_path_fn']
+ modules[module.name].add(treat_path_fn(module))
+ return {key: val for key, val in modules.items() if len(val) > 1}
+
+
+def report_conflicts(msgname, kind, msgtype):
+ conflicts = _detect_modules_conflicts(msgtype, kind)
+ if not conflicts:
+ return
+ report = [
+ reporting.Title('Conflicting requirements of {kind} modules for the {msgname} initramfs'.format(
+ kind=kind, msgname=msgname)),
+ reporting.Summary(SUMMARY_FMT.format(kind=kind, conflicts=_printable_modules(conflicts))),
+ reporting.Severity(reporting.Severity.HIGH),
+ reporting.Groups([reporting.Groups.SANITY, reporting.Groups.INHIBITOR]),
+ ]
+ reporting.create_report(report)
def process():
- conflicts = _detect_dracut_modules_conflicts(UpgradeInitramfsTasks)
- if conflicts:
- report = [
- reporting.Title('Conflicting requirements of dracut modules for the upgrade initramfs'),
- reporting.Summary(SUMMARY_DRACUT_FMT.format(_printable_modules(conflicts))),
- reporting.Severity(reporting.Severity.HIGH),
- reporting.Groups([reporting.Groups.SANITY]),
- reporting.Groups([reporting.Groups.INHIBITOR]),
- ]
- reporting.create_report(report)
-
- conflicts = _detect_dracut_modules_conflicts(TargetInitramfsTasks)
- if conflicts:
- report = [
- reporting.Title('Conflicting requirements of dracut modules for the target initramfs'),
- reporting.Summary(SUMMARY_DRACUT_FMT.format(_printable_modules(conflicts))),
- reporting.Severity(reporting.Severity.HIGH),
- reporting.Groups([reporting.Groups.SANITY]),
- reporting.Groups([reporting.Groups.INHIBITOR]),
- ]
- reporting.create_report(report)
+ report_conflicts('upgrade', 'kernel', UpgradeInitramfsTasks)
+ report_conflicts('upgrade', 'dracut', UpgradeInitramfsTasks)
+ report_conflicts('target', 'dracut', TargetInitramfsTasks)
diff --git a/repos/system_upgrade/common/actors/initramfs/checkinitramfstasks/tests/unit_test_checkinitramfstasks.py b/repos/system_upgrade/common/actors/initramfs/checkinitramfstasks/tests/unit_test_checkinitramfstasks.py
index aad79c73..fca15f73 100644
--- a/repos/system_upgrade/common/actors/initramfs/checkinitramfstasks/tests/unit_test_checkinitramfstasks.py
+++ b/repos/system_upgrade/common/actors/initramfs/checkinitramfstasks/tests/unit_test_checkinitramfstasks.py
@@ -6,7 +6,7 @@ from leapp import reporting
from leapp.libraries.actor import checkinitramfstasks
from leapp.libraries.common.testutils import create_report_mocked, CurrentActorMocked
from leapp.libraries.stdlib import api
-from leapp.models import DracutModule, Report, TargetInitramfsTasks, UpgradeInitramfsTasks
+from leapp.models import DracutModule, KernelModule, TargetInitramfsTasks, UpgradeInitramfsTasks
from leapp.utils.report import is_inhibitor
@@ -14,7 +14,8 @@ def gen_UIT(modules):
if not isinstance(modules, list):
modules = [modules]
dracut_modules = [DracutModule(name=i[0], module_path=i[1]) for i in modules]
- return UpgradeInitramfsTasks(include_dracut_modules=dracut_modules)
+ kernel_modules = [KernelModule(name=i[0], module_path=i[1]) for i in modules]
+ return UpgradeInitramfsTasks(include_dracut_modules=dracut_modules, include_kernel_modules=kernel_modules)
def gen_TIT(modules):
@@ -71,9 +72,57 @@ def gen_TIT(modules):
TargetInitramfsTasks,
),
])
-def test_conflict_detection(monkeypatch, expected_res, input_msgs, test_msg_type):
+def test_dracut_conflict_detection(monkeypatch, expected_res, input_msgs, test_msg_type):
monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=input_msgs))
- res = checkinitramfstasks._detect_dracut_modules_conflicts(test_msg_type)
+ res = checkinitramfstasks._detect_modules_conflicts(test_msg_type, 'dracut')
+ assert res == expected_res
+
+
+@pytest.mark.parametrize('expected_res,input_msgs,test_msg_type', [
+ (
+ {},
+ [],
+ UpgradeInitramfsTasks,
+ ),
+ (
+ {},
+ [gen_UIT([('modA', 'pathA'), ('modB', 'pathB')])],
+ UpgradeInitramfsTasks,
+ ),
+ (
+ {},
+ [gen_UIT([('modA', 'pathA'), ('modA', 'pathA')])],
+ UpgradeInitramfsTasks,
+ ),
+ (
+ {'modA': {'pathA', 'pathB'}},
+ [gen_UIT([('modA', 'pathA'), ('modA', 'pathB')])],
+ UpgradeInitramfsTasks,
+ ),
+ (
+ {'modA': {'pathA', 'pathB'}},
+ [gen_UIT(('modA', 'pathA')), gen_UIT(('modA', 'pathB'))],
+ UpgradeInitramfsTasks,
+ ),
+ (
+ {'modA': {'pathA', 'pathB'}},
+ [gen_UIT([('modA', 'pathA'), ('modA', 'pathB'), ('modB', 'pathC')])],
+ UpgradeInitramfsTasks,
+ ),
+ (
+ {'modA': {'modA (system)', 'pathB'}},
+ [gen_UIT([('modA', None), ('modA', 'pathB')])],
+ UpgradeInitramfsTasks,
+ ),
+ (
+ {},
+ [gen_UIT([('modA', 'pathA'), ('modA', 'pathB')])],
+ TargetInitramfsTasks,
+ ),
+])
+def test_kernel_conflict_detection(monkeypatch, expected_res, input_msgs, test_msg_type):
+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=input_msgs))
+ res = checkinitramfstasks._detect_modules_conflicts(test_msg_type, 'kernel')
assert res == expected_res
diff --git a/repos/system_upgrade/common/actors/initramfs/targetinitramfsgenerator/libraries/targetinitramfsgenerator.py b/repos/system_upgrade/common/actors/initramfs/targetinitramfsgenerator/libraries/targetinitramfsgenerator.py
index 1a7a3e19..39666017 100644
--- a/repos/system_upgrade/common/actors/initramfs/targetinitramfsgenerator/libraries/targetinitramfsgenerator.py
+++ b/repos/system_upgrade/common/actors/initramfs/targetinitramfsgenerator/libraries/targetinitramfsgenerator.py
@@ -1,3 +1,7 @@
+import errno
+import os
+import shutil
+
from leapp.exceptions import StopActorExecutionError
from leapp.libraries.stdlib import api, CalledProcessError, run
from leapp.models import InitrdIncludes # deprecated
@@ -7,25 +11,66 @@ from leapp.utils.deprecation import suppress_deprecation
DRACUT_DIR = '/usr/lib/dracut/modules.d/'
-def copy_dracut_modules(modules):
+def _get_target_kernel_modules_dir(kernel_version):
+ """
+ Return the path where the custom kernel modules should be copied.
+ """
+
+ modules_dir = os.path.join('/', 'lib', 'modules', kernel_version, 'extra', 'leapp')
+
+ return modules_dir
+
+
+def _copy_modules(modules, dst_dir, kind):
"""
- Copy every dracut module with specified path into the expected directory.
+ Copy modules of given kind to the specified destination directory.
+
+ Attempts to remove an cleanup by removing the existing destination
+ directory. If the directory does not exist, it is created anew. Then, for
+ each module message, it checks if the module has a module path specified. If
+ the module already exists in the destination directory, a debug message is
+ logged, and the operation is skipped. Otherwise, the module is copied to the
+ destination directory.
- original content is overwritten if exists
"""
- # FIXME: use just python functions instead of shell cmds
+
+ try:
+ os.makedirs(dst_dir)
+ except OSError as exc:
+ if exc.errno == errno.EEXIST and os.path.isdir(dst_dir):
+ pass
+ else:
+ raise
+
for module in modules:
if not module.module_path:
continue
+
+ dst_path = os.path.join(dst_dir, os.path.basename(module.module_path))
+ if os.path.exists(dst_path):
+ api.current_logger().debug(
+ 'The {name} {kind} module has been already installed. Skipping.'
+ .format(name=module.name, kind=kind))
+ continue
+
+ copy_fn = shutil.copytree
+ if os.path.isfile(module.module_path):
+ copy_fn = shutil.copy2
+
try:
- # context.copytree_to(module.module_path, os.path.join(DRACUT_DIR, os.path.basename(module.module_path)))
- run(['cp', '-f', '-a', module.module_path, DRACUT_DIR])
- except CalledProcessError as e:
- api.current_logger().error('Failed to copy dracut module "{name}" from "{source}" to "{target}"'.format(
- name=module.name, source=module.module_path, target=DRACUT_DIR), exc_info=True)
- # FIXME: really do we want to raise the error and stop execution completely??....
+ api.current_logger().debug(
+ 'Copying {kind} module "{name}" to "{path}".'
+ .format(kind=kind, name=module.name, path=dst_path))
+
+ copy_fn(module.module_path, dst_path)
+ except shutil.Error as e:
+ api.current_logger().error(
+ 'Failed to copy {kind} module "{name}" from "{source}" to "{target}"'.format(
+ kind=kind, name=module.name, source=module.module_path, target=dst_dir),
+ exc_info=True)
raise StopActorExecutionError(
- message='Failed to install dracut modules required in the target initramfs. Error: {}'.format(str(e))
+ message='Failed to install {kind} modules required in the initram. Error: {error}'.format(
+ kind=kind, error=str(e))
)
@@ -43,9 +88,11 @@ def _get_modules():
# supposed to create any such tasks before the reporting phase, so we
# are able to check it.
#
- modules = []
+ modules = {'dracut': [], 'kernel': []}
for task in api.consume(TargetInitramfsTasks):
- modules.extend(task.include_dracut_modules)
+ modules['dracut'].extend(task.include_dracut_modules)
+ modules['kernel'].extend(task.include_kernel_modules)
+
return modules
@@ -53,7 +100,7 @@ def process():
files = _get_files()
modules = _get_modules()
- if not files and not modules:
+ if not files and not modules['kernel'] and not modules['dracut']:
api.current_logger().debug(
'No additional files or modules required to add into the target initramfs.')
return
@@ -65,15 +112,29 @@ def process():
details={'Problem': 'Did not receive a message with installed RHEL-8 kernel version'
' (InstalledTargetKernelVersion)'})
- copy_dracut_modules(modules)
+ _copy_modules(modules['dracut'], DRACUT_DIR, 'dracut')
+ _copy_modules(modules['kernel'], _get_target_kernel_modules_dir(target_kernel.version), 'kernel')
+
+ # Discover any new modules and regenerate modules.dep
+ should_regenerate = any(module.module_path is not None for module in modules['kernel'])
+ if should_regenerate:
+ try:
+ run(['depmod', target_kernel.version, '-a'])
+ except CalledProcessError as e:
+ raise StopActorExecutionError('Failed to generate modules.dep and map files.', details={'details': str(e)})
+
try:
# multiple files|modules need to be quoted, see --install | --add in dracut(8)
- module_names = list({module.name for module in modules})
+ dracut_module_names = list({module.name for module in modules['dracut']})
+ kernel_module_names = list({module.name for module in modules['kernel']})
cmd = ['dracut', '-f', '--kver', target_kernel.version]
if files:
cmd += ['--install', '{}'.format(' '.join(files))]
- if modules:
- cmd += ['--add', '{}'.format(' '.join(module_names))]
+ if modules['dracut']:
+ cmd += ['--add', '{}'.format(' '.join(dracut_module_names))]
+ if modules['kernel']:
+ cmd += ['--add-drivers', '{}'.format(' '.join(kernel_module_names))]
+
run(cmd)
except CalledProcessError as e:
# just hypothetic check, it should not die
diff --git a/repos/system_upgrade/common/actors/initramfs/targetinitramfsgenerator/tests/test_targetinitramfsgenerator.py b/repos/system_upgrade/common/actors/initramfs/targetinitramfsgenerator/tests/test_targetinitramfsgenerator.py
index 8403a431..57daca75 100644
--- a/repos/system_upgrade/common/actors/initramfs/targetinitramfsgenerator/tests/test_targetinitramfsgenerator.py
+++ b/repos/system_upgrade/common/actors/initramfs/targetinitramfsgenerator/tests/test_targetinitramfsgenerator.py
@@ -6,12 +6,9 @@ from leapp.libraries.common.testutils import CurrentActorMocked, logger_mocked
from leapp.libraries.stdlib import api, CalledProcessError
from leapp.utils.deprecation import suppress_deprecation
-from leapp.models import ( # isort:skip
- InitrdIncludes, # deprecated
- DracutModule,
- InstalledTargetKernelVersion,
- TargetInitramfsTasks
-)
+from leapp.models import ( # isort:skip
+ InitrdIncludes, # deprecated
+ DracutModule, KernelModule, InstalledTargetKernelVersion, TargetInitramfsTasks)
FILES = ['/file1', '/file2', '/dir/subdir/subsubdir/file3', '/file4', '/file5']
MODULES = [
@@ -25,13 +22,19 @@ NO_INCLUDE_MSG = 'No additional files or modules required to add into the target
def raise_call_error(args=None):
- raise CalledProcessError(
- message='A Leapp Command Error occurred.',
- command=args,
- result={'signal': None, 'exit_code': 1, 'pid': 0, 'stdout': 'fake', 'stderr': 'fake'})
+ raise CalledProcessError(message='A Leapp Command Error occurred.',
+ command=args,
+ result={
+ 'signal': None,
+ 'exit_code': 1,
+ 'pid': 0,
+ 'stdout': 'fake',
+ 'stderr': 'fake'
+ })
class RunMocked(object):
+
def __init__(self, raise_err=False):
self.called = 0
self.args = []
@@ -44,20 +47,26 @@ class RunMocked(object):
raise_call_error(args)
-def gen_TIT(modules, files):
- if not isinstance(modules, list):
- modules = [modules]
- if not isinstance(files, list):
- files = [files]
- dracut_modules = [DracutModule(name=i[0], module_path=i[1]) for i in modules]
- return TargetInitramfsTasks(include_files=files, include_dracut_modules=dracut_modules)
+def _ensure_list(data):
+ return data if isinstance(data, list) else [data]
+
+
+def gen_TIT(dracut_modules, kernel_modules, files):
+ files = _ensure_list(files)
+
+ dracut_modules = [DracutModule(name=i[0], module_path=i[1]) for i in _ensure_list(dracut_modules)]
+ kernel_modules = [KernelModule(name=i[0], module_path=i[1]) for i in _ensure_list(kernel_modules)]
+
+ return TargetInitramfsTasks(
+ include_files=files,
+ include_dracut_modules=dracut_modules,
+ include_kernel_modules=kernel_modules,
+ )
@suppress_deprecation(InitrdIncludes)
def gen_InitrdIncludes(files):
- if not isinstance(files, list):
- files = [files]
- return InitrdIncludes(files=files)
+ return InitrdIncludes(files=_ensure_list(files))
def test_no_includes(monkeypatch):
@@ -77,12 +86,12 @@ TEST_CASES = [
gen_InitrdIncludes(FILES[3:]),
],
[
- gen_TIT([], FILES[0:3]),
- gen_TIT([], FILES[3:]),
+ gen_TIT([], [], FILES[0:3]),
+ gen_TIT([], [], FILES[3:]),
],
[
gen_InitrdIncludes(FILES[0:3]),
- gen_TIT([], FILES[3:]),
+ gen_TIT([], [], FILES[3:]),
],
]
@@ -93,7 +102,7 @@ def test_no_kernel_version(monkeypatch, msgs):
monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs))
monkeypatch.setattr(targetinitramfsgenerator, 'run', run_mocked)
# FIXME
- monkeypatch.setattr(targetinitramfsgenerator, 'copy_dracut_modules', lambda dummy: None)
+ monkeypatch.setattr(targetinitramfsgenerator, '_copy_modules', lambda *_: None)
with pytest.raises(StopActorExecutionError) as e:
targetinitramfsgenerator.process()
@@ -105,11 +114,11 @@ def test_no_kernel_version(monkeypatch, msgs):
def test_dracut_fail(monkeypatch, msgs):
run_mocked = RunMocked(raise_err=True)
monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs))
- monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(
- msgs=msgs+[InstalledTargetKernelVersion(version=KERNEL_VERSION)]))
+ monkeypatch.setattr(api, 'current_actor',
+ CurrentActorMocked(msgs=msgs + [InstalledTargetKernelVersion(version=KERNEL_VERSION)]))
monkeypatch.setattr(targetinitramfsgenerator, 'run', run_mocked)
# FIXME
- monkeypatch.setattr(targetinitramfsgenerator, 'copy_dracut_modules', lambda dummy: None)
+ monkeypatch.setattr(targetinitramfsgenerator, '_copy_modules', lambda *_: None)
with pytest.raises(StopActorExecutionError) as e:
targetinitramfsgenerator.process()
@@ -117,47 +126,71 @@ def test_dracut_fail(monkeypatch, msgs):
assert run_mocked.called
-@pytest.mark.parametrize('msgs,files,modules', [
- # deprecated set
- ([gen_InitrdIncludes(FILES[0])], FILES[0:1], []),
- ([gen_InitrdIncludes(FILES)], FILES, []),
- ([gen_InitrdIncludes(FILES[0:3]), gen_InitrdIncludes(FILES[3:])], FILES, []),
- ([gen_InitrdIncludes(FILES[0:3]), gen_InitrdIncludes(FILES)], FILES, []),
-
- # new set for files only
- ([gen_TIT([], FILES[0])], FILES[0:1], []),
- ([gen_TIT([], FILES)], FILES, []),
- ([gen_TIT([], FILES[0:3]), gen_TIT([], FILES[3:])], FILES, []),
- ([gen_TIT([], FILES[0:3]), gen_TIT([], FILES)], FILES, []),
-
- # deprecated and new msgs for files only
- ([gen_InitrdIncludes(FILES[0:3]), gen_TIT([], FILES[3:])], FILES, []),
-
- # modules only
- ([gen_TIT(MODULES[0], [])], [], MODULES[0:1]),
- ([gen_TIT(MODULES, [])], [], MODULES),
- ([gen_TIT(MODULES[0:3], []), gen_TIT(MODULES[3], [])], [], MODULES),
-
- # modules only - duplicates; see notes in the library
- ([gen_TIT(MODULES[0:3], []), gen_TIT(MODULES, [])], [], MODULES),
-
- # modules + files (new only)
- ([gen_TIT(MODULES, FILES)], FILES, MODULES),
- ([gen_TIT(MODULES[0:3], FILES[0:3]), gen_TIT(MODULES[3:], FILES[3:])], FILES, MODULES),
- ([gen_TIT(MODULES, []), gen_TIT([], FILES)], FILES, MODULES),
-
- # modules + files with deprecated msgs
- ([gen_TIT(MODULES, []), gen_InitrdIncludes(FILES)], FILES, MODULES),
- ([gen_TIT(MODULES, FILES[0:3]), gen_InitrdIncludes(FILES[3:])], FILES, MODULES),
-
-])
-def test_flawless(monkeypatch, msgs, files, modules):
+@pytest.mark.parametrize(
+ 'msgs,files,dracut_modules,kernel_modules',
+ [
+ # deprecated set
+ ([gen_InitrdIncludes(FILES[0])], FILES[0:1], [], []),
+ ([gen_InitrdIncludes(FILES)], FILES, [], []),
+ ([gen_InitrdIncludes(FILES[0:3]), gen_InitrdIncludes(FILES[3:])], FILES, [], []),
+ ([gen_InitrdIncludes(FILES[0:3]), gen_InitrdIncludes(FILES)], FILES, [], []),
+
+ # new set for files only
+ ([gen_TIT([], [], FILES[0])], FILES[0:1], [], []),
+ ([gen_TIT([], [], FILES)], FILES, [], []),
+ ([gen_TIT([], [], FILES[0:3]), gen_TIT([], [], FILES[3:])], FILES, [], []),
+ ([gen_TIT([], [], FILES[0:3]), gen_TIT([], [], FILES)], FILES, [], []),
+
+ # deprecated and new msgs for files only
+ ([gen_InitrdIncludes(FILES[0:3]), gen_TIT([], [], FILES[3:])], FILES, [], []),
+
+ # dracut modules only
+ ([gen_TIT(MODULES[0], [], [])], [], MODULES[0:1], []),
+ ([gen_TIT(MODULES, [], [])], [], MODULES, []),
+ ([gen_TIT(MODULES[0:3], [], []), gen_TIT(MODULES[3], [], [])], [], MODULES, []),
+
+ # kernel modules only
+ ([gen_TIT([], MODULES[0], [])], [], [], MODULES[0:1]),
+ ([gen_TIT([], MODULES, [])], [], [], MODULES),
+ ([gen_TIT([], MODULES[0:3], []), gen_TIT([], MODULES[3], [])], [], [], MODULES),
+
+ # modules only - duplicates; see notes in the library
+ ([gen_TIT(MODULES[0:3], [], []), gen_TIT(MODULES, [], [])], [], MODULES, []),
+ ([gen_TIT([], MODULES[0:3], []), gen_TIT([], MODULES, [])], [], [], MODULES),
+
+ # modules + files (new only)
+ ([gen_TIT(MODULES, [], FILES)], FILES, MODULES, []),
+ ([gen_TIT([], MODULES, FILES)], FILES, [], MODULES),
+
+ ([gen_TIT(MODULES[0:3], [], FILES[0:3]), gen_TIT(MODULES[3:], [], FILES[3:])], FILES, MODULES, []),
+ ([gen_TIT([], MODULES[0:3], FILES[0:3]), gen_TIT([], MODULES[3:], FILES[3:])], FILES, [], MODULES),
+
+ ([gen_TIT(MODULES, [], []), gen_TIT([], [], FILES)], FILES, MODULES, []),
+ ([gen_TIT([], MODULES, []), gen_TIT([], [], FILES)], FILES, [], MODULES),
+
+ # kernel + dracut modules
+ (
+ [
+ gen_TIT(MODULES[0:3], MODULES[0:3], FILES[0:3]),
+ gen_TIT(MODULES[3:], MODULES[3:], FILES[3:])
+ ],
+ FILES, MODULES, MODULES
+ ),
+
+ # modules + files with deprecated msgs
+ ([gen_TIT(MODULES, [], []), gen_InitrdIncludes(FILES)], FILES, MODULES, []),
+ ([gen_TIT([], MODULES, []), gen_InitrdIncludes(FILES)], FILES, [], MODULES),
+
+ ([gen_TIT(MODULES, [], FILES[0:3]), gen_InitrdIncludes(FILES[3:])], FILES, MODULES, []),
+ ([gen_TIT([], MODULES, FILES[0:3]), gen_InitrdIncludes(FILES[3:])], FILES, [], MODULES),
+ ])
+def test_flawless(monkeypatch, msgs, files, dracut_modules, kernel_modules):
_msgs = msgs + [InstalledTargetKernelVersion(version=KERNEL_VERSION)]
run_mocked = RunMocked()
monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=_msgs))
monkeypatch.setattr(targetinitramfsgenerator, 'run', run_mocked)
# FIXME
- monkeypatch.setattr(targetinitramfsgenerator, 'copy_dracut_modules', lambda dummy: None)
+ monkeypatch.setattr(targetinitramfsgenerator, '_copy_modules', lambda *_: None)
targetinitramfsgenerator.process()
assert run_mocked.called
@@ -171,11 +204,20 @@ def test_flawless(monkeypatch, msgs, files, modules):
else:
assert '--install' not in run_mocked.args
- # check modules
- if modules:
+ # check dracut modules
+ if dracut_modules:
assert '--add' in run_mocked.args
arg = run_mocked.args[run_mocked.args.index('--add') + 1]
- for m in modules:
+ for m in dracut_modules:
assert m[0] in arg
else:
assert '--add' not in run_mocked.args
+
+ # check kernel modules
+ if kernel_modules:
+ assert '--add-drivers' in run_mocked.args
+ arg = run_mocked.args[run_mocked.args.index('--add-drivers') + 1]
+ for m in kernel_modules:
+ assert m[0] in arg
+ else:
+ assert '--add-drivers' not in run_mocked.args
diff --git a/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/files/generate-initram.sh b/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/files/generate-initram.sh
index d6934147..9648234c 100755
--- a/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/files/generate-initram.sh
+++ b/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/files/generate-initram.sh
@@ -29,6 +29,9 @@ dracut_install_modules()
}
+# KERNEL_MODULES_ADD and DRACUT_MODULES_ADD are expected to be expanded and
+# we do not want to prevent word splitting in that case.
+# shellcheck disable=SC2086
build() {
dracut_install_modules
@@ -67,6 +70,15 @@ build() {
DRACUT_MODULES_ADD=$(echo "--add $LEAPP_ADD_DRACUT_MODULES" | sed 's/,/ --add /g')
fi
+ KERNEL_MODULES_ADD=""
+ if [[ -n "$LEAPP_ADD_KERNEL_MODULES" ]]; then
+ depmod "${KERNEL_VERSION}" -a
+ KERNEL_MODULES_ADD=$(
+ echo "--add-drivers $LEAPP_ADD_KERNEL_MODULES" |
+ sed 's/,/ --add-drivers /g'
+ )
+ fi
+
DRACUT_INSTALL="systemd-nspawn"
if [[ -n "$LEAPP_DRACUT_INSTALL_FILES" ]]; then
DRACUT_INSTALL="$DRACUT_INSTALL $LEAPP_DRACUT_INSTALL_FILES"
@@ -89,6 +101,7 @@ build() {
--confdir "$DRACUT_CONF_DIR" \
--install "$DRACUT_INSTALL" \
$DRACUT_MODULES_ADD \
+ $KERNEL_MODULES_ADD \
"$DRACUT_MDADMCONF_ARG" \
"$DRACUT_LVMCONF_ARG" \
--no-hostonly \
diff --git a/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/libraries/upgradeinitramfsgenerator.py b/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/libraries/upgradeinitramfsgenerator.py
index 2f145217..f141d9e3 100644
--- a/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/libraries/upgradeinitramfsgenerator.py
+++ b/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/libraries/upgradeinitramfsgenerator.py
@@ -1,10 +1,11 @@
import os
import shutil
+from distutils.version import LooseVersion
from leapp.exceptions import StopActorExecutionError
from leapp.libraries.common import dnfplugin, mounting
from leapp.libraries.common.config.version import get_target_major_version
-from leapp.libraries.stdlib import api
+from leapp.libraries.stdlib import api, CalledProcessError
from leapp.models import RequiredUpgradeInitramPackages # deprecated
from leapp.models import UpgradeDracutModule # deprecated
from leapp.models import (
@@ -21,6 +22,45 @@ INITRAM_GEN_SCRIPT_NAME = 'generate-initram.sh'
DRACUT_DIR = '/dracut'
+def _get_target_kernel_version(context):
+ """
+ Get the version of the most recent kernel version within the container.
+ """
+
+ kernel_version = None
+ try:
+ results = context.call(['rpm', '-qa', 'kernel-core'], split=True)
+
+ versions = [ver.replace('kernel-core-', '') for ver in results['stdout']]
+ api.current_logger().debug(
+ 'Versions detected {versions}.'
+ .format(versions=versions))
+ sorted_versions = sorted(versions, key=LooseVersion, reverse=True)
+ kernel_version = next(iter(sorted_versions), None)
+ except CalledProcessError:
+ raise StopActorExecutionError(
+ 'Cannot get version of the installed kernel.',
+ details={'Problem': 'Could not query the currently installed kernel through rmp.'})
+
+ if not kernel_version:
+ raise StopActorExecutionError(
+ 'Cannot get version of the installed kernel.',
+ details={'Problem': 'A rpm query for the available kernels did not produce any results.'})
+
+ return kernel_version
+
+
+def _get_target_kernel_modules_dir(context):
+ """
+ Return the path where the custom kernel modules should be copied.
+ """
+
+ kernel_version = _get_target_kernel_version(context)
+ modules_dir = os.path.join('/', 'lib', 'modules', kernel_version, 'extra', 'leapp')
+
+ return modules_dir
+
+
def _reinstall_leapp_repository_hint():
"""
Convenience function for creating a detail for StopActorExecutionError with a hint to reinstall the
@@ -31,39 +71,81 @@ def _reinstall_leapp_repository_hint():
}
-def copy_dracut_modules(context, modules):
+def _copy_modules(context, modules, dst_dir, kind):
"""
- Copy dracut modules into the target userspace.
+ Copy modules of given kind to the specified destination directory.
+
+ Attempts to remove an cleanup by removing the existing destination
+ directory. If the directory does not exist, it is created anew. Then, for
+ each module message, it checks if the module has a module path specified. If
+ the module already exists in the destination directory, a debug message is
+ logged, and the operation is skipped. Otherwise, the module is copied to the
+ destination directory.
- If duplicated requirements to copy a dracut module are detected,
- log the debug msg and skip any try to copy a dracut module into the
- target userspace that already exists inside DRACTUR_DIR.
"""
+
try:
- context.remove_tree(DRACUT_DIR)
+ context.remove_tree(dst_dir)
except EnvironmentError:
pass
+
+ context.makedirs(dst_dir)
+
for module in modules:
if not module.module_path:
continue
- dst_path = os.path.join(DRACUT_DIR, os.path.basename(module.module_path))
+
+ dst_path = os.path.join(dst_dir, os.path.basename(module.module_path))
if os.path.exists(context.full_path(dst_path)):
- # we are safe to skip it as we now the module is from the same path
- # regarding the actor checking all initramfs tasks
api.current_logger().debug(
- 'The {name} dracut module has been already installed. Skipping.'
- .format(name=module.name))
+ 'The {name} {kind} module has been already installed. Skipping.'
+ .format(name=module.name, kind=kind))
continue
+
+ copy_fn = context.copytree_to
+ if os.path.isfile(module.module_path):
+ copy_fn = context.copy_to
+
try:
- context.copytree_to(module.module_path, dst_path)
+ api.current_logger().debug(
+ 'Copying {kind} module "{name}" to "{path}".'
+ .format(kind=kind, name=module.name, path=dst_path))
+
+ copy_fn(module.module_path, dst_path)
except shutil.Error as e:
- api.current_logger().error('Failed to copy dracut module "{name}" from "{source}" to "{target}"'.format(
- name=module.name, source=module.module_path, target=context.full_path(DRACUT_DIR)), exc_info=True)
+ api.current_logger().error(
+ 'Failed to copy {kind} module "{name}" from "{source}" to "{target}"'.format(
+ kind=kind, name=module.name, source=module.module_path, target=context.full_path(dst_dir)),
+ exc_info=True)
raise StopActorExecutionError(
- message='Failed to install dracut modules required in the initram. Error: {}'.format(str(e))
+ message='Failed to install {kind} modules required in the initram. Error: {error}'.format(
+ kind=kind, error=str(e))
)
+def copy_dracut_modules(context, modules):
+ """
+ Copy dracut modules into the target userspace.
+
+ If a module cannot be copied, an error message is logged, and a
+ StopActorExecutionError exception is raised.
+ """
+
+ _copy_modules(context, modules, DRACUT_DIR, 'dracut')
+
+
+def copy_kernel_modules(context, modules):
+ """
+ Copy kernel modules into the target userspace.
+
+ If a module cannot be copied, an error message is logged, and a
+ StopActorExecutionError exception is raised.
+ """
+
+ dst_dir = _get_target_kernel_modules_dir(context)
+ _copy_modules(context, modules, dst_dir, 'kernel')
+
+
@suppress_deprecation(UpgradeDracutModule)
def _get_dracut_modules():
return list(api.consume(UpgradeDracutModule))
@@ -153,30 +235,43 @@ def generate_initram_disk(context):
"""
Function to actually execute the init ramdisk creation.
- Includes handling of specified dracut modules from the host when needed.
- The check for the 'conflicting' dracut modules is in a separate actor.
+ Includes handling of specified dracut and kernel modules from the host when
+ needed. The check for the 'conflicting' modules is in a separate actor.
"""
env = {}
if get_target_major_version() == '9':
env = {'SYSTEMD_SECCOMP': '0'}
+
# TODO(pstodulk): Add possibility to add particular drivers
# Issue #645
- modules = _get_dracut_modules() # deprecated
+ modules = {
+ 'dracut': _get_dracut_modules(), # deprecated
+ 'kernel': [],
+ }
files = set()
for task in api.consume(UpgradeInitramfsTasks):
- modules.extend(task.include_dracut_modules)
+ modules['dracut'].extend(task.include_dracut_modules)
+ modules['kernel'].extend(task.include_kernel_modules)
files.update(task.include_files)
- copy_dracut_modules(context, modules)
+
+ copy_dracut_modules(context, modules['dracut'])
+ copy_kernel_modules(context, modules['kernel'])
+
# FIXME: issue #376
context.call([
'/bin/sh', '-c',
- 'LEAPP_ADD_DRACUT_MODULES="{modules}" LEAPP_KERNEL_ARCH={arch} '
+ 'LEAPP_KERNEL_VERSION={kernel_version} '
+ 'LEAPP_ADD_DRACUT_MODULES="{dracut_modules}" LEAPP_KERNEL_ARCH={arch} '
+ 'LEAPP_ADD_KERNEL_MODULES="{kernel_modules}" '
'LEAPP_DRACUT_INSTALL_FILES="{files}" {cmd}'.format(
- modules=','.join([mod.name for mod in modules]),
+ kernel_version=_get_target_kernel_version(context),
+ dracut_modules=','.join([mod.name for mod in modules['dracut']]),
+ kernel_modules=','.join([mod.name for mod in modules['kernel']]),
arch=api.current_actor().configuration.architecture,
files=' '.join(files),
cmd=os.path.join('/', INITRAM_GEN_SCRIPT_NAME))
], env=env)
+
copy_boot_files(context)
diff --git a/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/tests/unit_test_upgradeinitramfsgenerator.py b/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/tests/unit_test_upgradeinitramfsgenerator.py
index cd9d0546..a2f1c837 100644
--- a/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/tests/unit_test_upgradeinitramfsgenerator.py
+++ b/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/tests/unit_test_upgradeinitramfsgenerator.py
@@ -10,12 +10,12 @@ from leapp.libraries.common.testutils import CurrentActorMocked, logger_mocked,
from leapp.utils.deprecation import suppress_deprecation
from leapp.models import ( # isort:skip
- FIPSInfo,
RequiredUpgradeInitramPackages, # deprecated
UpgradeDracutModule, # deprecated
BootContent,
CopyFile,
DracutModule,
+ KernelModule,
TargetUserSpaceUpgradeTasks,
UpgradeInitramfsTasks,
)
@@ -42,30 +42,36 @@ def adjust_cwd():
os.chdir(previous_cwd)
+def _ensure_list(data):
+ return data if isinstance(data, list) else [data]
+
+
def gen_TUSU(packages, copy_files=None):
- if not isinstance(packages, list):
- packages = [packages]
+ packages = _ensure_list(packages)
+
if not copy_files:
copy_files = []
- elif not isinstance(copy_files, list):
- copy_files = [copy_files]
+ copy_files = _ensure_list(copy_files)
+
return TargetUserSpaceUpgradeTasks(install_rpms=packages, copy_files=copy_files)
@suppress_deprecation(RequiredUpgradeInitramPackages)
def gen_RUIP(packages):
- if not isinstance(packages, list):
- packages = [packages]
+ packages = _ensure_list(packages)
return RequiredUpgradeInitramPackages(packages=packages)
-def gen_UIT(modules, files):
- if not isinstance(modules, list):
- modules = [modules]
- if not isinstance(files, list):
- files = [files]
- dracut_modules = [DracutModule(name=i[0], module_path=i[1]) for i in modules]
- return UpgradeInitramfsTasks(include_files=files, include_dracut_modules=dracut_modules)
+def gen_UIT(dracut_modules, kernel_modules, files):
+ files = _ensure_list(files)
+
+ dracut_modules = [DracutModule(name=i[0], module_path=i[1]) for i in _ensure_list(dracut_modules)]
+ kernel_modules = [KernelModule(name=i[0], module_path=i[1]) for i in _ensure_list(kernel_modules)]
+
+ return UpgradeInitramfsTasks(include_files=files,
+ include_dracut_modules=dracut_modules,
+ include_kernel_modules=kernel_modules,
+ )
@suppress_deprecation(UpgradeDracutModule)
@@ -81,6 +87,7 @@ class MockedContext(object):
self.called_copytree_from = []
self.called_copy_to = []
self.called_call = []
+ self.called_makedirs = []
self.content = set()
self.base_dir = "/base/dir"
"""
@@ -108,6 +115,9 @@ class MockedContext(object):
self.called_copy_to.append((src, dst))
self.content.add(dst)
+ def makedirs(self, path):
+ self.called_makedirs.append(path)
+
def remove_tree(self, path):
# make list for iteration as change of the set is expected during the
# iteration, which could lead to runtime error
@@ -240,38 +250,50 @@ def test_prepare_userspace_for_initram(monkeypatch, adjust_cwd, input_msgs, pkgs
assert _sort_files(upgradeinitramfsgenerator._copy_files.args[1]) == _files
-@pytest.mark.parametrize('input_msgs,modules', [
+@pytest.mark.parametrize('input_msgs,dracut_modules,kernel_modules', [
# test dracut modules with UpgradeDracutModule(s) - orig functionality
- (gen_UDM_list(MODULES[0]), MODULES[0]),
- (gen_UDM_list(MODULES), MODULES),
+ (gen_UDM_list(MODULES[0]), MODULES[0], []),
+ (gen_UDM_list(MODULES), MODULES, []),
# test dracut modules with UpgradeInitramfsTasks - new functionality
- ([gen_UIT(MODULES[0], [])], MODULES[0]),
- ([gen_UIT(MODULES, [])], MODULES),
+ ([gen_UIT(MODULES[0], MODULES[0], [])], MODULES[0], MODULES[0]),
+ ([gen_UIT(MODULES, MODULES, [])], MODULES, MODULES),
# test dracut modules with old and new models
- (gen_UDM_list(MODULES[1]) + [gen_UIT(MODULES[2], [])], MODULES[1:3]),
- (gen_UDM_list(MODULES[2:]) + [gen_UIT(MODULES[0:2], [])], MODULES),
+ (gen_UDM_list(MODULES[1]) + [gen_UIT(MODULES[2], [], [])], MODULES[1:3], []),
+ (gen_UDM_list(MODULES[2:]) + [gen_UIT(MODULES[0:2], [], [])], MODULES, []),
+ (gen_UDM_list(MODULES[1]) + [gen_UIT([], MODULES[2], [])], MODULES[1], MODULES[2]),
+ (gen_UDM_list(MODULES[2:]) + [gen_UIT([], MODULES[0:2], [])], MODULES[2:], MODULES[0:2]),
# TODO(pstodulk): test include files missing (relates #376)
])
-def test_generate_initram_disk(monkeypatch, input_msgs, modules):
+def test_generate_initram_disk(monkeypatch, input_msgs, dracut_modules, kernel_modules):
context = MockedContext()
curr_actor = CurrentActorMocked(msgs=input_msgs, arch=architecture.ARCH_X86_64)
monkeypatch.setattr(upgradeinitramfsgenerator.api, 'current_actor', curr_actor)
monkeypatch.setattr(upgradeinitramfsgenerator, 'copy_dracut_modules', MockedCopyArgs())
+ monkeypatch.setattr(upgradeinitramfsgenerator, '_get_target_kernel_version', lambda _: '')
+ monkeypatch.setattr(upgradeinitramfsgenerator, 'copy_kernel_modules', MockedCopyArgs())
monkeypatch.setattr(upgradeinitramfsgenerator, 'copy_boot_files', lambda dummy: None)
upgradeinitramfsgenerator.generate_initram_disk(context)
# test now just that all modules have been passed for copying - so we know
# all modules have been consumed
- detected_modules = set()
- _modules = set(modules) if isinstance(modules, list) else set([modules])
+ detected_dracut_modules = set()
+ _dracut_modules = set(dracut_modules) if isinstance(dracut_modules, list) else set([dracut_modules])
for dracut_module in upgradeinitramfsgenerator.copy_dracut_modules.args[1]:
module = (dracut_module.name, dracut_module.module_path)
- assert module in _modules
- detected_modules.add(module)
- assert detected_modules == _modules
+ assert module in _dracut_modules
+ detected_dracut_modules.add(module)
+ assert detected_dracut_modules == _dracut_modules
+
+ detected_kernel_modules = set()
+ _kernel_modules = set(kernel_modules) if isinstance(kernel_modules, list) else set([kernel_modules])
+ for kernel_module in upgradeinitramfsgenerator.copy_kernel_modules.args[1]:
+ module = (kernel_module.name, kernel_module.module_path)
+ assert module in _kernel_modules
+ detected_kernel_modules.add(module)
+ assert detected_kernel_modules == _kernel_modules
# TODO(pstodulk): this test is not created properly, as context.call check
# is skipped completely. Testing will more convenient with fixed #376
@@ -300,7 +322,8 @@ def test_copy_dracut_modules_rmtree_ignore(monkeypatch):
assert context.content
-def test_copy_dracut_modules_fail(monkeypatch):
+@pytest.mark.parametrize('kind', ['dracut', 'kernel'])
+def test_copy_modules_fail(monkeypatch, kind):
context = MockedContext()
def copytree_to_error(src, dst):
@@ -313,15 +336,30 @@ def test_copy_dracut_modules_fail(monkeypatch):
context.copytree_to = copytree_to_error
monkeypatch.setattr(os.path, 'exists', mock_context_path_exists)
monkeypatch.setattr(upgradeinitramfsgenerator.api, 'current_logger', MockedLogger())
- dmodules = [DracutModule(name='foo', module_path='/path/foo')]
+ monkeypatch.setattr(upgradeinitramfsgenerator, '_get_target_kernel_modules_dir', lambda _: '/kernel_modules')
+
+ module_class = None
+ copy_fn = None
+ if kind == 'dracut':
+ module_class = DracutModule
+ copy_fn = upgradeinitramfsgenerator.copy_dracut_modules
+ dst_path = 'dracut'
+ elif kind == 'kernel':
+ module_class = KernelModule
+ copy_fn = upgradeinitramfsgenerator.copy_kernel_modules
+ dst_path = 'kernel_modules'
+
+ modules = [module_class(name='foo', module_path='/path/foo')]
with pytest.raises(StopActorExecutionError) as err:
- upgradeinitramfsgenerator.copy_dracut_modules(context, dmodules)
- assert err.value.message.startswith('Failed to install dracut modules')
- expected_err_log = 'Failed to copy dracut module "foo" from "/path/foo" to "/base/dir/dracut"'
+ copy_fn(context, modules)
+ assert err.value.message.startswith('Failed to install {kind} modules'.format(kind=kind))
+ expected_err_log = 'Failed to copy {kind} module "foo" from "/path/foo" to "/base/dir/{dst_path}"'.format(
+ kind=kind, dst_path=dst_path)
assert expected_err_log in upgradeinitramfsgenerator.api.current_logger.errmsg
-def test_copy_dracut_modules_duplicate_skip(monkeypatch):
+@pytest.mark.parametrize('kind', ['dracut', 'kernel'])
+def test_copy_modules_duplicate_skip(monkeypatch, kind):
context = MockedContext()
def mock_context_path_exists(path):
@@ -330,10 +368,23 @@ def test_copy_dracut_modules_duplicate_skip(monkeypatch):
monkeypatch.setattr(os.path, 'exists', mock_context_path_exists)
monkeypatch.setattr(upgradeinitramfsgenerator.api, 'current_logger', MockedLogger())
- dm = DracutModule(name='foo', module_path='/path/foo')
- dmodules = [dm, dm]
- debugmsg = 'The foo dracut module has been already installed. Skipping.'
- upgradeinitramfsgenerator.copy_dracut_modules(context, dmodules)
+ monkeypatch.setattr(upgradeinitramfsgenerator, '_get_target_kernel_modules_dir', lambda _: '/kernel_modules')
+
+ module_class = None
+ copy_fn = None
+ if kind == 'dracut':
+ module_class = DracutModule
+ copy_fn = upgradeinitramfsgenerator.copy_dracut_modules
+ elif kind == 'kernel':
+ module_class = KernelModule
+ copy_fn = upgradeinitramfsgenerator.copy_kernel_modules
+
+ module = module_class(name='foo', module_path='/path/foo')
+ modules = [module, module]
+
+ copy_fn(context, modules)
+
+ debugmsg = 'The foo {kind} module has been already installed. Skipping.'.format(kind=kind)
assert context.content
assert len(context.called_copy_to) == 1
assert debugmsg in upgradeinitramfsgenerator.api.current_logger.dbgmsg
diff --git a/repos/system_upgrade/common/models/initramfs.py b/repos/system_upgrade/common/models/initramfs.py
index a5d1416e..03b71125 100644
--- a/repos/system_upgrade/common/models/initramfs.py
+++ b/repos/system_upgrade/common/models/initramfs.py
@@ -40,6 +40,46 @@ class DracutModule(Model):
"""
+class KernelModule(Model):
+ """
+ Specify a kernel module that should be included into the initramfs
+
+ The specified kernel module has to be compatible with the target system.
+
+ See the description of UpgradeInitramfsTasks and TargetInitramfsTasks
+ for more information about the role of initramfs in the in-place upgrade
+ process.
+ """
+ topic = BootPrepTopic
+
+ name = fields.String()
+ """
+ The kernel module that should be added (--add-drivers option of dracut)
+ when a initramfs is built. The possible options are
+
+ 1. ``=<kernel subdir>[/<kernel subdir>...]`` like ``=drivers/hid``
+ 2. ``<module name>``
+ """
+
+ module_path = fields.Nullable(fields.String(default=None))
+ """
+ module_path specifies kernel modules that are supposed to be copied
+
+ If the path is not set, the given name will just be activated. IOW,
+ if the kernel module is stored outside the /usr/lib/modules/$(uname -r)/
+ directory, set the absolute path to it, so leapp will manage it during
+ the upgrade to ensure the module will be added into the initramfs.
+
+ The module has to be stored on the local storage mounted in a persistent
+ fashion (/etc/fstab entry). In such a case, it is recommended to store it
+ into the 'files' directory of an actor generating this object.
+
+ Note: It's expected to set the full path from the host POV. In case
+ of actions inside containers, the module is still copied from the HOST
+ into the container workspace.
+ """
+
+
class UpgradeInitramfsTasks(Model):
"""
Influence generating of the (leapp) upgrade initramfs
@@ -73,6 +113,13 @@ class UpgradeInitramfsTasks(Model):
See the DracutModule model for more information.
"""
+ include_kernel_modules = fields.List(fields.Model(KernelModule), default=[])
+ """
+ List of kernel modules that should be installed in the initramfs.
+
+ See the KernelModule model for more information.
+ """
+
class TargetInitramfsTasks(UpgradeInitramfsTasks):
"""
--
2.41.0