IPU 9.6-10.0: CTC 2 candidate 2

- Detect XFS file systems with problematic parameters
- Raise an inhibitor if unsupported target version supplied instead of error
- Prevent a possible crash with LiveMode when adding the upgrade boot entry on systems with LVM
- Resolves: RHEL-57043, RHEL-52309, RHEL-60034
This commit is contained in:
Petr Stodulka 2025-01-29 15:26:03 +01:00
parent 34e892dbf6
commit 74a92adf20
11 changed files with 3205 additions and 1 deletions

View File

@ -0,0 +1,26 @@
From 57fa7a5781f6cff6ab1632d83e971c4bc395fc20 Mon Sep 17 00:00:00 2001
From: Matej Matuska <mmatuska@redhat.com>
Date: Wed, 22 Jan 2025 14:50:06 +0100
Subject: [PATCH 54/63] linter: Fix line too long in postgresqlcheck
---
.../actors/postgresqlcheck/libraries/postgresqlcheck.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/repos/system_upgrade/el8toel9/actors/postgresqlcheck/libraries/postgresqlcheck.py b/repos/system_upgrade/el8toel9/actors/postgresqlcheck/libraries/postgresqlcheck.py
index eefe583b..1cc5362d 100644
--- a/repos/system_upgrade/el8toel9/actors/postgresqlcheck/libraries/postgresqlcheck.py
+++ b/repos/system_upgrade/el8toel9/actors/postgresqlcheck/libraries/postgresqlcheck.py
@@ -9,7 +9,8 @@ report_server_inst_summary = (
' PostgreSQL server 13 by default, which is incompatible with 9.6, 10 and 12'
' included in RHEL-8, in those cases, it is necessary to proceed with additional steps'
' for the complete upgrade of the PostgreSQL data.'
- 'If the database has already been upgraded, meaning the system is already using PostgreSQL 13, then no further actions are required.'
+ 'If the database has already been upgraded, meaning the system is already using PostgreSQL 13,'
+ ' then no further actions are required.'
)
report_server_inst_hint = (
--
2.48.1

View File

@ -0,0 +1,39 @@
From bfcf59e5f78aa7500a6524094bdebf28d359d9d5 Mon Sep 17 00:00:00 2001
From: Petr Stodulka <pstodulk@redhat.com>
Date: Thu, 23 Jan 2025 11:44:59 +0100
Subject: [PATCH 55/63] Fix typos in comments to make spellchecker happy
---
.../common/actors/filterrpmtransactionevents/actor.py | 2 +-
.../selinuxcontentscanner/libraries/selinuxcontentscanner.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/repos/system_upgrade/common/actors/filterrpmtransactionevents/actor.py b/repos/system_upgrade/common/actors/filterrpmtransactionevents/actor.py
index 5ccdb35b..582a5821 100644
--- a/repos/system_upgrade/common/actors/filterrpmtransactionevents/actor.py
+++ b/repos/system_upgrade/common/actors/filterrpmtransactionevents/actor.py
@@ -14,7 +14,7 @@ class FilterRpmTransactionTasks(Actor):
In order to calculate a working DNF Upgrade transaction, Leapp can collect data from multiple
sources and find workarounds for possible problems. This actor will filter all collected
- workarounds and keep only those relevants to current system based on installed packages.
+ workarounds and keep only those relevant to current system based on installed packages.
"""
name = 'check_rpm_transaction_events'
diff --git a/repos/system_upgrade/common/actors/selinux/selinuxcontentscanner/libraries/selinuxcontentscanner.py b/repos/system_upgrade/common/actors/selinux/selinuxcontentscanner/libraries/selinuxcontentscanner.py
index 8f5e31ab..1ef69fe6 100644
--- a/repos/system_upgrade/common/actors/selinux/selinuxcontentscanner/libraries/selinuxcontentscanner.py
+++ b/repos/system_upgrade/common/actors/selinux/selinuxcontentscanner/libraries/selinuxcontentscanner.py
@@ -126,7 +126,7 @@ def get_selinux_modules():
for (name, priority) in modules:
# Udica templates should not be transferred, we only need a list of their
- # names and priorities so that we can reinstall their latest verisions
+ # names and priorities so that we can reinstall their latest versions
if name in UDICA_TEMPLATES:
template_list.append(
SELinuxModule(
--
2.48.1

View File

@ -0,0 +1,43 @@
From c8614161017104559d224c33a52648e2c317b8e0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Michal=20He=C4=8Dko?= <michal.sk.com@gmail.com>
Date: Tue, 28 Jan 2025 15:08:39 +0100
Subject: [PATCH 56/63] fix(add_upgrade_boot_entry): convert arg list into a
tuple (#1313)
Convert collected rd.lvm args into a tuple before trying to make
a set with one of the elements being the args. As list is not hashable,
this causes the actor to crash.
---
.../actors/addupgradebootentry/libraries/addupgradebootentry.py | 2 +-
.../addupgradebootentry/tests/unit_test_addupgradebootentry.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/repos/system_upgrade/common/actors/addupgradebootentry/libraries/addupgradebootentry.py b/repos/system_upgrade/common/actors/addupgradebootentry/libraries/addupgradebootentry.py
index b236e39b..981c9401 100644
--- a/repos/system_upgrade/common/actors/addupgradebootentry/libraries/addupgradebootentry.py
+++ b/repos/system_upgrade/common/actors/addupgradebootentry/libraries/addupgradebootentry.py
@@ -303,7 +303,7 @@ def _get_rdlvm_arg_values():
api.current_logger().debug('Collected the following rd.lvm.lv args that are undesired for the squashfs: %s',
rd_lvm_values)
- return rd_lvm_values
+ return tuple(rd_lvm_values)
def construct_cmdline_args_for_livemode():
diff --git a/repos/system_upgrade/common/actors/addupgradebootentry/tests/unit_test_addupgradebootentry.py b/repos/system_upgrade/common/actors/addupgradebootentry/tests/unit_test_addupgradebootentry.py
index 2f58ba9e..dde18782 100644
--- a/repos/system_upgrade/common/actors/addupgradebootentry/tests/unit_test_addupgradebootentry.py
+++ b/repos/system_upgrade/common/actors/addupgradebootentry/tests/unit_test_addupgradebootentry.py
@@ -273,7 +273,7 @@ def test_get_rdlvm_arg_values(monkeypatch):
args = addupgradebootentry._get_rdlvm_arg_values()
- assert args == ['A', 'B']
+ assert args == ('A', 'B')
def test_get_device_uuid(monkeypatch):
--
2.48.1

View File

@ -0,0 +1,251 @@
From 1207dececb3911efacc4ba548b2d173c0f604a41 Mon Sep 17 00:00:00 2001
From: Michal Hecko <mhecko@redhat.com>
Date: Thu, 16 Jan 2025 18:54:34 +0100
Subject: [PATCH 57/63] fix(arm,bootloader,efi): patch grub.cfg used for
upgrading
Use the grub.cfg bundled within leapp if we detect that
system's grub.cfg contains problematic configuration which
will not load grubenv of the upgrade BLS entry. We need
to ensure that this grubenv is loaded, as without it we
cannot guarantee a successful boot into upgrade environment.
---
.../files/grub2_config_template | 26 +++++
.../libraries/addupgradebootloader.py | 102 +++++++++++++++++-
.../tests/test_addarmbootloaderworkaround.py | 29 +++++
.../libraries/removeupgradeefientry.py | 4 +-
4 files changed, 158 insertions(+), 3 deletions(-)
create mode 100644 repos/system_upgrade/el8toel9/actors/addarmbootloaderworkaround/files/grub2_config_template
diff --git a/repos/system_upgrade/el8toel9/actors/addarmbootloaderworkaround/files/grub2_config_template b/repos/system_upgrade/el8toel9/actors/addarmbootloaderworkaround/files/grub2_config_template
new file mode 100644
index 00000000..83de1417
--- /dev/null
+++ b/repos/system_upgrade/el8toel9/actors/addarmbootloaderworkaround/files/grub2_config_template
@@ -0,0 +1,26 @@
+set timeout=0
+
+# Make sure to load EFI/leapp/grubenv and not system's default path
+if [ -f ${config_directory}/grubenv ]; then
+ load_env -f ${config_directory}/grubenv
+elif [ -s $prefix/grubenv ]; then
+ load_env
+fi
+
+# EFI/leapp/grubenv contains our upgrade BLS entry as saved_entry
+if [ "${next_entry}" ] ; then
+ set default="${next_entry}"
+ set next_entry=
+ save_env next_entry
+ set boot_once=true
+else
+ set default="${saved_entry}"
+fi
+
+search --no-floppy --set=root --fs-uuid LEAPP_BOOT_UUID
+set boot=${root}
+function load_video {
+ insmod all_video
+}
+${serial}${terminal_input}${terminal_output}
+blscfg
diff --git a/repos/system_upgrade/el8toel9/actors/addarmbootloaderworkaround/libraries/addupgradebootloader.py b/repos/system_upgrade/el8toel9/actors/addarmbootloaderworkaround/libraries/addupgradebootloader.py
index 5e9bf5c6..01db9bbf 100644
--- a/repos/system_upgrade/el8toel9/actors/addarmbootloaderworkaround/libraries/addupgradebootloader.py
+++ b/repos/system_upgrade/el8toel9/actors/addarmbootloaderworkaround/libraries/addupgradebootloader.py
@@ -6,6 +6,7 @@ from leapp.libraries.common import mounting
from leapp.libraries.common.grub import (
canonical_path_to_efi_format,
EFIBootInfo,
+ get_boot_partition,
get_device_number,
get_efi_device,
get_efi_partition,
@@ -26,6 +27,15 @@ RHEL_EFIDIR_CANONICAL_PATH = os.path.join(EFI_MOUNTPOINT, 'EFI/redhat/')
CONTAINER_DOWNLOAD_DIR = '/tmp_pkg_download_dir'
+LEAPP_GRUB2_CFG_TEMPLATE = 'grub2_config_template'
+"""
+Our grub configuration file template that is used in case the system's grubcfg would not load our grubenv.
+
+The template contains placeholders named with LEAPP_*, that need to be replaced in order to
+obtain a valid config.
+
+"""
+
def _copy_file(src_path, dst_path):
if os.path.exists(dst_path):
@@ -49,11 +59,14 @@ def process():
context.copytree_from(RHEL_EFIDIR_CANONICAL_PATH, LEAPP_EFIDIR_CANONICAL_PATH)
_copy_grub_files(['grubenv', 'grub.cfg'], ['user.cfg'])
- _link_grubenv_to_upgrade_entry()
efibootinfo = EFIBootInfo()
current_boot_entry = efibootinfo.entries[efibootinfo.current_bootnum]
upgrade_boot_entry = _add_upgrade_boot_entry(efibootinfo)
+
+ leapp_efi_grubenv = os.path.join(EFI_MOUNTPOINT, LEAPP_EFIDIR_CANONICAL_PATH, 'grubenv')
+ patch_efi_redhat_grubcfg_to_load_correct_grubenv()
+
_set_bootnext(upgrade_boot_entry.boot_number)
efibootentry_fields = ['boot_number', 'label', 'active', 'efi_bin_source']
@@ -183,3 +196,90 @@ def _set_bootnext(boot_number):
run(['/usr/sbin/efibootmgr', '--bootnext', boot_number])
except CalledProcessError:
raise StopActorExecutionError('Could not set boot entry {} as BootNext.'.format(boot_number))
+
+
+def _notify_user_to_check_grub2_cfg():
+ # Or maybe rather ask a question in a dialog? But this is rare, so maybe continuing is fine.
+ pass
+
+
+def _will_grubcfg_read_our_grubenv(grubcfg_path):
+ with open(grubcfg_path) as grubcfg:
+ config_lines = grubcfg.readlines()
+
+ will_read = False
+ for line in config_lines:
+ if line.strip() == 'load_env -f ${config_directory}/grubenv':
+ will_read = True
+ break
+
+ return will_read
+
+
+def _get_boot_device_uuid():
+ boot_device = get_boot_partition()
+ try:
+ raw_device_info_lines = run(['blkid', boot_device], split=True)['stdout']
+ raw_device_info = raw_device_info_lines[0] # There is only 1 output line
+
+ uuid_needle_start_pos = raw_device_info.index('UUID')
+ raw_device_info = raw_device_info[uuid_needle_start_pos:] # results in: "UUID="..." ....
+
+ uuid = raw_device_info.split(' ', 1)[0] # UUID cannot contain spaces
+ uuid = uuid[len('UUID='):] # Remove UUID=
+ uuid = uuid.strip('"')
+ return uuid
+
+ except CalledProcessError as error:
+ details = {'details': 'blkid failed with error: {}'.format(error)}
+ raise StopActorExecutionError('Failed to obtain UUID of /boot partition', details=details)
+
+
+def _prepare_config_contents():
+ config_template_path = api.get_actor_file_path(LEAPP_GRUB2_CFG_TEMPLATE)
+ with open(config_template_path) as config_template_handle:
+ config_template = config_template_handle.read()
+
+ substitutions = {
+ 'LEAPP_BOOT_UUID': _get_boot_device_uuid()
+ }
+
+ api.current_logger().debug(
+ 'Applying the following substitution map to grub config template: {}'.format(substitutions)
+ )
+
+ for placeholder, placeholder_value in substitutions.items():
+ config_template = config_template.replace(placeholder, placeholder_value)
+
+ return config_template
+
+
+def _write_config(config_path, config_contents):
+ with open(config_path, 'w') as grub_cfg_handle:
+ grub_cfg_handle.write(config_contents)
+
+
+def patch_efi_redhat_grubcfg_to_load_correct_grubenv():
+ """
+ Replaces /boot/efi/EFI/redhat/grub2.cfg with a patched grub2.cfg shipped in leapp.
+
+ The grub2.cfg shipped on some AWS images omits the section that loads grubenv different
+ EFI entries. Thus, we need to replace it with our own that will load grubenv shipped
+ of our UEFI boot entry.
+ """
+ leapp_grub_cfg_path = os.path.join(EFI_MOUNTPOINT, LEAPP_EFIDIR_CANONICAL_PATH, 'grub.cfg')
+
+ if not os.path.isfile(leapp_grub_cfg_path):
+ msg = 'The file {} does not exists, cannot check whether bootloader is configured properly.'
+ raise StopActorExecutionError(msg.format(leapp_grub_cfg_path))
+
+ if _will_grubcfg_read_our_grubenv(leapp_grub_cfg_path):
+ api.current_logger().debug('The current grub.cfg will read our grubenv without any modifications.')
+ return
+
+ api.current_logger().info('Current grub2.cfg is likely faulty (would not read our grubenv), patching.')
+
+ config_contents = _prepare_config_contents()
+ _write_config(leapp_grub_cfg_path, config_contents)
+
+ api.current_logger().info('New upgrade grub.cfg has been written to {}'.format(leapp_grub_cfg_path))
diff --git a/repos/system_upgrade/el8toel9/actors/addarmbootloaderworkaround/tests/test_addarmbootloaderworkaround.py b/repos/system_upgrade/el8toel9/actors/addarmbootloaderworkaround/tests/test_addarmbootloaderworkaround.py
index d2015272..9ab3b7d0 100644
--- a/repos/system_upgrade/el8toel9/actors/addarmbootloaderworkaround/tests/test_addarmbootloaderworkaround.py
+++ b/repos/system_upgrade/el8toel9/actors/addarmbootloaderworkaround/tests/test_addarmbootloaderworkaround.py
@@ -298,6 +298,9 @@ def test_process(monkeypatch):
monkeypatch.setattr(addupgradebootloader, '_add_upgrade_boot_entry', mock_add_upgrade_boot_entry)
monkeypatch.setattr(addupgradebootloader, '_set_bootnext', lambda _: None)
+ monkeypatch.setattr(addupgradebootloader, 'patch_efi_redhat_grubcfg_to_load_correct_grubenv',
+ lambda: None)
+
addupgradebootloader.process()
assert api.produce.called == 1
@@ -307,6 +310,32 @@ def test_process(monkeypatch):
expected = ArmWorkaroundEFIBootloaderInfo(
original_entry=EFIBootEntry(**{f: getattr(TEST_RHEL_EFI_ENTRY, f) for f in efibootentry_fields}),
upgrade_entry=EFIBootEntry(**{f: getattr(TEST_UPGRADE_EFI_ENTRY, f) for f in efibootentry_fields}),
+ upgrade_bls_dir='/boot/upgrade-loader/entries',
+ upgrade_entry_efi_path='/boot/efi/EFI/leapp/',
)
actual = api.produce.model_instances[0]
assert actual == expected
+
+
+@pytest.mark.parametrize('is_config_ok', (True, False))
+def test_patch_grubcfg(is_config_ok, monkeypatch):
+
+ expected_grubcfg_path = os.path.join(addupgradebootloader.EFI_MOUNTPOINT,
+ addupgradebootloader.LEAPP_EFIDIR_CANONICAL_PATH,
+ 'grub.cfg')
+ def isfile_mocked(path):
+ assert expected_grubcfg_path == path
+ return True
+
+ def prepare_config_contents_mocked():
+ return 'config contents'
+
+ def write_config(path, contents):
+ assert not is_config_ok # We should write only when the config is not OK
+ assert path == expected_grubcfg_path
+ assert contents == 'config contents'
+
+ monkeypatch.setattr(os.path, 'isfile', isfile_mocked)
+ monkeypatch.setattr(addupgradebootloader, '_will_grubcfg_read_our_grubenv', lambda cfg_path: is_config_ok)
+ monkeypatch.setattr(addupgradebootloader, '_prepare_config_contents', prepare_config_contents_mocked)
+ monkeypatch.setattr(addupgradebootloader, '_write_config', write_config)
diff --git a/repos/system_upgrade/el8toel9/actors/removeupgradeefientry/libraries/removeupgradeefientry.py b/repos/system_upgrade/el8toel9/actors/removeupgradeefientry/libraries/removeupgradeefientry.py
index 3ff3ead9..97ede80a 100644
--- a/repos/system_upgrade/el8toel9/actors/removeupgradeefientry/libraries/removeupgradeefientry.py
+++ b/repos/system_upgrade/el8toel9/actors/removeupgradeefientry/libraries/removeupgradeefientry.py
@@ -35,8 +35,8 @@ def remove_upgrade_efi_entry():
bootloader_info = get_workaround_efi_info()
- _copy_grub_files(['grubenv', 'grub.cfg'], ['user.cfg'])
- _link_grubenv_to_rhel_entry()
+ # _copy_grub_files(['grubenv', 'grub.cfg'], ['user.cfg'])
+ # _link_grubenv_to_rhel_entry()
upgrade_boot_number = bootloader_info.upgrade_entry.boot_number
try:
--
2.48.1

View File

@ -0,0 +1,524 @@
From b71b666594043ef6076c0c6220aeb54e7ee3a2a4 Mon Sep 17 00:00:00 2001
From: Michal Hecko <mhecko@redhat.com>
Date: Mon, 20 Jan 2025 14:59:37 +0100
Subject: [PATCH 58/63] feat(arm,bootloader,efi): use separate BLS directory
for upgrades
Use a separate BLS directory '/boot/upgrade-loader/entries'
that mimics '/boot/loader/entries'. This allows very fine
control of what boot entries are available when booting
into upgrade environment via a separate EFI entry.
---
.../actors/addupgradebootentry/actor.py | 2 +
.../libraries/addupgradebootentry.py | 97 +++++++++++++++++++
.../tests/unit_test_addupgradebootentry.py | 53 ++++++++++
.../actors/removeupgradebootentry/actor.py | 4 +-
.../libraries/removeupgradebootentry.py | 10 +-
.../tests/unit_test_removeupgradebootentry.py | 41 +++++---
.../libraries/addupgradebootloader.py | 4 +-
.../tests/test_addarmbootloaderworkaround.py | 3 +-
.../libraries/removeupgradeefientry.py | 14 ++-
.../tests/test_removeupgradeefientry.py | 11 ++-
.../el8toel9/models/upgradeefientry.py | 16 +++
11 files changed, 232 insertions(+), 23 deletions(-)
diff --git a/repos/system_upgrade/common/actors/addupgradebootentry/actor.py b/repos/system_upgrade/common/actors/addupgradebootentry/actor.py
index e4ecf39e..9698f3c2 100644
--- a/repos/system_upgrade/common/actors/addupgradebootentry/actor.py
+++ b/repos/system_upgrade/common/actors/addupgradebootentry/actor.py
@@ -4,6 +4,7 @@ from leapp.actors import Actor
from leapp.exceptions import StopActorExecutionError
from leapp.libraries.actor.addupgradebootentry import add_boot_entry, fix_grub_config_error
from leapp.models import (
+ ArmWorkaroundEFIBootloaderInfo,
BootContent,
FirmwareFacts,
GrubConfigError,
@@ -28,6 +29,7 @@ class AddUpgradeBootEntry(Actor):
name = 'add_upgrade_boot_entry'
consumes = (
+ ArmWorkaroundEFIBootloaderInfo,
BootContent,
GrubConfigError,
FirmwareFacts,
diff --git a/repos/system_upgrade/common/actors/addupgradebootentry/libraries/addupgradebootentry.py b/repos/system_upgrade/common/actors/addupgradebootentry/libraries/addupgradebootentry.py
index 981c9401..53b57e95 100644
--- a/repos/system_upgrade/common/actors/addupgradebootentry/libraries/addupgradebootentry.py
+++ b/repos/system_upgrade/common/actors/addupgradebootentry/libraries/addupgradebootentry.py
@@ -1,11 +1,13 @@
import itertools
import os
import re
+import shutil
from leapp.exceptions import StopActorExecutionError
from leapp.libraries.common.config import architecture, get_env
from leapp.libraries.stdlib import api, CalledProcessError, run
from leapp.models import (
+ ArmWorkaroundEFIBootloaderInfo,
BootContent,
KernelCmdline,
KernelCmdlineArg,
@@ -197,6 +199,8 @@ def add_boot_entry(configs=None):
details={'details': '{}: {}'.format(str(e), e.stderr)}
)
+ apply_arm_specific_modifications()
+
def _remove_old_upgrade_boot_entry(kernel_dst_path, configs=None):
"""
@@ -357,3 +361,96 @@ def construct_cmdline_args_for_livemode():
api.current_logger().info('The use of live mode image implies the following cmdline args: %s', args)
return args
+
+
+def _list_grubenv_variables():
+ try:
+ output_lines = run(['grub2-editenv', 'list'], split=True)['stdout']
+ except CalledProcessError:
+ raise StopActorExecutionError('Failed to list grubenv variables used by the system')
+
+ vars_with_values = {}
+ for line in output_lines:
+ var_with_value = line.split('=', 1)
+ if len(var_with_value) <= 1:
+ api.current_logger().warning(
+ 'Skipping \'{}\' in grub2-editenv output, the line does not have the form <var>=<value>'
+ )
+ continue
+ vars_with_values[var_with_value[0]] = var_with_value[1]
+
+ return vars_with_values
+
+
+def apply_arm_specific_modifications():
+ arm_efi_info = next(api.consume(ArmWorkaroundEFIBootloaderInfo), None)
+ if not arm_efi_info:
+ return
+
+ modify_our_grubenv_to_have_separate_blsdir(arm_efi_info)
+
+
+def modify_our_grubenv_to_have_separate_blsdir(efi_info):
+ """ Create a new blsdir for the upgrade entry if using a separate EFI entry. """
+ leapp_efi_grubenv_path = os.path.join(efi_info.upgrade_entry_efi_path, 'grubenv')
+
+ api.current_logger().debug(
+ 'Setting up separate blsdir for the upgrade using grubenv: {}'.format(leapp_efi_grubenv_path)
+ )
+
+ grubenv_vars = _list_grubenv_variables()
+ system_bls_dir = grubenv_vars.get('blsdir', '/loader/entries').lstrip('/')
+
+ # BLS dir is relative to /boot, prepend it so we can list its contents
+ system_bls_dir = os.path.join('/boot', system_bls_dir)
+
+ # Find our loader entry
+ try:
+ bls_entries = os.listdir(system_bls_dir)
+ except IOError: # Technically, we want FileNotFoundError, but that is only Python3.3+, so this is fine
+ details = {
+ 'details': 'Failed to list {}.'.format(system_bls_dir)
+ }
+ raise StopActorExecutionError('Failed to set up bootloader for the upgrade.', details=details)
+
+ leapp_bls_entry = None
+ for bls_entry in bls_entries:
+ if bls_entry.endswith('upgrade.aarch64.conf'):
+ leapp_bls_entry = bls_entry
+ break
+
+ if not leapp_bls_entry:
+ details = {
+ 'details': 'Failed to identify BLS entry that belongs to leapp in {}'.format(system_bls_dir)
+ }
+ raise StopActorExecutionError('Failed to set up bootloader for the upgrade.')
+
+ # The 'blsdir' grubenv variable specifies location of bls directory relative to /boot
+ if os.path.exists(efi_info.upgrade_bls_dir):
+ msg = 'The {} directory exists, probably a left-over from previous executions. Removing.'
+ api.current_logger().debug(msg.format(efi_info.upgrade_bls_dir))
+ shutil.rmtree(efi_info.upgrade_bls_dir)
+
+ os.makedirs(efi_info.upgrade_bls_dir)
+ api.current_logger().debug('Successfully created upgrade BLS directory: {}'.format(efi_info.upgrade_bls_dir))
+
+ leapp_bls_entry_fullpath = os.path.join(system_bls_dir, leapp_bls_entry)
+ bls_entry_dst = os.path.join(efi_info.upgrade_bls_dir, leapp_bls_entry)
+ api.current_logger().debug(
+ 'Moving leapp\'s BLS entry ({}) into a separate BLS dir located at {}'.format(
+ leapp_bls_entry, efi_info.upgrade_bls_dir
+ )
+ )
+
+ shutil.move(leapp_bls_entry_fullpath, bls_entry_dst)
+
+ upgrade_bls_dir_rel_to_boot = efi_info.upgrade_bls_dir[len('/boot'):]
+
+ # Modify leapp's grubenv to define our own BLSDIR
+ try:
+ run(['grub2-editenv', leapp_efi_grubenv_path, 'set', 'blsdir={}'.format(upgrade_bls_dir_rel_to_boot)])
+ except CalledProcessError as error:
+ details = {
+ 'details': 'Failed to modify upgrade grubenv to contain a custom blsdir definition. Error {}'.format(error)
+ }
+ raise StopActorExecutionError('Failed to set up bootloader for the upgrade.', details=details)
diff --git a/repos/system_upgrade/common/actors/addupgradebootentry/tests/unit_test_addupgradebootentry.py b/repos/system_upgrade/common/actors/addupgradebootentry/tests/unit_test_addupgradebootentry.py
index dde18782..2a0b3f0f 100644
--- a/repos/system_upgrade/common/actors/addupgradebootentry/tests/unit_test_addupgradebootentry.py
+++ b/repos/system_upgrade/common/actors/addupgradebootentry/tests/unit_test_addupgradebootentry.py
@@ -1,4 +1,5 @@
import os
+import shutil
from collections import namedtuple
import pytest
@@ -9,7 +10,9 @@ from leapp.libraries.common.config.architecture import ARCH_S390X, ARCH_X86_64
from leapp.libraries.common.testutils import CurrentActorMocked, produce_mocked
from leapp.libraries.stdlib import api
from leapp.models import (
+ ArmWorkaroundEFIBootloaderInfo,
BootContent,
+ EFIBootEntry,
KernelCmdline,
KernelCmdlineArg,
LateTargetKernelCmdlineArgTasks,
@@ -326,3 +329,53 @@ def test_get_device_uuid(monkeypatch):
uuid = addupgradebootentry._get_device_uuid(path)
assert uuid == 'MY_UUID1'
+
+
+def test_modify_grubenv_to_have_separate_blsdir(monkeypatch):
+ efi_info = ArmWorkaroundEFIBootloaderInfo(
+ original_entry=EFIBootEntry(
+ boot_number='0001',
+ label='Redhat',
+ active=True,
+ efi_bin_source="HD(.*)/File(\\EFI\\redhat\\shimx64.efi)",
+ ),
+ upgrade_entry=EFIBootEntry(
+ boot_number='0002',
+ label='Leapp',
+ active=True,
+ efi_bin_source="HD(.*)/File(\\EFI\\leapp\\shimx64.efi)",
+ ),
+ upgrade_bls_dir='/boot/upgrade-loader/entries',
+ upgrade_entry_efi_path='/boot/efi/EFI/leapp'
+ )
+
+ def list_grubenv_variables_mock():
+ return {
+ 'blsdir': '/blsdir'
+ }
+
+ def listdir_mock(dir_path):
+ assert dir_path == '/boot/blsdir'
+ return [
+ '4a9c76478b98444fb5e0fbf533950edf-6.12.5-200.fc41.x86_64.conf',
+ '4a9c76478b98444fb5e0fbf533950edf-upgrade.aarch64.conf',
+ ]
+
+ def assert_path_correct(path):
+ assert path == efi_info.upgrade_bls_dir
+
+ def move_mocked(src, dst):
+ assert src == '/boot/blsdir/4a9c76478b98444fb5e0fbf533950edf-upgrade.aarch64.conf'
+ assert dst == '/boot/upgrade-loader/entries/4a9c76478b98444fb5e0fbf533950edf-upgrade.aarch64.conf'
+
+ def run_mocked(cmd, *arg, **kwargs):
+ assert cmd == ['grub2-editenv', '/boot/efi/EFI/leapp/grubenv', 'set', 'blsdir=/upgrade-loader/entries']
+
+ monkeypatch.setattr(addupgradebootentry, '_list_grubenv_variables', list_grubenv_variables_mock)
+ monkeypatch.setattr(os, 'listdir', listdir_mock)
+ monkeypatch.setattr(os.path, 'exists', assert_path_correct)
+ monkeypatch.setattr(os, 'makedirs', assert_path_correct)
+ monkeypatch.setattr(shutil, 'move', move_mocked)
+ monkeypatch.setattr(addupgradebootentry, 'run', run_mocked)
+
+ addupgradebootentry.modify_our_grubenv_to_have_separate_blsdir(efi_info)
diff --git a/repos/system_upgrade/common/actors/removeupgradebootentry/actor.py b/repos/system_upgrade/common/actors/removeupgradebootentry/actor.py
index 32759e77..6a0a1081 100644
--- a/repos/system_upgrade/common/actors/removeupgradebootentry/actor.py
+++ b/repos/system_upgrade/common/actors/removeupgradebootentry/actor.py
@@ -1,6 +1,6 @@
from leapp.actors import Actor
from leapp.libraries.actor.removeupgradebootentry import remove_boot_entry
-from leapp.models import BootContent, FirmwareFacts
+from leapp.models import ArmWorkaroundEFIBootloaderInfo, BootContent, FirmwareFacts
from leapp.tags import InitRamStartPhaseTag, IPUWorkflowTag
@@ -12,7 +12,7 @@ class RemoveUpgradeBootEntry(Actor):
"""
name = 'remove_upgrade_boot_entry'
- consumes = (BootContent, FirmwareFacts)
+ consumes = (ArmWorkaroundEFIBootloaderInfo, BootContent, FirmwareFacts)
produces = ()
tags = (IPUWorkflowTag, InitRamStartPhaseTag)
diff --git a/repos/system_upgrade/common/actors/removeupgradebootentry/libraries/removeupgradebootentry.py b/repos/system_upgrade/common/actors/removeupgradebootentry/libraries/removeupgradebootentry.py
index ee8e1ecd..7434e48c 100644
--- a/repos/system_upgrade/common/actors/removeupgradebootentry/libraries/removeupgradebootentry.py
+++ b/repos/system_upgrade/common/actors/removeupgradebootentry/libraries/removeupgradebootentry.py
@@ -1,7 +1,7 @@
from leapp.exceptions import StopActorExecutionError
from leapp.libraries.common.config import architecture
from leapp.libraries.stdlib import api, CalledProcessError, run
-from leapp.models import BootContent, FirmwareFacts
+from leapp.models import ArmWorkaroundEFIBootloaderInfo, BootContent, FirmwareFacts
def remove_boot_entry():
@@ -25,6 +25,14 @@ def remove_boot_entry():
# partitions have been most likely already mounted
pass
kernel_filepath = get_upgrade_kernel_filepath()
+
+ arm_bootloader_workaround_info = next(api.consume(ArmWorkaroundEFIBootloaderInfo), None)
+ if arm_bootloader_workaround_info and arm_bootloader_workaround_info.upgrade_bls_dir:
+ # Leapp has a separate BLS dir and grubby will not know about it. We don't need to call
+ # grubby here - we are removing the entire BLS dir in another actor.
+ api.current_logger().debug('Skipping removal of upgrade kernel entry since we are using a separate BLS dir.')
+ return
+
run([
'/usr/sbin/grubby',
'--remove-kernel={0}'.format(kernel_filepath)
diff --git a/repos/system_upgrade/common/actors/removeupgradebootentry/tests/unit_test_removeupgradebootentry.py b/repos/system_upgrade/common/actors/removeupgradebootentry/tests/unit_test_removeupgradebootentry.py
index 54eec552..c84d3085 100644
--- a/repos/system_upgrade/common/actors/removeupgradebootentry/tests/unit_test_removeupgradebootentry.py
+++ b/repos/system_upgrade/common/actors/removeupgradebootentry/tests/unit_test_removeupgradebootentry.py
@@ -5,11 +5,12 @@ from leapp.libraries.actor import removeupgradebootentry
from leapp.libraries.common.config import architecture
from leapp.libraries.common.testutils import CurrentActorMocked, logger_mocked
from leapp.libraries.stdlib import api
-from leapp.models import BootContent, FirmwareFacts
+from leapp.models import ArmWorkaroundEFIBootloaderInfo, BootContent, EFIBootEntry, FirmwareFacts
class run_mocked(object):
- args = []
+ def __init__(self):
+ self.args = []
def __call__(self, args, split=True):
self.args.append(args)
@@ -17,17 +18,25 @@ class run_mocked(object):
@pytest.mark.parametrize('firmware', ['bios', 'efi'])
@pytest.mark.parametrize('arch', [architecture.ARCH_X86_64, architecture.ARCH_S390X])
-def test_remove_boot_entry(firmware, arch, monkeypatch):
+@pytest.mark.parametrize('has_separate_bls_dir', [True, False])
+def test_remove_boot_entry(firmware, arch, has_separate_bls_dir, monkeypatch):
def get_upgrade_kernel_filepath_mocked():
return '/abc'
- def consume_systemfacts_mocked(*models):
- yield FirmwareFacts(firmware=firmware)
-
- monkeypatch.setattr(removeupgradebootentry, 'get_upgrade_kernel_filepath', get_upgrade_kernel_filepath_mocked, )
- monkeypatch.setattr(api, 'consume', consume_systemfacts_mocked)
+ messages = [FirmwareFacts(firmware=firmware)]
+ if has_separate_bls_dir:
+ some_efi_entry = EFIBootEntry(boot_number='0001', label='entry', active=True, efi_bin_source='')
+ workaround_info = ArmWorkaroundEFIBootloaderInfo(
+ original_entry=some_efi_entry,
+ upgrade_entry=some_efi_entry,
+ upgrade_bls_dir='/boot/upgrade-loader/entries',
+ upgrade_entry_efi_path='/boot/efi/EFI/leapp/'
+ )
+ messages.append(workaround_info)
+
+ monkeypatch.setattr(removeupgradebootentry, 'get_upgrade_kernel_filepath', get_upgrade_kernel_filepath_mocked)
monkeypatch.setattr(removeupgradebootentry, 'run', run_mocked())
- monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(arch))
+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(arch, msgs=messages))
monkeypatch.setattr(api, 'current_logger', logger_mocked())
removeupgradebootentry.remove_boot_entry()
@@ -36,16 +45,16 @@ def test_remove_boot_entry(firmware, arch, monkeypatch):
if firmware == 'efi':
boot_mounts.append(['/bin/mount', '/boot/efi'])
- calls = boot_mounts + [['/usr/sbin/grubby', '--remove-kernel=/abc']]
- if arch == architecture.ARCH_S390X:
- calls.append(['/usr/sbin/zipl'])
- calls.append(['/bin/mount', '-a'])
+ calls = boot_mounts
+ if not has_separate_bls_dir:
+ # If we are using a separate BLS dir (ARM specific), then do not call anything
+ calls += [['/usr/sbin/grubby', '--remove-kernel=/abc']]
+ if arch == architecture.ARCH_S390X:
+ calls.append(['/usr/sbin/zipl'])
+ calls.append(['/bin/mount', '-a'])
assert removeupgradebootentry.run.args == calls
- # clear args for next run
- del removeupgradebootentry.run.args[:]
-
def test_get_upgrade_kernel_filepath(monkeypatch):
# BootContent message available
diff --git a/repos/system_upgrade/el8toel9/actors/addarmbootloaderworkaround/libraries/addupgradebootloader.py b/repos/system_upgrade/el8toel9/actors/addarmbootloaderworkaround/libraries/addupgradebootloader.py
index 01db9bbf..27621185 100644
--- a/repos/system_upgrade/el8toel9/actors/addarmbootloaderworkaround/libraries/addupgradebootloader.py
+++ b/repos/system_upgrade/el8toel9/actors/addarmbootloaderworkaround/libraries/addupgradebootloader.py
@@ -24,6 +24,7 @@ ARM_GRUB_PACKAGE_NAME = 'grub2-efi-aa64'
EFI_MOUNTPOINT = '/boot/efi/'
LEAPP_EFIDIR_CANONICAL_PATH = os.path.join(EFI_MOUNTPOINT, 'EFI/leapp/')
RHEL_EFIDIR_CANONICAL_PATH = os.path.join(EFI_MOUNTPOINT, 'EFI/redhat/')
+UPGRADE_BLS_DIR = '/boot/upgrade-loader'
CONTAINER_DOWNLOAD_DIR = '/tmp_pkg_download_dir'
@@ -64,7 +65,6 @@ def process():
current_boot_entry = efibootinfo.entries[efibootinfo.current_bootnum]
upgrade_boot_entry = _add_upgrade_boot_entry(efibootinfo)
- leapp_efi_grubenv = os.path.join(EFI_MOUNTPOINT, LEAPP_EFIDIR_CANONICAL_PATH, 'grubenv')
patch_efi_redhat_grubcfg_to_load_correct_grubenv()
_set_bootnext(upgrade_boot_entry.boot_number)
@@ -74,6 +74,8 @@ def process():
ArmWorkaroundEFIBootloaderInfo(
original_entry=EFIBootEntry(**{f: getattr(current_boot_entry, f) for f in efibootentry_fields}),
upgrade_entry=EFIBootEntry(**{f: getattr(upgrade_boot_entry, f) for f in efibootentry_fields}),
+ upgrade_bls_dir=UPGRADE_BLS_DIR,
+ upgrade_entry_efi_path=os.path.join(EFI_MOUNTPOINT, LEAPP_EFIDIR_CANONICAL_PATH),
)
)
diff --git a/repos/system_upgrade/el8toel9/actors/addarmbootloaderworkaround/tests/test_addarmbootloaderworkaround.py b/repos/system_upgrade/el8toel9/actors/addarmbootloaderworkaround/tests/test_addarmbootloaderworkaround.py
index 9ab3b7d0..7017e645 100644
--- a/repos/system_upgrade/el8toel9/actors/addarmbootloaderworkaround/tests/test_addarmbootloaderworkaround.py
+++ b/repos/system_upgrade/el8toel9/actors/addarmbootloaderworkaround/tests/test_addarmbootloaderworkaround.py
@@ -310,7 +310,7 @@ def test_process(monkeypatch):
expected = ArmWorkaroundEFIBootloaderInfo(
original_entry=EFIBootEntry(**{f: getattr(TEST_RHEL_EFI_ENTRY, f) for f in efibootentry_fields}),
upgrade_entry=EFIBootEntry(**{f: getattr(TEST_UPGRADE_EFI_ENTRY, f) for f in efibootentry_fields}),
- upgrade_bls_dir='/boot/upgrade-loader/entries',
+ upgrade_bls_dir=addupgradebootloader.UPGRADE_BLS_DIR,
upgrade_entry_efi_path='/boot/efi/EFI/leapp/',
)
actual = api.produce.model_instances[0]
@@ -323,6 +323,7 @@ def test_patch_grubcfg(is_config_ok, monkeypatch):
expected_grubcfg_path = os.path.join(addupgradebootloader.EFI_MOUNTPOINT,
addupgradebootloader.LEAPP_EFIDIR_CANONICAL_PATH,
'grub.cfg')
+
def isfile_mocked(path):
assert expected_grubcfg_path == path
return True
diff --git a/repos/system_upgrade/el8toel9/actors/removeupgradeefientry/libraries/removeupgradeefientry.py b/repos/system_upgrade/el8toel9/actors/removeupgradeefientry/libraries/removeupgradeefientry.py
index 97ede80a..3a32ddcc 100644
--- a/repos/system_upgrade/el8toel9/actors/removeupgradeefientry/libraries/removeupgradeefientry.py
+++ b/repos/system_upgrade/el8toel9/actors/removeupgradeefientry/libraries/removeupgradeefientry.py
@@ -54,6 +54,8 @@ def remove_upgrade_efi_entry():
except CalledProcessError:
api.current_logger().warning('Unable to remove Leapp upgrade efi files.')
+ _remove_upgrade_blsdir(bootloader_info)
+
original_boot_number = bootloader_info.original_entry.boot_number
run(['/usr/sbin/efibootmgr', '--bootnext', original_boot_number])
@@ -82,7 +84,7 @@ def _copy_file(src_path, dst_path):
def _copy_grub_files(required, optional):
"""
- Copy grub files from redhat/ dir to the /boot/efi/EFI/leapp/ dir.
+ Copy grub files from /boot/efi/EFI/leapp/ dir to the /boot/efi/EFI/redhat/ dir.
"""
all_files = required + optional
@@ -98,3 +100,13 @@ def _copy_grub_files(required, optional):
continue
_copy_file(src_path, dst_path)
+
+
+def _remove_upgrade_blsdir(bootloader_info):
+ api.current_logger().debug('Removing upgrade BLS directory: {}'.format(bootloader_info.upgrade_bls_dir))
+ try:
+ shutil.rmtree(bootloader_info.upgrade_bls_dir)
+ except OSError as error:
+ # I tried, no can do at this point
+ msg = 'Failed to remove upgrade BLS directory: {} with error {}'
+ api.current_logger().debug(msg.format(bootloader_info.upgrade_bls_dir, error))
diff --git a/repos/system_upgrade/el8toel9/actors/removeupgradeefientry/tests/test_removeupgradeefientry.py b/repos/system_upgrade/el8toel9/actors/removeupgradeefientry/tests/test_removeupgradeefientry.py
index 1af3cd1e..30fde2da 100644
--- a/repos/system_upgrade/el8toel9/actors/removeupgradeefientry/tests/test_removeupgradeefientry.py
+++ b/repos/system_upgrade/el8toel9/actors/removeupgradeefientry/tests/test_removeupgradeefientry.py
@@ -1,4 +1,5 @@
import os
+import shutil
import pytest
@@ -20,7 +21,9 @@ TEST_EFI_INFO = ArmWorkaroundEFIBootloaderInfo(
label='Leapp',
active=True,
efi_bin_source="HD(.*)/File(\\EFI\\leapp\\shimx64.efi)",
- )
+ ),
+ upgrade_bls_dir='/boot/upgrade-loaders/entries',
+ upgrade_entry_efi_path='/boot/efi/EFI/leapp'
)
@@ -89,9 +92,14 @@ def test_remove_upgrade_efi_entry(monkeypatch):
def mock_copy_grub_files(required, optional):
copy_grub_files_calls.append((required, optional))
+ def rmtree_mocked(tree, *args):
+ run_calls.append('shutil.rmtree')
+ assert tree == TEST_EFI_INFO.upgrade_bls_dir
+
monkeypatch.setattr(removeupgradeefientry, '_copy_grub_files', mock_copy_grub_files)
monkeypatch.setattr(removeupgradeefientry, '_link_grubenv_to_rhel_entry', lambda: None)
monkeypatch.setattr(removeupgradeefientry, 'run', mock_run)
+ monkeypatch.setattr(shutil, 'rmtree', rmtree_mocked)
removeupgradeefientry.remove_upgrade_efi_entry()
@@ -100,6 +108,7 @@ def test_remove_upgrade_efi_entry(monkeypatch):
['/bin/mount', '/boot/efi'],
['/usr/sbin/efibootmgr', '--delete-bootnum', '--bootnum', '0002'],
['rm', '-rf', removeupgradeefientry.LEAPP_EFIDIR_CANONICAL_PATH],
+ 'shutil.rmtree',
['/usr/sbin/efibootmgr', '--bootnext', '0001'],
['/bin/mount', '-a'],
]
diff --git a/repos/system_upgrade/el8toel9/models/upgradeefientry.py b/repos/system_upgrade/el8toel9/models/upgradeefientry.py
index 877cdc8f..f29fc88f 100644
--- a/repos/system_upgrade/el8toel9/models/upgradeefientry.py
+++ b/repos/system_upgrade/el8toel9/models/upgradeefientry.py
@@ -12,3 +12,19 @@ class ArmWorkaroundEFIBootloaderInfo(Model):
original_entry = fields.Model(EFIBootEntry)
upgrade_entry = fields.Model(EFIBootEntry)
+
+ upgrade_bls_dir = fields.String()
+ """
+ Path to custom BLS dir used by the upgrade EFI bootloader
+
+ The path is absolute w.r.t. '/'. The actual value of the 'blsdir' variable
+ that is set in the upgrade grubenv will be relative to '/boot/'.
+ """
+
+ upgrade_entry_efi_path = fields.String()
+ """
+ Full path to the folder containing EFI binaries for the upgrade entry.
+
+ Example:
+ /boot/efi/EFI/leapp
+ """
--
2.48.1

View File

@ -0,0 +1,21 @@
From 8fe49982ee048d6b74aec4f4537ea9f1b4a7e021 Mon Sep 17 00:00:00 2001
From: Michal Hecko <mhecko@redhat.com>
Date: Mon, 27 Jan 2025 10:58:48 +0100
Subject: [PATCH 59/63] fix(models): move arm bootloader workaround model into
common
Move model used to implement arm bootloader workarounds to common
as this model will be also used when adding/removing kernel entries
to use custom blsdir.
---
.../system_upgrade/{el8toel9 => common}/models/upgradeefientry.py | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename repos/system_upgrade/{el8toel9 => common}/models/upgradeefientry.py (100%)
diff --git a/repos/system_upgrade/el8toel9/models/upgradeefientry.py b/repos/system_upgrade/common/models/upgradeefientry.py
similarity index 100%
rename from repos/system_upgrade/el8toel9/models/upgradeefientry.py
rename to repos/system_upgrade/common/models/upgradeefientry.py
--
2.48.1

View File

@ -0,0 +1,184 @@
From 31af8f485f6bb78b4aed665857daa956aa79adf1 Mon Sep 17 00:00:00 2001
From: Michal Hecko <mhecko@redhat.com>
Date: Tue, 28 Jan 2025 15:09:12 +0100
Subject: [PATCH 60/63] cleanup(8to9,efi): do not use symlinks or copy grub
files
---
.../libraries/addupgradebootloader.py | 10 +----
.../tests/test_addarmbootloaderworkaround.py | 1 -
.../libraries/removeupgradeefientry.py | 41 -------------------
.../tests/test_removeupgradeefientry.py | 35 ----------------
4 files changed, 1 insertion(+), 86 deletions(-)
diff --git a/repos/system_upgrade/el8toel9/actors/addarmbootloaderworkaround/libraries/addupgradebootloader.py b/repos/system_upgrade/el8toel9/actors/addarmbootloaderworkaround/libraries/addupgradebootloader.py
index 27621185..c076fe6b 100644
--- a/repos/system_upgrade/el8toel9/actors/addarmbootloaderworkaround/libraries/addupgradebootloader.py
+++ b/repos/system_upgrade/el8toel9/actors/addarmbootloaderworkaround/libraries/addupgradebootloader.py
@@ -9,9 +9,7 @@ from leapp.libraries.common.grub import (
get_boot_partition,
get_device_number,
get_efi_device,
- get_efi_partition,
- GRUB2_BIOS_ENTRYPOINT,
- GRUB2_BIOS_ENV_FILE
+ get_efi_partition
)
from leapp.libraries.stdlib import api, CalledProcessError, run
from leapp.models import ArmWorkaroundEFIBootloaderInfo, EFIBootEntry, TargetUserSpaceInfo
@@ -118,12 +116,6 @@ def _copy_grub_files(required, optional):
_copy_file(src_path, dst_path)
-def _link_grubenv_to_upgrade_entry():
- upgrade_env_file = os.path.join(LEAPP_EFIDIR_CANONICAL_PATH, 'grubenv')
- upgrade_env_file_relpath = os.path.relpath(upgrade_env_file, GRUB2_BIOS_ENTRYPOINT)
- run(['ln', '--symbolic', '--force', upgrade_env_file_relpath, GRUB2_BIOS_ENV_FILE])
-
-
def _add_upgrade_boot_entry(efibootinfo):
"""
Create a new UEFI bootloader entry with a upgrade label and bin file.
diff --git a/repos/system_upgrade/el8toel9/actors/addarmbootloaderworkaround/tests/test_addarmbootloaderworkaround.py b/repos/system_upgrade/el8toel9/actors/addarmbootloaderworkaround/tests/test_addarmbootloaderworkaround.py
index 7017e645..4f990e00 100644
--- a/repos/system_upgrade/el8toel9/actors/addarmbootloaderworkaround/tests/test_addarmbootloaderworkaround.py
+++ b/repos/system_upgrade/el8toel9/actors/addarmbootloaderworkaround/tests/test_addarmbootloaderworkaround.py
@@ -287,7 +287,6 @@ def test_process(monkeypatch):
monkeypatch.setattr(addupgradebootloader.mounting, 'NspawnActions', lambda *args, **kwargs: context_mock)
monkeypatch.setattr(addupgradebootloader, '_copy_grub_files', lambda optional, required: None)
- monkeypatch.setattr(addupgradebootloader, '_link_grubenv_to_upgrade_entry', lambda: None)
efibootinfo_mock = MockEFIBootInfo([TEST_RHEL_EFI_ENTRY])
monkeypatch.setattr(addupgradebootloader, 'EFIBootInfo', lambda: efibootinfo_mock)
diff --git a/repos/system_upgrade/el8toel9/actors/removeupgradeefientry/libraries/removeupgradeefientry.py b/repos/system_upgrade/el8toel9/actors/removeupgradeefientry/libraries/removeupgradeefientry.py
index 3a32ddcc..daa7b2ca 100644
--- a/repos/system_upgrade/el8toel9/actors/removeupgradeefientry/libraries/removeupgradeefientry.py
+++ b/repos/system_upgrade/el8toel9/actors/removeupgradeefientry/libraries/removeupgradeefientry.py
@@ -2,7 +2,6 @@ import os
import shutil
from leapp.exceptions import StopActorExecutionError
-from leapp.libraries.common.grub import GRUB2_BIOS_ENTRYPOINT, GRUB2_BIOS_ENV_FILE
from leapp.libraries.stdlib import api, CalledProcessError, run
from leapp.models import ArmWorkaroundEFIBootloaderInfo
@@ -35,9 +34,6 @@ def remove_upgrade_efi_entry():
bootloader_info = get_workaround_efi_info()
- # _copy_grub_files(['grubenv', 'grub.cfg'], ['user.cfg'])
- # _link_grubenv_to_rhel_entry()
-
upgrade_boot_number = bootloader_info.upgrade_entry.boot_number
try:
run([
@@ -65,43 +61,6 @@ def remove_upgrade_efi_entry():
run(['/bin/mount', '-a'])
-def _link_grubenv_to_rhel_entry():
- rhel_env_file = os.path.join(RHEL_EFIDIR_CANONICAL_PATH, 'grubenv')
- rhel_env_file_relpath = os.path.relpath(rhel_env_file, GRUB2_BIOS_ENTRYPOINT)
- run(['ln', '--symbolic', '--force', rhel_env_file_relpath, GRUB2_BIOS_ENV_FILE])
-
-
-def _copy_file(src_path, dst_path):
- if os.path.exists(dst_path):
- api.current_logger().debug("The {} file already exists and its content will be overwritten.".format(dst_path))
-
- api.current_logger().info("Copying {} to {}".format(src_path, dst_path))
- try:
- shutil.copy2(src_path, dst_path)
- except (OSError, IOError) as err:
- raise StopActorExecutionError('I/O error({}): {}'.format(err.errno, err.strerror))
-
-
-def _copy_grub_files(required, optional):
- """
- Copy grub files from /boot/efi/EFI/leapp/ dir to the /boot/efi/EFI/redhat/ dir.
- """
-
- all_files = required + optional
- for filename in all_files:
- src_path = os.path.join(LEAPP_EFIDIR_CANONICAL_PATH, filename)
- dst_path = os.path.join(RHEL_EFIDIR_CANONICAL_PATH, filename)
-
- if not os.path.exists(src_path):
- if filename in required:
- msg = 'Required file {} does not exists. Aborting.'.format(filename)
- raise StopActorExecutionError(msg)
-
- continue
-
- _copy_file(src_path, dst_path)
-
-
def _remove_upgrade_blsdir(bootloader_info):
api.current_logger().debug('Removing upgrade BLS directory: {}'.format(bootloader_info.upgrade_bls_dir))
try:
diff --git a/repos/system_upgrade/el8toel9/actors/removeupgradeefientry/tests/test_removeupgradeefientry.py b/repos/system_upgrade/el8toel9/actors/removeupgradeefientry/tests/test_removeupgradeefientry.py
index 30fde2da..11cd3126 100644
--- a/repos/system_upgrade/el8toel9/actors/removeupgradeefientry/tests/test_removeupgradeefientry.py
+++ b/repos/system_upgrade/el8toel9/actors/removeupgradeefientry/tests/test_removeupgradeefientry.py
@@ -1,4 +1,3 @@
-import os
import shutil
import pytest
@@ -52,52 +51,18 @@ def test_get_workaround_efi_info_no_entry(monkeypatch):
removeupgradeefientry.get_workaround_efi_info()
-def test_copy_grub_files(monkeypatch):
- copy_file_calls = []
-
- def mock_copy_file(src, dst):
- copy_file_calls.append((src, dst))
-
- monkeypatch.setattr(removeupgradeefientry, '_copy_file', mock_copy_file)
- monkeypatch.setattr(os.path, 'exists', lambda path: True)
-
- removeupgradeefientry._copy_grub_files(['required'], ['optional'])
-
- assert (
- os.path.join(removeupgradeefientry.LEAPP_EFIDIR_CANONICAL_PATH, 'required'),
- os.path.join(removeupgradeefientry.RHEL_EFIDIR_CANONICAL_PATH, 'required'),
- ) in copy_file_calls
- assert (
- os.path.join(removeupgradeefientry.LEAPP_EFIDIR_CANONICAL_PATH, 'optional'),
- os.path.join(removeupgradeefientry.RHEL_EFIDIR_CANONICAL_PATH, 'optional'),
- ) in copy_file_calls
-
-
-def test_copy_grub_files_missing_required(monkeypatch):
- monkeypatch.setattr(os.path, 'exists', lambda path: False)
-
- with pytest.raises(StopActorExecutionError, match='Required file required does not exists'):
- removeupgradeefientry._copy_grub_files(['required'], [])
-
-
def test_remove_upgrade_efi_entry(monkeypatch):
run_calls = []
- copy_grub_files_calls = []
monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=[TEST_EFI_INFO]))
def mock_run(command, checked=False):
run_calls.append(command)
return {'exit_code': 0}
- def mock_copy_grub_files(required, optional):
- copy_grub_files_calls.append((required, optional))
-
def rmtree_mocked(tree, *args):
run_calls.append('shutil.rmtree')
assert tree == TEST_EFI_INFO.upgrade_bls_dir
- monkeypatch.setattr(removeupgradeefientry, '_copy_grub_files', mock_copy_grub_files)
- monkeypatch.setattr(removeupgradeefientry, '_link_grubenv_to_rhel_entry', lambda: None)
monkeypatch.setattr(removeupgradeefientry, 'run', mock_run)
monkeypatch.setattr(shutil, 'rmtree', rmtree_mocked)
--
2.48.1

View File

@ -0,0 +1,303 @@
From a6438828415c094c600de80e2e05409a4ccd5822 Mon Sep 17 00:00:00 2001
From: Petr Stodulka <pstodulk@redhat.com>
Date: Wed, 29 Jan 2025 04:43:33 +0100
Subject: [PATCH 61/63] Introduce deprecated IPUPaths msg (temporary solution)
This is hackish precursor to move checking of the specified
target system versions into actors to be able to create report
when unsupported target version is specified.
The problem is that currently there is no information about the
supported upgrade paths in messages. Also, the information about
supported source and target versions are stored in two different
places (in system upgrade common repo):
* shared configs.version library
* files/upgrade_paths.json
As a temporary solution let's introduce IPUPaths message which
will contain filtered data from the json file based on:
* the upgrade flavour (default, saphana)
* and source major version
There is no value to print information to users about different
upgrade paths for other flavours and OS major versions. The model
is marked as deprecated so we can remove it in the next release when
we redesign this solution to unify how actors get this data
(and define them just in one place).
jira: RHEL-51072
---
.../actors/scandefinedipupaths/actor.py | 31 ++++++
.../libraries/scandefinedipupaths.py | 43 ++++++++
.../tests/files/upgrade_paths.json | 15 +++
.../tests/test_scandefinedipupaths.py | 97 +++++++++++++++++++
.../system_upgrade/common/models/ipupaths.py | 43 ++++++++
5 files changed, 229 insertions(+)
create mode 100644 repos/system_upgrade/common/actors/scandefinedipupaths/actor.py
create mode 100644 repos/system_upgrade/common/actors/scandefinedipupaths/libraries/scandefinedipupaths.py
create mode 100644 repos/system_upgrade/common/actors/scandefinedipupaths/tests/files/upgrade_paths.json
create mode 100644 repos/system_upgrade/common/actors/scandefinedipupaths/tests/test_scandefinedipupaths.py
create mode 100644 repos/system_upgrade/common/models/ipupaths.py
diff --git a/repos/system_upgrade/common/actors/scandefinedipupaths/actor.py b/repos/system_upgrade/common/actors/scandefinedipupaths/actor.py
new file mode 100644
index 00000000..a84c85f2
--- /dev/null
+++ b/repos/system_upgrade/common/actors/scandefinedipupaths/actor.py
@@ -0,0 +1,31 @@
+from leapp.actors import Actor
+from leapp.libraries.actor import scandefinedipupaths
+from leapp.models import IPUPaths
+from leapp.tags import FactsPhaseTag, IPUWorkflowTag
+
+
+class ScanDefinedIPUPaths(Actor):
+ """
+ Load defined IPU paths for the current major source system version
+ and defined upgrade flavour.
+
+ The upgrade paths are defined inside `files/upgrade_paths.json`.
+ Based on the defined upgrade flavour (default, saphana, ..) loads particular
+ definitions and filter out all upgrade paths from other system major versions.
+ I.e. for RHEL 8.10 system with the default upgrade flavour, load all upgrade
+ paths from any RHEL 8 system defined under the 'default' flavour.
+
+ The code is mostly taken from the CLI command_utils. The duplicate solution
+ is not so problematic now as it will be unified next time.
+
+ Note the deprecation suppression is expected here as this is considered as
+ temporary solution now.
+ """
+
+ name = 'scan_defined_ipu_paths'
+ consumes = ()
+ produces = (IPUPaths,)
+ tags = (IPUWorkflowTag, FactsPhaseTag)
+
+ def process(self):
+ scandefinedipupaths.process()
diff --git a/repos/system_upgrade/common/actors/scandefinedipupaths/libraries/scandefinedipupaths.py b/repos/system_upgrade/common/actors/scandefinedipupaths/libraries/scandefinedipupaths.py
new file mode 100644
index 00000000..1e39f2c8
--- /dev/null
+++ b/repos/system_upgrade/common/actors/scandefinedipupaths/libraries/scandefinedipupaths.py
@@ -0,0 +1,43 @@
+import json
+
+from leapp.libraries.common.config.version import get_source_major_version
+from leapp.libraries.stdlib import api
+from leapp.models import IPUPath, IPUPaths
+from leapp.utils.deprecation import suppress_deprecation
+
+
+def load_ipu_paths_for_flavour(flavour, _filename='upgrade_paths.json'):
+ """
+ Load defined IPU paths from the upgrade_paths.json file for the specified
+ flavour.
+
+ Note the file is required to be always present, so skipping any test
+ for the missing file. Crash hard and terribly if the file is missing
+ or the content is invalid.
+
+ We expect the flavour to be always good as it is under our control
+ (already sanitized in IPUConfig), but return empty dict and log it if missing.
+ """
+ with open(api.get_common_file_path(_filename)) as fp:
+ data = json.loads(fp.read())
+ if flavour not in data:
+ api.current_logger().warning(
+ 'Cannot discover any upgrade paths for flavour: {}'
+ .format(flavour)
+ )
+ return data.get(flavour, {})
+
+
+def get_filtered_ipu_paths(ipu_paths, src_major_version):
+ result = []
+ for src_version, tgt_versions in ipu_paths.items():
+ if src_version.split('.')[0] == src_major_version:
+ result.append(IPUPath(source_version=src_version, target_versions=tgt_versions))
+ return result
+
+
+@suppress_deprecation(IPUPaths)
+def process():
+ flavour = api.current_actor().configuration.flavour
+ ipu_paths = load_ipu_paths_for_flavour(flavour)
+ api.produce(IPUPaths(data=get_filtered_ipu_paths(ipu_paths, get_source_major_version())))
diff --git a/repos/system_upgrade/common/actors/scandefinedipupaths/tests/files/upgrade_paths.json b/repos/system_upgrade/common/actors/scandefinedipupaths/tests/files/upgrade_paths.json
new file mode 100644
index 00000000..edd32224
--- /dev/null
+++ b/repos/system_upgrade/common/actors/scandefinedipupaths/tests/files/upgrade_paths.json
@@ -0,0 +1,15 @@
+{
+ "default": {
+ "8.10": ["9.4", "9.5", "9.6"],
+ "8.4": ["9.2"],
+ "9.6": ["10.0"],
+ "8": ["9.4", "9.5", "9.6"],
+ "9": ["10.0"]
+ },
+ "saphana": {
+ "8.10": ["9.6", "9.4"],
+ "8": ["9.6", "9.4"],
+ "9.6": ["10.0"],
+ "9": ["10.0"]
+ }
+}
diff --git a/repos/system_upgrade/common/actors/scandefinedipupaths/tests/test_scandefinedipupaths.py b/repos/system_upgrade/common/actors/scandefinedipupaths/tests/test_scandefinedipupaths.py
new file mode 100644
index 00000000..9ffc9829
--- /dev/null
+++ b/repos/system_upgrade/common/actors/scandefinedipupaths/tests/test_scandefinedipupaths.py
@@ -0,0 +1,97 @@
+import json
+import os
+
+import pytest
+
+from leapp.libraries.actor import scandefinedipupaths
+from leapp.libraries.common.testutils import CurrentActorMocked, produce_mocked
+from leapp.models import IPUPath, IPUPaths
+from leapp.utils.deprecation import suppress_deprecation
+
+CUR_DIR = os.path.dirname(os.path.abspath(__file__))
+
+
+class CurrentActorMockedModified(CurrentActorMocked):
+ def get_common_file_path(self, fname):
+ fpath = os.path.join(CUR_DIR, 'files', fname)
+ assert os.path.exists(fpath)
+ if os.path.exists(fpath):
+ return fpath
+ return None
+
+
+@pytest.mark.parametrize(('flavour', 'expected_result'), (
+ ('nonsense', {}),
+ (
+ 'default',
+ {
+ '8.10': ['9.4', '9.5', '9.6'],
+ '8.4': ['9.2'],
+ '9.6': ['10.0'],
+ '8': ['9.4', '9.5', '9.6'],
+ '9': ['10.0']
+ }
+ ),
+ (
+ 'saphana',
+ {
+ '8.10': ['9.6', '9.4'],
+ '8': ['9.6', '9.4'],
+ '9.6': ['10.0'],
+ '9': ['10.0']
+ }
+ ),
+))
+def test_load_ipu_paths_for_flavour(monkeypatch, flavour, expected_result):
+ monkeypatch.setattr(scandefinedipupaths.api, 'current_actor', CurrentActorMockedModified())
+
+ result = scandefinedipupaths.load_ipu_paths_for_flavour(flavour=flavour)
+ assert result == expected_result
+
+
+_DATA_IPU_PATHS = {
+ '8.10': ['9.4', '9.5', '9.6'],
+ '8.4': ['9.2'],
+ '9.6': ['10.0'],
+ '8': ['9.4', '9.5', '9.6'],
+ '80.0': ['81.0']
+}
+
+
+@suppress_deprecation(IPUPaths)
+@pytest.mark.parametrize(('maj_version', 'expected_result'), (
+ ('7', []),
+ (
+ '8',
+ [
+ IPUPath(source_version='8.10', target_versions=['9.4', '9.5', '9.6']),
+ IPUPath(source_version='8.4', target_versions=['9.2']),
+ IPUPath(source_version='8', target_versions=['9.4', '9.5', '9.6']),
+ ]
+ ),
+ (
+ '80',
+ [
+ IPUPath(source_version='80.0', target_versions=['81.0']),
+ ]
+ ),
+
+
+))
+def test_get_filtered_ipu_paths(monkeypatch, maj_version, expected_result):
+ result = scandefinedipupaths.get_filtered_ipu_paths(_DATA_IPU_PATHS, maj_version)
+ result = sorted(result, key=lambda x: x.source_version)
+ assert result == sorted(expected_result, key=lambda x: x.source_version)
+
+
+def test_scan_defined_ipu_paths(monkeypatch):
+ # let's try one 'full' happy run
+ monkeypatch.setattr(scandefinedipupaths.api, 'current_actor', CurrentActorMockedModified(src_ver='9.6'))
+ monkeypatch.setattr(scandefinedipupaths.api, 'produce', produce_mocked())
+ scandefinedipupaths.process()
+
+ assert scandefinedipupaths.api.produce.called == 1
+ msg = scandefinedipupaths.api.produce.model_instances[0]
+ assert isinstance(msg, IPUPaths)
+ assert len(msg.data) == 2
+ assert {i.source_version for i in msg.data} == {'9', '9.6'}
diff --git a/repos/system_upgrade/common/models/ipupaths.py b/repos/system_upgrade/common/models/ipupaths.py
new file mode 100644
index 00000000..5469f25e
--- /dev/null
+++ b/repos/system_upgrade/common/models/ipupaths.py
@@ -0,0 +1,43 @@
+from leapp.models import fields, Model
+from leapp.topics import SystemInfoTopic
+from leapp.utils.deprecation import deprecated
+
+
+class IPUPath(Model):
+ """
+ Represent upgrade paths from a source system version.
+
+ This model is not supposed to be produced nor consumed directly by any actor.
+ See `IPUPaths` instead.
+ """
+ topic = SystemInfoTopic
+
+ source_version = fields.String()
+ """Version of a particular source system."""
+
+ target_versions = fields.List(fields.String())
+ """List of defined target system versions for the `source_version` system."""
+
+
+@deprecated(
+ since="2025-02-01",
+ message="This model is temporary and not assumed to be used in any actors."
+)
+class IPUPaths(Model):
+ """
+ Defined Upgrade paths from the source system major version and used upgrade flavour.
+
+ In example for the RHEL 8.10 system with the 'default' upgrade flavour it will
+ contain information about all defined upgrade paths from any RHEL 8 system
+ for the 'default' flavour (other flavour can be e.g. 'saphana' for systems
+ with SAP HANA installed.
+
+ Note this model is marked as deprecated now as it is considered as a temporary
+ solution. It can be removed in any future release!
+ """
+ topic = SystemInfoTopic
+
+ data = fields.List(fields.Model(IPUPath))
+ """
+ List of defined (filtered) upgrade paths.
+ """
--
2.48.1

View File

@ -0,0 +1,385 @@
From 0a5f66e7d04e41f25a87781cc2e8fb1601cfe70e Mon Sep 17 00:00:00 2001
From: tomasfratrik <tomasfratrik8@gmail.com>
Date: Tue, 14 Jan 2025 14:59:04 +0100
Subject: [PATCH 62/63] Verify supported target OS version in actors
Originally when user specified the target system release using
`--target` CLI option the verification has been performed immediately
as only supported releases have been listed as possible choices for
this option. The benefit of this solution was that users did not have
to wait for all other checks to realize they execute leapp probably
incorrectly. Unfortunately,
* number of users do not understand why only some versions are supported
* users upgrading with via various webUIs presenting only leapp reports
could not see the error message available in terminal
To resolve this problem the checks are moved into actors so in case
of specified unsupported target version the information is present
in generated leapp reports.
Current behaviour is like this:
* in case of invalid input (incorrect format of input data) the hard
error is raised as before immediately. Malformed input data will
not be processed anyhow by any actors
* report error when the specified target major version is not direct
successor of the current system version. I.e. specify 10.0 when
upgrading from RHEL 8 (only RHEL 9 is acceptable).
* this prevents number of cryptic errors as actors are not prepared
for this situation
* report standard inhibitor if the target release is not in the defined
upgrade path, unless LEAPP_UNSUPPORTED=1
* running leapp in unsupported (devel) mode skips the inhibitor and
entire report
Additional changes:
* Update error message when format of target version is incorrect to
clarify the expected version format
jira: RHEL-51072
Co-authored-by: Petr Stodulk <pstodulk@redhat.com>
---
commands/command_utils.py | 13 +--
commands/preupgrade/__init__.py | 3 +-
commands/upgrade/__init__.py | 3 +-
.../common/actors/checktargetversion/actor.py | 22 +++++
.../libraries/checktargetversion.py | 86 ++++++++++++++++++
.../tests/test_checktargetversion.py | 90 +++++++++++++++++++
.../libraries/ipuworkflowconfig.py | 26 +++++-
7 files changed, 229 insertions(+), 14 deletions(-)
create mode 100644 repos/system_upgrade/common/actors/checktargetversion/actor.py
create mode 100644 repos/system_upgrade/common/actors/checktargetversion/libraries/checktargetversion.py
create mode 100644 repos/system_upgrade/common/actors/checktargetversion/tests/test_checktargetversion.py
diff --git a/commands/command_utils.py b/commands/command_utils.py
index 190f5f03..84b9de1b 100644
--- a/commands/command_utils.py
+++ b/commands/command_utils.py
@@ -28,7 +28,10 @@ def check_version(version):
:return: release tuple
"""
if not re.match(VERSION_REGEX, version):
- raise CommandError('Unexpected format of target version: {}'.format(version))
+ raise CommandError(
+ "Unexpected format of target version: {}. "
+ "The required format is 'X.Y' (major and minor version).".format(version)
+ )
return version.split('.')
@@ -126,7 +129,6 @@ def vet_upgrade_path(args):
Make sure the user requested upgrade_path is a supported one.
If LEAPP_DEVEL_TARGET_RELEASE is set then it's value is not vetted against upgrade_paths_map but used as is.
- :raises: `CommandError` if the specified upgrade_path is not supported
:return: `tuple` (target_release, flavor)
"""
flavor = get_upgrade_flavour()
@@ -135,13 +137,6 @@ def vet_upgrade_path(args):
check_version(env_version_override)
return (env_version_override, flavor)
target_release = args.target or get_target_version(flavor)
- supported_target_versions = get_supported_target_versions(flavor)
- if target_release not in supported_target_versions:
- raise CommandError(
- "Upgrade to {to} for {flavor} upgrade path is not supported, possible choices are {choices}".format(
- to=target_release,
- flavor=flavor,
- choices=','.join(supported_target_versions)))
return (target_release, flavor)
diff --git a/commands/preupgrade/__init__.py b/commands/preupgrade/__init__.py
index 631eca6b..c1fabbbd 100644
--- a/commands/preupgrade/__init__.py
+++ b/commands/preupgrade/__init__.py
@@ -28,8 +28,7 @@ from leapp.utils.output import beautify_actor_exception, report_errors, report_i
choices=['ga', 'e4s', 'eus', 'aus'],
value_type=str.lower) # This allows the choices to be case insensitive
@command_opt('iso', help='Use provided target RHEL installation image to perform the in-place upgrade.')
-@command_opt('target', choices=command_utils.get_supported_target_versions(),
- help='Specify RHEL version to upgrade to for {} detected upgrade flavour'.format(
+@command_opt('target', help='Specify RHEL version to upgrade to for {} detected upgrade flavour'.format(
command_utils.get_upgrade_flavour()))
@command_opt('report-schema', help='Specify report schema version for leapp-report.json',
choices=['1.0.0', '1.1.0', '1.2.0'], default=get_config().get('report', 'schema'))
diff --git a/commands/upgrade/__init__.py b/commands/upgrade/__init__.py
index 3dedd438..608099ac 100644
--- a/commands/upgrade/__init__.py
+++ b/commands/upgrade/__init__.py
@@ -34,8 +34,7 @@ from leapp.utils.output import beautify_actor_exception, report_errors, report_i
choices=['ga', 'e4s', 'eus', 'aus'],
value_type=str.lower) # This allows the choices to be case insensitive
@command_opt('iso', help='Use provided target RHEL installation image to perform the in-place upgrade.')
-@command_opt('target', choices=command_utils.get_supported_target_versions(),
- help='Specify RHEL version to upgrade to for {} detected upgrade flavour'.format(
+@command_opt('target', help='Specify RHEL version to upgrade to for {} detected upgrade flavour'.format(
command_utils.get_upgrade_flavour()))
@command_opt('report-schema', help='Specify report schema version for leapp-report.json',
choices=['1.0.0', '1.1.0', '1.2.0'], default=get_config().get('report', 'schema'))
diff --git a/repos/system_upgrade/common/actors/checktargetversion/actor.py b/repos/system_upgrade/common/actors/checktargetversion/actor.py
new file mode 100644
index 00000000..291ce3da
--- /dev/null
+++ b/repos/system_upgrade/common/actors/checktargetversion/actor.py
@@ -0,0 +1,22 @@
+from leapp.actors import Actor
+from leapp.libraries.actor import checktargetversion
+from leapp.models import IPUPaths
+from leapp.reporting import Report
+from leapp.tags import ChecksPhaseTag, IPUWorkflowTag
+
+
+class CheckTargetVersion(Actor):
+ """
+ Check that the target system version is supported by the upgrade process.
+
+ Invoke inhibitor if the target system is not supported.
+ Allow unsupported target if `LEAPP_UNSUPPORTED=1` is set.
+ """
+
+ name = 'check_target_version'
+ consumes = (IPUPaths,)
+ produces = (Report,)
+ tags = (ChecksPhaseTag, IPUWorkflowTag)
+
+ def process(self):
+ checktargetversion.process()
diff --git a/repos/system_upgrade/common/actors/checktargetversion/libraries/checktargetversion.py b/repos/system_upgrade/common/actors/checktargetversion/libraries/checktargetversion.py
new file mode 100644
index 00000000..0df1ece2
--- /dev/null
+++ b/repos/system_upgrade/common/actors/checktargetversion/libraries/checktargetversion.py
@@ -0,0 +1,86 @@
+from leapp import reporting
+from leapp.exceptions import StopActorExecutionError
+from leapp.libraries.common.config import get_env, version
+from leapp.libraries.stdlib import api
+from leapp.models import IPUPaths
+from leapp.utils.deprecation import suppress_deprecation
+
+FMT_LIST_SEPARATOR = '\n - '
+
+
+@suppress_deprecation(IPUPaths)
+def get_supported_target_versions():
+ ipu_paths = next(api.consume(IPUPaths), None)
+ src_version = version.get_source_version()
+ if not ipu_paths:
+ # NOTE: missing unit-tests. Unexpected situation and the solution
+ # is possibly temporary
+ raise StopActorExecutionError('Missing the IPUPaths message. Cannot determine defined upgrade paths.')
+ for ipu_path in ipu_paths.data:
+ if ipu_path.source_version == src_version:
+ return ipu_path.target_versions
+
+ # Nothing discovered. Current src_version is not already supported or not yet.
+ # Problem of supported source versions is handled now separately in other
+ # actors. Fallbak from X.Y versioning to major version only.
+ api.current_logger().warning(
+ 'Cannot discover support upgrade path for this system release: {}'
+ .format(src_version)
+ )
+ maj_version = version.get_source_major_version()
+ for ipu_path in ipu_paths.data:
+ if ipu_path.source_version == maj_version:
+ return ipu_path.target_versions
+
+ # Completely unknown
+ api.current_logger().warning(
+ 'Cannot discover supported upgrade path for this system major version: {}'
+ .format(maj_version)
+ )
+ return []
+
+
+def process():
+ target_version = version.get_target_version()
+ supported_target_versions = get_supported_target_versions()
+
+ if target_version in supported_target_versions:
+ api.current_logger().info('Target version is supported. Continue.')
+ return
+
+ if get_env('LEAPP_UNSUPPORTED', '0') == '1':
+ api.current_logger().warning(
+ 'Upgrading to an unsupported version of the target system but LEAPP_UNSUPPORTED=1. Continue.'
+ )
+ return
+
+ # inhibit the upgrade - unsupported target and leapp running in production mode
+ hint = (
+ 'Choose a supported version of the target OS for the upgrade.'
+ ' Alternatively, if you require to upgrade using an unsupported upgrade path,'
+ ' set the `LEAPP_UNSUPPORTED=1` environment variable to confirm you'
+ ' want to upgrade on your own risk.'
+ )
+
+ reporting.create_report([
+ reporting.Title('Specified version of the target system is not supported'),
+ reporting.Summary(
+ 'The in-place upgrade to the specified version ({tgt_ver}) of the target system'
+ ' is not supported from the current system version. Follow the official'
+ ' documentation for up to date information about supported upgrade'
+ ' paths and future plans (see the attached link).'
+ ' The in-place upgrade is enabled to the following versions of the target system:{sep}{ver_list}'
+ .format(
+ sep=FMT_LIST_SEPARATOR,
+ ver_list=FMT_LIST_SEPARATOR.join(supported_target_versions),
+ tgt_ver=target_version
+ )
+ ),
+ reporting.Groups([reporting.Groups.INHIBITOR]),
+ reporting.Severity(reporting.Severity.HIGH),
+ reporting.Remediation(hint=hint),
+ reporting.ExternalLink(
+ url='https://access.redhat.com/articles/4263361',
+ title='Supported in-place upgrade paths for Red Hat Enterprise Linux'
+ )
+ ])
diff --git a/repos/system_upgrade/common/actors/checktargetversion/tests/test_checktargetversion.py b/repos/system_upgrade/common/actors/checktargetversion/tests/test_checktargetversion.py
new file mode 100644
index 00000000..07391e7a
--- /dev/null
+++ b/repos/system_upgrade/common/actors/checktargetversion/tests/test_checktargetversion.py
@@ -0,0 +1,90 @@
+import os
+
+import pytest
+
+from leapp import reporting
+from leapp.libraries.actor import checktargetversion
+from leapp.libraries.common.testutils import create_report_mocked, CurrentActorMocked, logger_mocked
+from leapp.libraries.stdlib import api
+from leapp.models import IPUPath, IPUPaths
+from leapp.utils.deprecation import suppress_deprecation
+from leapp.utils.report import is_inhibitor
+
+
+# It must be in a function so we can suppress the deprecation warning in tests.
+@suppress_deprecation(IPUPaths)
+def _get_upgrade_paths_data():
+ return IPUPaths(data=[
+ IPUPath(source_version='7.9', target_versions=['8.10']),
+ IPUPath(source_version='8.10', target_versions=['9.4', '9.5', '9.6']),
+ IPUPath(source_version='9.6', target_versions=['10.0']),
+ IPUPath(source_version='7', target_versions=['8.10']),
+ IPUPath(source_version='8', target_versions=['9.4', '9.5', '9.6']),
+ IPUPath(source_version='9', target_versions=['10.0'])
+ ])
+
+
+@pytest.fixture
+def setup_monkeypatch(monkeypatch):
+ """Fixture to set up common monkeypatches."""
+
+ def _setup(source_version, target_version, leapp_unsupported='0'):
+ curr_actor_mocked = CurrentActorMocked(
+ src_ver=source_version,
+ dst_ver=target_version,
+ envars={'LEAPP_UNSUPPORTED': leapp_unsupported},
+ msgs=[_get_upgrade_paths_data()]
+ )
+ monkeypatch.setattr(api, 'current_actor', curr_actor_mocked)
+ monkeypatch.setattr(api, 'current_logger', logger_mocked())
+ monkeypatch.setattr(reporting, 'create_report', create_report_mocked())
+ return _setup
+
+
+@pytest.mark.parametrize(('source_version', 'target_version', 'leapp_unsupported'), [
+ # LEAPP_UNSUPPORTED=0
+ ('7.9', '9.0', '0'),
+ ('8.10', '9.0', '0'),
+ ('9.6', '10.1', '0'),
+ ('7', '9.0', '0'),
+ ('8', '9.0', '0'),
+ ('9', '10.1', '0'),
+ # LEAPP_UNSUPPORTED=1
+ ('7.9', '9.0', '1'),
+ ('8.10', '9.0', '1'),
+ ('9.6', '10.1', '1'),
+ ('7', '9.0', '1'),
+ ('8', '9.0', '1'),
+ ('9', '10.1', '1'),
+])
+def test_unsuppoted_paths(setup_monkeypatch, source_version, target_version, leapp_unsupported):
+ setup_monkeypatch(source_version, target_version, leapp_unsupported)
+
+ if leapp_unsupported == '1':
+ checktargetversion.process()
+ assert reporting.create_report.called == 0
+ assert api.current_logger.warnmsg
+ else:
+ checktargetversion.process()
+ assert reporting.create_report.called == 1
+ assert is_inhibitor(reporting.create_report.report_fields)
+
+
+@pytest.mark.parametrize(('source_version', 'target_version'), [
+ ('7.9', '8.10'),
+ ('8.10', '9.4'),
+ ('8.10', '9.5'),
+ ('8.10', '9.6'),
+ ('9.6', '10.0'),
+ ('7', '8.10'),
+ ('8', '9.4'),
+ ('8', '9.5'),
+ ('8', '9.6'),
+ ('9', '10.0'),
+])
+def test_supported_paths(setup_monkeypatch, source_version, target_version):
+ setup_monkeypatch(source_version, target_version, leapp_unsupported='0')
+
+ checktargetversion.process()
+ assert reporting.create_report.called == 0
+ assert api.current_logger.infomsg
diff --git a/repos/system_upgrade/common/actors/ipuworkflowconfig/libraries/ipuworkflowconfig.py b/repos/system_upgrade/common/actors/ipuworkflowconfig/libraries/ipuworkflowconfig.py
index 9e213f64..749b3347 100644
--- a/repos/system_upgrade/common/actors/ipuworkflowconfig/libraries/ipuworkflowconfig.py
+++ b/repos/system_upgrade/common/actors/ipuworkflowconfig/libraries/ipuworkflowconfig.py
@@ -64,17 +64,41 @@ def get_os_release(path):
details={'details': str(e)})
+def check_target_major_version(curr_version, target_version):
+ required_major_version = int(curr_version.split('.')[0]) + 1
+ specified_major_version = int(target_version.split('.')[0])
+ if specified_major_version != required_major_version:
+ raise StopActorExecutionError(
+ message='Specified invalid major version of the target system',
+ details={
+ 'Specified target major version': str(specified_major_version),
+ 'Required target major version': str(required_major_version),
+ 'hint': (
+ 'The in-place upgrade is possible only to the next system'
+ ' major version: {ver}. Specify a valid version of the'
+ ' target system when running leapp.'
+ ' For more information about supported in-place upgrade paths'
+ ' follow: https://access.redhat.com/articles/4263361'
+ .format(ver=required_major_version)
+ )
+ }
+ )
+
+
def produce_ipu_config(actor):
flavour = os.environ.get('LEAPP_UPGRADE_PATH_FLAVOUR')
target_version = os.environ.get('LEAPP_UPGRADE_PATH_TARGET_RELEASE')
os_release = get_os_release('/etc/os-release')
+ source_version = os_release.version_id
+
+ check_target_major_version(source_version, target_version)
actor.produce(IPUConfig(
leapp_env_vars=get_env_vars(),
os_release=os_release,
architecture=platform.machine(),
version=Version(
- source=os_release.version_id,
+ source=source_version,
target=target_version
),
kernel=get_booted_kernel(),
--
2.48.1

File diff suppressed because it is too large Load Diff

View File

@ -52,7 +52,7 @@ py2_byte_compile "%1" "%2"}
Name: leapp-repository
Version: 0.21.0
Release: 5%{?dist}
Release: 6%{?dist}
Summary: Repositories for leapp
License: ASL 2.0
@ -118,6 +118,16 @@ Patch0050: 0050-redhatsignedrpmcheck-Add-remediation-hint-and-URL.patch
Patch0051: 0051-Update-postgresqlcheck.py.patch
Patch0052: 0052-Update-repos-system_upgrade-el8toel9-actors-postgres.patch
Patch0053: 0053-Fix-remediation-message-in-the-networkdeprecations-a.patch
Patch0054: 0054-linter-Fix-line-too-long-in-postgresqlcheck.patch
Patch0055: 0055-Fix-typos-in-comments-to-make-spellchecker-happy.patch
Patch0056: 0056-fix-add_upgrade_boot_entry-convert-arg-list-into-a-t.patch
Patch0057: 0057-fix-arm-bootloader-efi-patch-grub.cfg-used-for-upgra.patch
Patch0058: 0058-feat-arm-bootloader-efi-use-separate-BLS-directory-f.patch
Patch0059: 0059-fix-models-move-arm-bootloader-workaround-model-into.patch
Patch0060: 0060-cleanup-8to9-efi-do-not-use-symlinks-or-copy-grub-fi.patch
Patch0061: 0061-Introduce-deprecated-IPUPaths-msg-temporary-solution.patch
Patch0062: 0062-Verify-supported-target-OS-version-in-actors.patch
Patch0063: 0063-Add-inhibitor-for-unsupported-XFS.patch
%description
@ -339,6 +349,16 @@ Requires: libdb-utils
%patch -P 0051 -p1
%patch -P 0052 -p1
%patch -P 0053 -p1
%patch -P 0054 -p1
%patch -P 0055 -p1
%patch -P 0056 -p1
%patch -P 0057 -p1
%patch -P 0058 -p1
%patch -P 0059 -p1
%patch -P 0060 -p1
%patch -P 0061 -p1
%patch -P 0062 -p1
%patch -P 0063 -p1
%build
@ -420,6 +440,12 @@ done;
# no files here
%changelog
* Wed Jan 29 2025 Petr Stodulka <pstodulk@redhat.com> - 0.21.0-6
- Detect XFS file systems with problematic parameters
- Raise an inhibitor if unsupported target version supplied instead of error
- Prevent a possible crash with LiveMode when adding the upgrade boot entry on systems with LVM
- Resolves: RHEL-57043, RHEL-52309, RHEL-60034
* Fri Jan 17 2025 Petr Stodulka <pstodulk@redhat.com> - 0.21.0-5
- Obsolete RHEL9 GPG key signed with SHA1
- Activate LVM VGs with `--sysinit` option to correct the use in the upgrade initramfs