Update AlmaLinux ELevate and Vendors patch to upstream b04d9168519eb0b388abe788577df566899d2eab (0.24.0-3)

The package version 0.24.0-3.elevate.2
This commit is contained in:
Yuriy Kohut 2026-08-07 12:16:28 +03:00
parent 1a861d5b07
commit 03b5babe4d
2 changed files with 407 additions and 1 deletions

View File

@ -906865,6 +906865,186 @@ index eeca8be0..949a731f 100644
checkmicroarchitecture.process()
diff --git a/repos/system_upgrade/common/actors/checkresumekernelarg/actor.py b/repos/system_upgrade/common/actors/checkresumekernelarg/actor.py
new file mode 100644
index 00000000..0c047f19
--- /dev/null
+++ b/repos/system_upgrade/common/actors/checkresumekernelarg/actor.py
@@ -0,0 +1,27 @@
+from leapp.actors import Actor
+from leapp.libraries.actor import checkresumekernelarg
+from leapp.models import KernelCmdline, TargetKernelCmdlineArgTasks, UpgradeKernelCmdlineArgTasks
+from leapp.tags import ChecksPhaseTag, IPUWorkflowTag
+
+
+class CheckResumeKernelArg(Actor):
+ """
+ Remove the resume argument from the upgrade boot entry.
+
+ The resume argument points to a swap device used for hibernation. During the
+ upgrade the dracut resume module is excluded, so the resume device cannot be
+ resolved and the system can hang during reboot. Therefore, we remove the
+ resume kernel command line argument from the upgrade boot entry to prevent
+ a possible hang during the upgrade reboot.
+
+ The argument is restored on the target kernel so hibernation keeps working
+ after the upgrade.
+ """
+
+ name = 'check_resume_kernel_arg'
+ consumes = (KernelCmdline,)
+ produces = (TargetKernelCmdlineArgTasks, UpgradeKernelCmdlineArgTasks)
+ tags = (ChecksPhaseTag, IPUWorkflowTag)
+
+ def process(self):
+ checkresumekernelarg.process()
diff --git a/repos/system_upgrade/common/actors/checkresumekernelarg/libraries/checkresumekernelarg.py b/repos/system_upgrade/common/actors/checkresumekernelarg/libraries/checkresumekernelarg.py
new file mode 100644
index 00000000..b881db6b
--- /dev/null
+++ b/repos/system_upgrade/common/actors/checkresumekernelarg/libraries/checkresumekernelarg.py
@@ -0,0 +1,35 @@
+from leapp.libraries.stdlib import api
+from leapp.models import KernelCmdline, TargetKernelCmdlineArgTasks, UpgradeKernelCmdlineArgTasks
+
+
+def process():
+ """
+ Remove resume from the upgrade boot entry and restore it on the target.
+
+ The upgrade initramfs omits the dracut resume module, so the resume device
+ cannot be resolved during the upgrade boot. This can cause the system to
+ hang waiting for the device. Stripping resume argument from the upgrade kernel
+ command line avoids the hang. The value is added back to the target kernel
+ entry via TargetKernelCmdlineArgTasks so hibernation continues working
+ after the upgrade.
+ """
+ cmdline = next(api.consume(KernelCmdline), None)
+ if not cmdline:
+ api.current_logger().debug('No KernelCmdline message received, nothing to do.')
+ return
+
+ resume_args = [arg for arg in cmdline.parameters if arg.key == 'resume']
+ if not resume_args:
+ api.current_logger().debug('No resume argument found on the kernel command line.')
+ return
+
+ api.current_logger().info(
+ 'Requesting removal of resume argument from the upgrade kernel command line: %s',
+ ['{}={}'.format(a.key, a.value or '') for a in resume_args]
+ )
+
+ api.produce(UpgradeKernelCmdlineArgTasks(to_remove=resume_args))
+ # When installing the target kernel RPM, the cmdline is copied from the booted system.
+ # As we remove the resume= args, we would accidentally remove them also from the
+ # target entry. Therefore, we add them back in here.
+ api.produce(TargetKernelCmdlineArgTasks(to_add=resume_args))
diff --git a/repos/system_upgrade/common/actors/checkresumekernelarg/tests/test_checkresumekernelarg.py b/repos/system_upgrade/common/actors/checkresumekernelarg/tests/test_checkresumekernelarg.py
new file mode 100644
index 00000000..c5f032f2
--- /dev/null
+++ b/repos/system_upgrade/common/actors/checkresumekernelarg/tests/test_checkresumekernelarg.py
@@ -0,0 +1,100 @@
+import pytest
+
+from leapp.libraries.actor import checkresumekernelarg
+from leapp.libraries.common.testutils import CurrentActorMocked, produce_mocked
+from leapp.libraries.stdlib import api
+from leapp.models import KernelCmdline, KernelCmdlineArg, TargetKernelCmdlineArgTasks, UpgradeKernelCmdlineArgTasks
+
+_COMMON_PARAMS = [
+ KernelCmdlineArg(key='ro', value=None),
+ KernelCmdlineArg(key='root', value='/dev/mapper/rhel-root'),
+]
+
+
+@pytest.mark.parametrize('resume_value', [
+ 'UUID=010b9c5c-5aca-469b-a852-fdf2fefcf817',
+ '/dev/mapper/rhel-swap',
+ '/dev/md/swap',
+ '/dev/md127',
+ '/dev/dm-1',
+])
+def test_resume_removed_from_upgrade_and_restored_on_target(monkeypatch, resume_value):
+ cmdline = KernelCmdline(parameters=_COMMON_PARAMS + [
+ KernelCmdlineArg(key='resume', value=resume_value),
+ ])
+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[cmdline]))
+ monkeypatch.setattr(api, 'produce', produce_mocked())
+
+ checkresumekernelarg.process()
+
+ upgrade_msgs = [m for m in api.produce.model_instances if isinstance(m, UpgradeKernelCmdlineArgTasks)]
+ assert len(upgrade_msgs) == 1
+ assert len(upgrade_msgs[0].to_remove) == 1
+ assert upgrade_msgs[0].to_remove[0].key == 'resume'
+ assert upgrade_msgs[0].to_remove[0].value == resume_value
+
+ target_msgs = [m for m in api.produce.model_instances if isinstance(m, TargetKernelCmdlineArgTasks)]
+ assert len(target_msgs) == 1
+ assert len(target_msgs[0].to_add) == 1
+ assert target_msgs[0].to_add[0].key == 'resume'
+ assert target_msgs[0].to_add[0].value == resume_value
+
+
+def test_multiple_resume_args_all_handled(monkeypatch):
+ cmdline = KernelCmdline(parameters=_COMMON_PARAMS + [
+ KernelCmdlineArg(key='resume', value='UUID=aaa-bbb'),
+ KernelCmdlineArg(key='resume', value='/dev/md127'),
+ ])
+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[cmdline]))
+ monkeypatch.setattr(api, 'produce', produce_mocked())
+
+ checkresumekernelarg.process()
+
+ upgrade_msgs = [m for m in api.produce.model_instances if isinstance(m, UpgradeKernelCmdlineArgTasks)]
+ assert len(upgrade_msgs) == 1
+ removed = {(a.key, a.value) for a in upgrade_msgs[0].to_remove}
+ assert removed == {('resume', 'UUID=aaa-bbb'), ('resume', '/dev/md127')}
+
+ target_msgs = [m for m in api.produce.model_instances if isinstance(m, TargetKernelCmdlineArgTasks)]
+ assert len(target_msgs) == 1
+ added = {(a.key, a.value) for a in target_msgs[0].to_add}
+ assert added == {('resume', 'UUID=aaa-bbb'), ('resume', '/dev/md127')}
+
+
+def test_resume_bare_key_without_value(monkeypatch):
+ cmdline = KernelCmdline(parameters=_COMMON_PARAMS + [
+ KernelCmdlineArg(key='resume', value=None),
+ ])
+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[cmdline]))
+ monkeypatch.setattr(api, 'produce', produce_mocked())
+
+ checkresumekernelarg.process()
+
+ upgrade_msgs = [m for m in api.produce.model_instances if isinstance(m, UpgradeKernelCmdlineArgTasks)]
+ assert len(upgrade_msgs) == 1
+ assert upgrade_msgs[0].to_remove[0].key == 'resume'
+ assert upgrade_msgs[0].to_remove[0].value is None
+
+ target_msgs = [m for m in api.produce.model_instances if isinstance(m, TargetKernelCmdlineArgTasks)]
+ assert len(target_msgs) == 1
+ assert target_msgs[0].to_add[0].key == 'resume'
+ assert target_msgs[0].to_add[0].value is None
+
+
+def test_no_resume_produces_nothing(monkeypatch):
+ cmdline = KernelCmdline(parameters=_COMMON_PARAMS)
+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[cmdline]))
+ monkeypatch.setattr(api, 'produce', produce_mocked())
+
+ checkresumekernelarg.process()
+
+ assert not api.produce.model_instances
+
+
+def test_no_kernel_cmdline_message_produces_nothing(monkeypatch):
+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[]))
+ monkeypatch.setattr(api, 'produce', produce_mocked())
+
+ checkresumekernelarg.process()
+
+ assert not api.produce.model_instances
diff --git a/repos/system_upgrade/common/actors/commonleappdracutmodules/files/dracut/85sys-upgrade-redhat/do-upgrade.sh b/repos/system_upgrade/common/actors/commonleappdracutmodules/files/dracut/85sys-upgrade-redhat/do-upgrade.sh
index 7d470459..af67cab5 100755
--- a/repos/system_upgrade/common/actors/commonleappdracutmodules/files/dracut/85sys-upgrade-redhat/do-upgrade.sh
@ -907231,6 +907411,229 @@ index aa735da3..fdb4c5ac 100644
+ to_reinstall=list(to_reinstall),
modules_to_reset=list(modules_to_reset.values()),
modules_to_enable=list(modules_to_enable.values())))
diff --git a/repos/system_upgrade/common/actors/initramfs/mount_units_generator/libraries/mount_unit_generator.py b/repos/system_upgrade/common/actors/initramfs/mount_units_generator/libraries/mount_unit_generator.py
index 900132f2..60355c40 100644
--- a/repos/system_upgrade/common/actors/initramfs/mount_units_generator/libraries/mount_unit_generator.py
+++ b/repos/system_upgrade/common/actors/initramfs/mount_units_generator/libraries/mount_unit_generator.py
@@ -9,6 +9,28 @@ from leapp.models import LiveModeConfig, StorageInfo, TargetUserSpaceInfo, Upgra
BIND_MOUNT_SYSROOT_BOOT_UNIT = 'boot.mount'
+# Virtual/pseudo filesystem types that should be treated as nofail during the upgrade initramfs phase.
+# Their mount units are kept but placed only in .wants (not .requires) directories, so systemd
+# will attempt to mount them but continue booting if the mount fails (e.g. mount point does not
+# exist under /sysroot).
+PSEUDO_FS_TYPES = frozenset([
+ 'hugetlbfs',
+ 'tmpfs',
+ 'devtmpfs',
+ 'devpts',
+ 'sysfs',
+ 'proc',
+ 'cgroup',
+ 'cgroup2',
+ 'securityfs',
+ 'selinuxfs',
+ 'debugfs',
+ 'mqueue',
+ 'pstore',
+ 'efivarfs',
+ 'bpf',
+])
+
def run_systemd_fstab_generator(output_directory):
api.current_logger().debug(
@@ -110,25 +132,33 @@ def prefix_all_mount_units_with_sysroot(dir_containing_units):
api.current_logger().debug('Original mount unit {} removed.'.format(unit_file_path))
-def is_unit_marked_with_nofail(unit_path: str) -> bool:
+def parse_unit(unit_path: str) -> dict:
try:
with open(unit_path) as in_file:
lines = [line.strip() for line in in_file.readlines()]
except OSError:
api.current_logger().debug(
- 'Could not read the unit {} to determine whether it is nofail.'.format(unit_path)
+ 'Could not read the unit {} to parse it contents.'.format(unit_path)
)
- return False # This way, the unit will end up in target's '.requires', so it is a safe choice
+ return {}
+
+ unit_properties = {}
for line in lines:
+ if not line.startswith(('Options', 'Type')):
+ continue
+ line_fragments = line.split('=', 1)
+ if len(line_fragments) <= 1:
+ continue # Should not happen
+
+ option_value = [opt.strip() for opt in line_fragments[1].split(',')]
+
if line.startswith('Options'):
- line_fragments = line.split('=', 1)
- if len(line_fragments) <= 1:
- continue # Should not happen
- used_options = [opt.strip() for opt in line_fragments[1].split(',')]
- if 'nofail' in used_options:
- return True
- return False
+ unit_properties['Options'] = option_value
+ elif line.startswith('Type'):
+ unit_properties['Type'] = option_value[0]
+
+ return unit_properties
def _fix_symlinks_in_dir(dir_containing_mount_units, target_dir):
@@ -164,8 +194,11 @@ def _fix_symlinks_in_dir(dir_containing_mount_units, target_dir):
if not unit_file.endswith('.mount'):
continue
- is_nofail = is_unit_marked_with_nofail(os.path.join(dir_containing_mount_units, unit_file))
- if is_nofail and will_units_be_required:
+ unit_full_path = os.path.join(dir_containing_mount_units, unit_file)
+ unit_properties = parse_unit(unit_full_path)
+ is_nofail = 'nofail' in unit_properties.get('Options', tuple())
+ is_pseudo_fs = unit_properties.get('Type') in PSEUDO_FS_TYPES
+ if (is_nofail or is_pseudo_fs) and will_units_be_required:
continue
place_fastlink_at = os.path.join(target_dir_path, unit_file)
@@ -226,6 +259,7 @@ def fix_symlinks_in_targets(dir_containing_mount_units):
'remote-fs-pre.target.requires',
'remote-fs-pre.target.wants',
]
+
for tdir in dir_list:
_fix_symlinks_in_dir(dir_containing_mount_units, tdir)
diff --git a/repos/system_upgrade/common/actors/initramfs/mount_units_generator/tests/test_mount_unit_generation.py b/repos/system_upgrade/common/actors/initramfs/mount_units_generator/tests/test_mount_unit_generation.py
index 99f26ad1..1d4cb80d 100644
--- a/repos/system_upgrade/common/actors/initramfs/mount_units_generator/tests/test_mount_unit_generation.py
+++ b/repos/system_upgrade/common/actors/initramfs/mount_units_generator/tests/test_mount_unit_generation.py
@@ -134,6 +134,50 @@ def test_prefix_all_mount_units_with_sysroot(monkeypatch):
assert should_be_deleted == was_deleted
+def test_parse_unit_missing_file(monkeypatch, leapp_tmpdir):
+ """A non-existent unit file results in an empty dict without raising."""
+ monkeypatch.setattr(api, 'current_logger', logger_mocked())
+
+ unit_path = os.path.join(leapp_tmpdir, 'does-not-exist.mount')
+
+ assert mount_unit_generator.parse_unit(unit_path) == {}
+ assert api.current_logger.dbgmsg
+
+
+def test_parse_unit_parses_options_and_type(leapp_tmpdir):
+ """Options and Type keys are extracted, Options being split into a list."""
+ unit_path = os.path.join(leapp_tmpdir, 'home.mount')
+ with open(unit_path, 'w') as unit_file:
+ unit_file.write(
+ '[Unit]\n'
+ 'Description=Mount unit for /home\n'
+ '\n'
+ '[Mount]\n'
+ 'What=/dev/sda1\n'
+ 'Where=/home\n'
+ 'Type=xfs\n'
+ 'Options=defaults,nofail,x-systemd.device-timeout=0\n'
+ )
+
+ unit_properties = mount_unit_generator.parse_unit(unit_path)
+
+ assert unit_properties == {
+ 'Type': 'xfs',
+ 'Options': ['defaults', 'nofail', 'x-systemd.device-timeout=0'],
+ }
+
+
+def test_parse_unit_strips_whitespace_around_options(leapp_tmpdir):
+ """Whitespace surrounding comma-separated Options values is stripped."""
+ unit_path = os.path.join(leapp_tmpdir, 'var.mount')
+ with open(unit_path, 'w') as unit_file:
+ unit_file.write('Options = nofail , x-systemd.device-timeout=0 \n')
+
+ unit_properties = mount_unit_generator.parse_unit(unit_path)
+
+ assert unit_properties == {'Options': ['nofail', 'x-systemd.device-timeout=0']}
+
+
@pytest.mark.parametrize('dirname', (
'local-fs.target.requires',
'local-fs.target.wants',
@@ -328,18 +372,56 @@ def test_injection_of_sysroot_boot_bindmount_unit(monkeypatch, has_separate_boot
assert was_copyfile_for_sysroot_boot_called
-TEST_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'files')
+@pytest.mark.parametrize('dirname', (
+ 'local-fs.target.requires',
+ 'local-fs.target.wants',
+))
+def test_fix_symlinks_in_dir_skips_pseudo_fs_in_requires(monkeypatch, dirname):
+ """Pseudo FS mount units should be excluded from .requires directories."""
+
+ DIR_PATH = os.path.join('/test/dir/', dirname)
+
+ def mock_rmtree(dir_path):
+ assert dir_path == DIR_PATH
+
+ def mock_mkdir(dir_path):
+ assert dir_path == DIR_PATH
+
+ def mock_listdir(dir_path):
+ return ['sysroot-home.mount', 'sysroot-hugepages.mount', 'not-a-mount.service', 'sysroot-nofail.mount']
+
+ def mock_os_path_exist(dir_path):
+ assert dir_path == DIR_PATH
+ return dir_path == DIR_PATH
+ def mock_parse_unit(unit_path):
+ return {
+ 'Type': 'hugetlbfs' if 'hugepages' in unit_path else 'xfs',
+ 'Options': ['nofail'] if 'nofail' in unit_path else ['']
+ }
-@pytest.mark.parametrize(
- ('unit_filename', 'is_nofail'),
- [
- ('unit.mount', False),
- ('unit-nofail.mount', True),
- ('non-existing-unit.mount', False), # This file does not exist in the files/
+ expected_calls = [
+ ['ln', '-s', '../sysroot-home.mount', os.path.join(DIR_PATH, 'sysroot-home.mount')],
]
-)
-def test_is_unit_marked_with_nofail(unit_filename, is_nofail):
- unit_path = os.path.join(TEST_DIR, unit_filename)
- determined_no_fail = mount_unit_generator.is_unit_marked_with_nofail(unit_path)
- assert determined_no_fail == is_nofail
+ if dirname.endswith('.wants'):
+ expected_calls.extend((
+ ['ln', '-s', '../sysroot-hugepages.mount', os.path.join(DIR_PATH, 'sysroot-hugepages.mount')],
+ ['ln', '-s', '../sysroot-nofail.mount', os.path.join(DIR_PATH, 'sysroot-nofail.mount')]
+ ))
+
+ def mock_run(command):
+ assert command in expected_calls
+ return {
+ "stdout": "",
+ "stderr": "",
+ "exit_code": 0,
+ }
+
+ monkeypatch.setattr('shutil.rmtree', mock_rmtree)
+ monkeypatch.setattr('os.mkdir', mock_mkdir)
+ monkeypatch.setattr('os.listdir', mock_listdir)
+ monkeypatch.setattr('os.path.exists', mock_os_path_exist)
+ monkeypatch.setattr(mount_unit_generator, 'run', mock_run)
+ monkeypatch.setattr(mount_unit_generator, 'parse_unit', mock_parse_unit)
+
+ mount_unit_generator._fix_symlinks_in_dir('/test/dir', dirname)
diff --git a/repos/system_upgrade/common/actors/missinggpgkeysinhibitor/libraries/missinggpgkey.py b/repos/system_upgrade/common/actors/missinggpgkeysinhibitor/libraries/missinggpgkey.py
index 32e4527b..1e595e9a 100644
--- a/repos/system_upgrade/common/actors/missinggpgkeysinhibitor/libraries/missinggpgkey.py

View File

@ -53,7 +53,7 @@ py2_byte_compile "%1" "%2"}
Epoch: 1
Name: leapp-repository
Version: 0.24.0
Release: 3%{?dist}.elevate.1
Release: 3%{?dist}.elevate.2
Summary: Repositories for leapp
License: ASL 2.0
@ -567,6 +567,9 @@ fi
%changelog
* Fri Aug 07 2026 Yuriy Kohut <ykohut@almalinux.org> - 0.24.0-3.elevate.2
- ELevate vendors support for upstream 0.24.0-3 version (b04d9168519eb0b388abe788577df566899d2eab)
* Thu Jul 25 2026 Matej Matuska <mmatuska@redhat.com> - 0.24.0-3
- Requires leapp-framework 6.6+
- Initial implementation of upgrades on systems with configured Software RAID