leapp-repository/0093-raid-mdadm-include-configuration-in-initramfs.patch
Matej Matuska b31dbfa161 IPU 8.10 -> 9.9: CTC2 candidate 1
- Requires leapp-framework 6.6+
- Initial implementation of upgrades on systems with configured Software RAID
- Handle multipath device identification in upgrade environment
- Fix upgrade incorrectly resuming during SELinux relabeling
- Fix upgrades on systems with multiple LUKS devices
- Fix upgrades for systems with /boot/efi on a Software RAID
- Fix mount failures for FSTAB entries with the `nofail` option specified
- Fix AVC errors triggered by registration to insight-client during the upgrade
- Install the correct kernel for the source system kernel page size on ARM systems
- Inhibit the upgrade when network devices configured with ifcfg files could depend on NM
- Introduce rhui.obsolete_gpg_keys configuration option for the removal of obsoleted RPM GPG keys of RHUI Cloud providers
- LiveMode: Fix upgrade getting stuck when the `.leapp_upgrade_failed` file exists
- LiveMode: Fix system version determination in the upgrade environment on CentOS Stream
- Fix target system initramfs containing outdated configuration files in some circumstances
- Ensure SELinux is set to permissive mode during the upgrade even when enforcing=1 is set on the kernel cmdline
- Fix source and target distribution names in /etc/migration-results
- Fix upgrades with the RealTime kernel on CentOS Stream
- Detect misconfigured kernel & systemd API mountpoints in FSTAB
- Detect misconfigured /var/run on the source system
- Unify behaviour of upgrades on systems with enabled CRB repositories and explicitly required CRB repositories by user during the upgrade
- Drop the inconsistent report about the use of CRB (unsupported by Red Hat) repositories
- Fix upgrade crashing when dnf repofiles contain URL encoded characters

- Resolves: RHEL-3289, RHEL-17842, RHEL-36249, RHEL-56040, RHEL-56176, RHEL-60060, RHEL-76846, RHEL-104384, RHEL-145136, RHEL-148631, RHEL-162192, RHEL-185508
2026-06-29 13:26:50 +02:00

492 lines
17 KiB
Diff

From 28fd5ea3fccb9d62bb7de90916eaa7ee8943a50e Mon Sep 17 00:00:00 2001
From: Michal Hecko <mhecko@redhat.com>
Date: Thu, 28 May 2026 15:30:14 +0200
Subject: [PATCH 093/108] raid(mdadm): include configuration in initramfs
Add code to handle detection of mdraid arrays based on the outputs of
`mdadm --detail --scan`. The code is structured to handle raids in
general (there is also dm-raid), i.e., it follows the usual pattern:
scan, check; boot entry addition is not being modified for now. New
model RAIDInfo is introduced, which provides basic information about
RAID technologies used. To activate all arrays during boot, it is
necessary to have rd.md.uuid=UUID on the kernel cmdline for every RAID
array.
Jira-ref: RHEL-36249
---
.../common/actors/checkraid/actor.py | 22 ++++
.../actors/checkraid/libraries/checkraid.py | 33 +++++
.../actors/checkraid/tests/test_checkraid.py | 112 ++++++++++++++++
.../upgradeinitramfsgenerator/actor.py | 2 +
.../libraries/upgradeinitramfsgenerator.py | 5 +
.../common/actors/scanraid/actor.py | 21 +++
.../actors/scanraid/libraries/scanraid.py | 40 ++++++
.../actors/scanraid/tests/test_scanraid.py | 122 ++++++++++++++++++
.../system_upgrade/common/models/raidinfo.py | 19 +++
9 files changed, 376 insertions(+)
create mode 100644 repos/system_upgrade/common/actors/checkraid/actor.py
create mode 100644 repos/system_upgrade/common/actors/checkraid/libraries/checkraid.py
create mode 100644 repos/system_upgrade/common/actors/checkraid/tests/test_checkraid.py
create mode 100644 repos/system_upgrade/common/actors/scanraid/actor.py
create mode 100644 repos/system_upgrade/common/actors/scanraid/libraries/scanraid.py
create mode 100644 repos/system_upgrade/common/actors/scanraid/tests/test_scanraid.py
create mode 100644 repos/system_upgrade/common/models/raidinfo.py
diff --git a/repos/system_upgrade/common/actors/checkraid/actor.py b/repos/system_upgrade/common/actors/checkraid/actor.py
new file mode 100644
index 00000000..7af1124f
--- /dev/null
+++ b/repos/system_upgrade/common/actors/checkraid/actor.py
@@ -0,0 +1,22 @@
+from leapp.actors import Actor
+from leapp.libraries.actor import checkraid
+from leapp.models import RAIDInfo, TargetUserSpaceUpgradeTasks
+from leapp.tags import ChecksPhaseTag, IPUWorkflowTag
+
+
+class CheckRaid(Actor):
+ """
+ Ensure RAID configuration files are available in the target userspace.
+
+ If mdadm software RAID is in use, copies present mdadm configuration
+ files and directories into the target userspace container so that
+ dracut can include them in the upgrade initramfs.
+ """
+
+ name = 'check_raid'
+ consumes = (RAIDInfo,)
+ produces = (TargetUserSpaceUpgradeTasks,)
+ tags = (ChecksPhaseTag, IPUWorkflowTag)
+
+ def process(self):
+ checkraid.process()
diff --git a/repos/system_upgrade/common/actors/checkraid/libraries/checkraid.py b/repos/system_upgrade/common/actors/checkraid/libraries/checkraid.py
new file mode 100644
index 00000000..dbd02f54
--- /dev/null
+++ b/repos/system_upgrade/common/actors/checkraid/libraries/checkraid.py
@@ -0,0 +1,33 @@
+import os
+
+from leapp.libraries.stdlib import api
+from leapp.models import CopyFile, RAIDInfo, TargetUserSpaceUpgradeTasks
+
+# Host paths for mdadm configuration (see mdadm.conf(5) FILES section).
+MDADM_CONFIG_PATHS = (
+ '/etc/mdadm.conf',
+ '/etc/mdadm.conf.d',
+ '/etc/mdadm/mdadm.conf',
+ '/etc/mdadm/mdadm.conf.d',
+)
+
+
+def _mdadm_config_paths_present():
+ for path in MDADM_CONFIG_PATHS:
+ if os.path.isfile(path) or os.path.isdir(path):
+ yield path
+
+
+def process():
+ raid_info = next(api.consume(RAIDInfo), None)
+ if not raid_info or not raid_info.md_arrays:
+ return
+
+ copy_files = [CopyFile(src=path) for path in _mdadm_config_paths_present()]
+ if not copy_files:
+ api.current_logger().warning(
+ 'mdadm software RAID is in use but no mdadm configuration was found under: %s',
+ ', '.join(MDADM_CONFIG_PATHS),
+ )
+ else:
+ api.produce(TargetUserSpaceUpgradeTasks(copy_files=copy_files))
diff --git a/repos/system_upgrade/common/actors/checkraid/tests/test_checkraid.py b/repos/system_upgrade/common/actors/checkraid/tests/test_checkraid.py
new file mode 100644
index 00000000..02a6122a
--- /dev/null
+++ b/repos/system_upgrade/common/actors/checkraid/tests/test_checkraid.py
@@ -0,0 +1,112 @@
+import os
+
+from leapp.libraries.actor import checkraid
+from leapp.libraries.common.testutils import CurrentActorMocked, produce_mocked
+from leapp.libraries.stdlib import api
+from leapp.models import MDArray, RAIDInfo, TargetUserSpaceUpgradeTasks
+
+
+def _mock_path_checks(monkeypatch, files=(), directories=()):
+ def isfile(path):
+ return path in files
+
+ def isdir(path):
+ return path in directories
+
+ monkeypatch.setattr(os.path, 'isfile', isfile)
+ monkeypatch.setattr(os.path, 'isdir', isdir)
+
+
+def _md_arrays(*uuids):
+ return [MDArray(uuid=uuid) for uuid in uuids]
+
+
+def test_md_arrays_with_conf_dir(monkeypatch):
+ _mock_path_checks(
+ monkeypatch,
+ files=('/etc/mdadm.conf',),
+ directories=('/etc/mdadm.conf.d',),
+ )
+
+ msgs = [RAIDInfo(md_arrays=_md_arrays('aaa:bbb'))]
+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs))
+ monkeypatch.setattr(api, 'produce', produce_mocked())
+
+ checkraid.process()
+
+ assert api.produce.called == 1
+ copy_tasks = [m for m in api.produce.model_instances if isinstance(m, TargetUserSpaceUpgradeTasks)]
+ assert len(copy_tasks) == 1
+ assert [task.src for task in copy_tasks[0].copy_files] == [
+ '/etc/mdadm.conf',
+ '/etc/mdadm.conf.d',
+ ]
+
+
+def test_md_arrays_without_conf_dir(monkeypatch):
+ _mock_path_checks(monkeypatch, files=('/etc/mdadm.conf',))
+
+ msgs = [RAIDInfo(md_arrays=_md_arrays('aaa:bbb'))]
+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs))
+ monkeypatch.setattr(api, 'produce', produce_mocked())
+
+ checkraid.process()
+
+ copy_tasks = [m for m in api.produce.model_instances if isinstance(m, TargetUserSpaceUpgradeTasks)]
+ assert len(copy_tasks) == 1
+ assert [task.src for task in copy_tasks[0].copy_files] == ['/etc/mdadm.conf']
+
+
+def test_md_arrays_alternative_mdadm_conf(monkeypatch):
+ _mock_path_checks(
+ monkeypatch,
+ files=('/etc/mdadm/mdadm.conf',),
+ directories=('/etc/mdadm/mdadm.conf.d',),
+ )
+
+ msgs = [RAIDInfo(md_arrays=_md_arrays('aaa:bbb'))]
+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs))
+ monkeypatch.setattr(api, 'produce', produce_mocked())
+
+ checkraid.process()
+
+ copy_tasks = [m for m in api.produce.model_instances if isinstance(m, TargetUserSpaceUpgradeTasks)]
+ assert len(copy_tasks) == 1
+ assert [task.src for task in copy_tasks[0].copy_files] == [
+ '/etc/mdadm/mdadm.conf',
+ '/etc/mdadm/mdadm.conf.d',
+ ]
+
+
+def test_md_arrays_no_config_paths(monkeypatch):
+ _mock_path_checks(monkeypatch)
+
+ msgs = [RAIDInfo(md_arrays=_md_arrays('aaa:bbb'))]
+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs))
+ monkeypatch.setattr(api, 'produce', produce_mocked())
+
+ checkraid.process()
+
+ copy_tasks = [m for m in api.produce.model_instances if isinstance(m, TargetUserSpaceUpgradeTasks)]
+ assert not copy_tasks
+ assert not api.produce.called
+
+
+def test_no_raid_info(monkeypatch):
+ msgs = []
+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs))
+ monkeypatch.setattr(api, 'produce', produce_mocked())
+
+ checkraid.process()
+
+ assert not api.produce.called
+
+
+def test_no_md_arrays(monkeypatch):
+ msgs = [RAIDInfo(md_arrays=[])]
+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs))
+ monkeypatch.setattr(api, 'produce', produce_mocked())
+
+ checkraid.process()
+
+ assert not api.produce.called
diff --git a/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/actor.py b/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/actor.py
index c0c93036..32016ab0 100644
--- a/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/actor.py
+++ b/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/actor.py
@@ -7,6 +7,7 @@ from leapp.models import (
FIPSInfo,
LiveModeConfig,
LVMConfig,
+ RaidInfo,
TargetOSInstallationImage,
TargetUserSpaceInfo,
TargetUserSpaceUpgradeTasks,
@@ -33,6 +34,7 @@ class UpgradeInitramfsGenerator(Actor):
FIPSInfo,
LiveModeConfig,
LVMConfig,
+ RaidInfo,
RequiredUpgradeInitramPackages, # deprecated
TargetOSInstallationImage,
TargetUserSpaceInfo,
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 24f35847..f2b45ae7 100644
--- a/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/libraries/upgradeinitramfsgenerator.py
+++ b/repos/system_upgrade/common/actors/initramfs/upgradeinitramfsgenerator/libraries/upgradeinitramfsgenerator.py
@@ -14,6 +14,7 @@ from leapp.models import (
BootContent,
LiveModeConfig,
LVMConfig,
+ RaidInfo,
TargetOSInstallationImage,
TargetUserSpaceInfo,
TargetUserSpaceUpgradeTasks,
@@ -385,6 +386,10 @@ def generate_initram_disk(context):
if next(api.consume(LVMConfig), None):
env_variables.append('LEAPP_DRACUT_LVMCONF="1"')
+ raid_info = next(api.consume(RaidInfo), None)
+ if raid_info and raid_info.md_arrays:
+ env_variables.append('LEAPP_DRACUT_MDADMCONF="1"')
+
env_variables = ' '.join(env_variables)
env_variables = env_variables.format(
kernel_version=_get_target_kernel_version(context),
diff --git a/repos/system_upgrade/common/actors/scanraid/actor.py b/repos/system_upgrade/common/actors/scanraid/actor.py
new file mode 100644
index 00000000..62234b93
--- /dev/null
+++ b/repos/system_upgrade/common/actors/scanraid/actor.py
@@ -0,0 +1,21 @@
+from leapp.actors import Actor
+from leapp.libraries.actor import scanraid
+from leapp.models import DistributionSignedRPM, RAIDInfo
+from leapp.tags import FactsPhaseTag, IPUWorkflowTag
+
+
+class ScanRaid(Actor):
+ """
+ Detect whether software RAID is in use on the system.
+
+ Checks if the mdadm package is installed and whether any MD arrays
+ are currently assembled and active by scanning mdadm configuration.
+ """
+
+ name = 'scan_raid'
+ consumes = (DistributionSignedRPM,)
+ produces = (RAIDInfo,)
+ tags = (FactsPhaseTag, IPUWorkflowTag)
+
+ def process(self):
+ scanraid.process()
diff --git a/repos/system_upgrade/common/actors/scanraid/libraries/scanraid.py b/repos/system_upgrade/common/actors/scanraid/libraries/scanraid.py
new file mode 100644
index 00000000..a810a238
--- /dev/null
+++ b/repos/system_upgrade/common/actors/scanraid/libraries/scanraid.py
@@ -0,0 +1,40 @@
+import re
+
+from leapp.libraries.common.rpms import has_package
+from leapp.libraries.stdlib import api, CalledProcessError, run
+from leapp.models import DistributionSignedRPM, MDArray, RAIDInfo
+
+MDADM_SCAN_CMD = ['mdadm', '--detail', '--scan', '--verbose']
+UUID_PATTERN = re.compile(r'UUID=([0-9a-fA-F:]+)')
+
+
+def _scan_md_array_uuids():
+ try:
+ result = run(MDADM_SCAN_CMD)
+ except CalledProcessError as err:
+ api.current_logger().warning('Failed to scan mdadm arrays: %s', err)
+ return []
+
+ uuids = []
+ for line in result['stdout'].splitlines():
+ if not line.startswith('ARRAY '):
+ continue
+ match = UUID_PATTERN.search(line)
+ if match:
+ uuids.append(match.group(1))
+
+ return uuids
+
+
+def process():
+ if not has_package(DistributionSignedRPM, 'mdadm'):
+ api.current_logger().debug('The mdadm package is not installed. Skipping.')
+ return
+
+ uuids = _scan_md_array_uuids()
+ if not uuids:
+ api.current_logger().debug('No active mdadm software RAID arrays found.')
+ return
+
+ api.current_logger().info('Detected active mdadm software RAID arrays.')
+ api.produce(RAIDInfo(md_arrays=[MDArray(uuid=uuid) for uuid in uuids]))
diff --git a/repos/system_upgrade/common/actors/scanraid/tests/test_scanraid.py b/repos/system_upgrade/common/actors/scanraid/tests/test_scanraid.py
new file mode 100644
index 00000000..255be095
--- /dev/null
+++ b/repos/system_upgrade/common/actors/scanraid/tests/test_scanraid.py
@@ -0,0 +1,122 @@
+import pytest
+
+from leapp.libraries.actor import scanraid
+from leapp.libraries.common.testutils import CurrentActorMocked, logger_mocked, produce_mocked
+from leapp.libraries.stdlib import api, CalledProcessError
+from leapp.models import DistributionSignedRPM, MDArray, RAIDInfo, RPM
+
+_MDADM_RPM = RPM(
+ name='mdadm',
+ version='4.2',
+ release='1.el9',
+ epoch='0',
+ packager='',
+ arch='x86_64',
+ pgpsig='RSA/SHA256, Mon 01 Jan 1970 00:00:00 AM -03, Key ID 199e2f91fd431d51'
+)
+
+MDADM_SCAN_WITH_ARRAY = (
+ 'ARRAY /dev/md0 level=raid1 num-devices=2 metadata=1.2 '
+ 'name=localhost.localdomain:0 UUID=c4acea6e:d56e1598:91822e3f:fb26832c\n'
+)
+
+MDADM_SCAN_WITH_TWO_ARRAYS = (
+ 'ARRAY /dev/md0 metadata=1.2 UUID=c4acea6e:d56e1598:91822e3f:fb26832c\n'
+ 'ARRAY /dev/md1 metadata=1.2 UUID=5eb8cf98:a8d13c2e:3e91b4ca:2e1ac678\n'
+)
+
+
+class RunMocked:
+
+ def __init__(self, stdout='', raise_err=False):
+ self.called = 0
+ self.args = None
+ self.stdout = stdout
+ self.raise_err = raise_err
+
+ def __call__(self, args, encoding=None):
+ self.called += 1
+ self.args = args
+ if self.raise_err:
+ raise CalledProcessError(
+ message='A Leapp Command Error occurred.',
+ command=args,
+ result={'signal': None, 'exit_code': 1, 'pid': 0, 'stdout': 'fake', 'stderr': 'fake'}
+ )
+ assert args == scanraid.MDADM_SCAN_CMD
+ return {'stdout': self.stdout}
+
+
+def test_mdadm_not_installed(monkeypatch):
+ msgs = [DistributionSignedRPM(items=[])]
+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs))
+ monkeypatch.setattr(api, 'produce', produce_mocked())
+
+ scanraid.process()
+
+ assert not api.produce.called
+
+
+def test_mdadm_installed_with_active_arrays(monkeypatch):
+ run_mocked = RunMocked(stdout=MDADM_SCAN_WITH_ARRAY)
+ monkeypatch.setattr(scanraid, 'run', run_mocked)
+
+ msgs = [DistributionSignedRPM(items=[_MDADM_RPM])]
+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs))
+ monkeypatch.setattr(api, 'produce', produce_mocked())
+
+ scanraid.process()
+
+ assert run_mocked.called == 1
+ assert api.produce.called == 1
+ assert len(api.produce.model_instances) == 1
+ produced = api.produce.model_instances[0]
+ assert isinstance(produced, RAIDInfo)
+ assert produced.md_arrays == [MDArray(uuid='c4acea6e:d56e1598:91822e3f:fb26832c')]
+
+
+def test_mdadm_installed_with_multiple_arrays(monkeypatch):
+ run_mocked = RunMocked(stdout=MDADM_SCAN_WITH_TWO_ARRAYS)
+ monkeypatch.setattr(scanraid, 'run', run_mocked)
+
+ msgs = [DistributionSignedRPM(items=[_MDADM_RPM])]
+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs))
+ monkeypatch.setattr(api, 'produce', produce_mocked())
+
+ scanraid.process()
+
+ produced = api.produce.model_instances[0]
+ assert [md_array.uuid for md_array in produced.md_arrays] == [
+ 'c4acea6e:d56e1598:91822e3f:fb26832c',
+ '5eb8cf98:a8d13c2e:3e91b4ca:2e1ac678',
+ ]
+
+
+def test_mdadm_installed_no_active_arrays(monkeypatch):
+ run_mocked = RunMocked(stdout='')
+ monkeypatch.setattr(scanraid, 'run', run_mocked)
+
+ msgs = [DistributionSignedRPM(items=[_MDADM_RPM])]
+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs))
+ monkeypatch.setattr(api, 'produce', produce_mocked())
+
+ scanraid.process()
+
+ assert run_mocked.called == 1
+ assert not api.produce.called
+
+
+def test_mdadm_installed_scan_failure(monkeypatch):
+ run_mocked = RunMocked(raise_err=True)
+ monkeypatch.setattr(scanraid, 'run', run_mocked)
+ monkeypatch.setattr(api, 'current_logger', logger_mocked())
+
+ msgs = [DistributionSignedRPM(items=[_MDADM_RPM])]
+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs))
+ monkeypatch.setattr(api, 'produce', produce_mocked())
+
+ scanraid.process()
+
+ assert run_mocked.called == 1
+ assert not api.produce.called
+ assert api.current_logger.warnmsg
diff --git a/repos/system_upgrade/common/models/raidinfo.py b/repos/system_upgrade/common/models/raidinfo.py
new file mode 100644
index 00000000..69188f1e
--- /dev/null
+++ b/repos/system_upgrade/common/models/raidinfo.py
@@ -0,0 +1,19 @@
+from leapp.models import fields, Model
+from leapp.topics import SystemInfoTopic
+
+
+class MDArray(Model):
+ """Information about a single mdadm software RAID array."""
+
+ topic = SystemInfoTopic
+
+ uuid = fields.String()
+ """UUID of the mdadm array."""
+
+
+class RAIDInfo(Model):
+ """Information about RAID usage on the source system."""
+ topic = SystemInfoTopic
+
+ md_arrays = fields.List(fields.Model(MDArray), default=[])
+ """List of active mdadm software RAID arrays."""
--
2.54.0