Skip to content

Commit 1fb81f2

Browse files
authored
Merge pull request #61 from prilr/CLOS-2842-elevate-actor-add_upgrade_boot_entry-fai
CLOS-2842: Cover centos/cloudlinux EFI paths in hybrid BIOS+UEFI handling
2 parents f446a57 + 74d65be commit 1fb81f2

3 files changed

Lines changed: 101 additions & 17 deletions

File tree

repos/system_upgrade/common/actors/addupgradebootentry/actor.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
import os
2-
31
from leapp.actors import Actor
42
from leapp.exceptions import StopActorExecutionError
5-
from leapp.libraries.actor.addupgradebootentry import add_boot_entry, fix_grub_config_error
3+
from leapp.libraries.actor.addupgradebootentry import (
4+
add_boot_entry,
5+
fix_grub_config_error,
6+
get_hybrid_bios_efi_configs,
7+
)
68
from leapp.models import BootContent, FirmwareFacts, GrubConfigError, TargetKernelCmdlineArgTasks, TransactionDryRun
79
from leapp.tags import InterimPreparationPhaseTag, IPUWorkflowTag
810

@@ -24,16 +26,11 @@ def process(self):
2426
if grub_config_error.error_detected:
2527
fix_grub_config_error('/etc/default/grub', grub_config_error.error_type)
2628

27-
configs = None
2829
ff = next(self.consume(FirmwareFacts), None)
2930
if not ff:
3031
raise StopActorExecutionError(
3132
'Could not identify system firmware',
3233
details={'details': 'Actor did not receive FirmwareFacts message.'}
3334
)
3435

35-
# related to issue with hybrid BIOS and UEFI images
36-
# https://bugzilla.redhat.com/show_bug.cgi?id=1667028
37-
if ff.firmware == 'bios' and os.path.ismount('/boot/efi') and os.path.isfile('/boot/efi/EFI/redhat/grub.cfg'):
38-
configs = ['/boot/grub2/grub.cfg', '/boot/efi/EFI/redhat/grub.cfg']
39-
add_boot_entry(configs)
36+
add_boot_entry(get_hybrid_bios_efi_configs(ff.firmware))

repos/system_upgrade/common/actors/addupgradebootentry/libraries/addupgradebootentry.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,44 @@
77
from leapp.models import BootContent, KernelCmdlineArg, TargetKernelCmdlineArgTasks
88

99

10+
def get_hybrid_bios_efi_configs(firmware):
11+
"""
12+
On a BIOS-firmware system with /boot/efi mounted, the EFI directory may also hold
13+
a grub.cfg that GRUB consults at boot. grubby --copy-default --make-default only
14+
updates /boot/grub2/grub.cfg by default; if the EFI-side grub.cfg is not also
15+
updated, the new entry may not become the actual boot default.
16+
17+
The OS-name subdirectory under /boot/efi/EFI/ varies by distribution. Check the
18+
known options and return the configs list for add_boot_entry, or None if no
19+
EFI-side grub.cfg is present.
20+
21+
Related: https://bugzilla.redhat.com/show_bug.cgi?id=1667028
22+
"""
23+
if firmware != 'bios' or not os.path.ismount('/boot/efi'):
24+
return None
25+
for efi_subdir in ('redhat', 'centos', 'cloudlinux', 'almalinux'):
26+
efi_cfg = '/boot/efi/EFI/{0}/grub.cfg'.format(efi_subdir)
27+
if os.path.isfile(efi_cfg):
28+
return ['/boot/grub2/grub.cfg', efi_cfg]
29+
return None
30+
31+
1032
def add_boot_entry(configs=None):
1133
debug = 'debug' if os.getenv('LEAPP_DEBUG', '0') == '1' else ''
1234
enable_network = os.getenv('LEAPP_DEVEL_INITRAM_NETWORK') in ('network-manager', 'scripts')
13-
ip_arg = ' ip=dhcp rd.neednet=1' if enable_network else ''
35+
ip_args = 'ip=dhcp rd.neednet=1' if enable_network else ''
1436
kernel_dst_path, initram_dst_path = get_boot_file_paths()
1537
_remove_old_upgrade_boot_entry(kernel_dst_path, configs=configs)
1638
try:
39+
args_parts = [x for x in [debug, ip_args, 'enforcing=0 rd.plymouth=0 plymouth.enable=0'] if x]
1740
cmd = [
1841
'/usr/sbin/grubby',
1942
'--add-kernel', '{0}'.format(kernel_dst_path),
2043
'--initrd', '{0}'.format(initram_dst_path),
2144
'--title', 'ELevate-Upgrade-Initramfs',
2245
'--copy-default',
2346
'--make-default',
24-
'--args', '{DEBUG}{NET} enforcing=0 rd.plymouth=0 plymouth.enable=0'.format(DEBUG=debug, NET=ip_arg)
47+
'--args', ' '.join(args_parts)
2548
]
2649
if configs:
2750
for config in configs:

repos/system_upgrade/common/actors/addupgradebootentry/tests/unit_test_addupgradebootentry.py

Lines changed: 70 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def __call__(self, filename, content):
3838
'--remove-kernel', '/abc'
3939
]
4040

41-
run_args_add = [
41+
run_args_add_debug = [
4242
'/usr/sbin/grubby',
4343
'--add-kernel', '/abc',
4444
'--initrd', '/def',
@@ -49,16 +49,27 @@ def __call__(self, filename, content):
4949
'debug enforcing=0 rd.plymouth=0 plymouth.enable=0'
5050
]
5151

52+
run_args_add_no_debug = [
53+
'/usr/sbin/grubby',
54+
'--add-kernel', '/abc',
55+
'--initrd', '/def',
56+
'--title', 'ELevate-Upgrade-Initramfs',
57+
'--copy-default',
58+
'--make-default',
59+
'--args',
60+
'enforcing=0 rd.plymouth=0 plymouth.enable=0'
61+
]
62+
5263
run_args_zipl = ['/usr/sbin/zipl']
5364

5465

5566
@pytest.mark.parametrize('run_args, arch', [
5667
# non s390x
57-
(RunArgs(run_args_remove, run_args_add, None, 2), ARCH_X86_64),
68+
(RunArgs(run_args_remove, run_args_add_debug, None, 2), ARCH_X86_64),
5869
# s390x
59-
(RunArgs(run_args_remove, run_args_add, run_args_zipl, 3), ARCH_S390X),
70+
(RunArgs(run_args_remove, run_args_add_debug, run_args_zipl, 3), ARCH_S390X),
6071
# config file specified
61-
(RunArgs(run_args_remove, run_args_add, None, 2), ARCH_X86_64),
72+
(RunArgs(run_args_remove, run_args_add_debug, None, 2), ARCH_X86_64),
6273
])
6374
def test_add_boot_entry(monkeypatch, run_args, arch):
6475
def get_boot_file_paths_mocked():
@@ -84,6 +95,27 @@ def get_boot_file_paths_mocked():
8495
assert addupgradebootentry.run.args[2] == run_args.args_zipl
8596

8697

98+
def test_add_boot_entry_no_debug(monkeypatch):
99+
"""--args value must not have a leading space when LEAPP_DEBUG is unset (the common path)."""
100+
def get_boot_file_paths_mocked():
101+
return '/abc', '/def'
102+
103+
monkeypatch.setattr(addupgradebootentry, 'get_boot_file_paths', get_boot_file_paths_mocked)
104+
monkeypatch.setattr(api, 'produce', produce_mocked())
105+
monkeypatch.setenv('LEAPP_DEBUG', '0')
106+
monkeypatch.setattr(addupgradebootentry, 'run', run_mocked())
107+
monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(ARCH_X86_64))
108+
109+
addupgradebootentry.add_boot_entry()
110+
111+
assert len(addupgradebootentry.run.args) == 2
112+
assert addupgradebootentry.run.args[0] == run_args_remove
113+
assert addupgradebootentry.run.args[1] == run_args_add_no_debug
114+
# Confirm the --args value has no leading space.
115+
grubby_args_value = addupgradebootentry.run.args[1][-1]
116+
assert not grubby_args_value.startswith(' ')
117+
118+
87119
@pytest.mark.parametrize('is_leapp_invoked_with_debug', [True, False])
88120
def test_debug_kernelopt_removal_task_production(monkeypatch, is_leapp_invoked_with_debug):
89121
def get_boot_file_paths_mocked():
@@ -124,8 +156,8 @@ def get_boot_file_paths_mocked():
124156
assert len(addupgradebootentry.run.args) == 4
125157
assert addupgradebootentry.run.args[0] == run_args_remove + ['-c', CONFIGS[0]]
126158
assert addupgradebootentry.run.args[1] == run_args_remove + ['-c', CONFIGS[1]]
127-
assert addupgradebootentry.run.args[2] == run_args_add + ['-c', CONFIGS[0]]
128-
assert addupgradebootentry.run.args[3] == run_args_add + ['-c', CONFIGS[1]]
159+
assert addupgradebootentry.run.args[2] == run_args_add_debug + ['-c', CONFIGS[0]]
160+
assert addupgradebootentry.run.args[3] == run_args_add_debug + ['-c', CONFIGS[1]]
129161
assert api.produce.model_instances == [
130162
TargetKernelCmdlineArgTasks(to_remove=[KernelCmdlineArg(key='debug')]),
131163
TargetKernelCmdlineArgTasks(to_remove=[KernelCmdlineArg(key='enforcing', value='0')])
@@ -153,6 +185,38 @@ def consume_no_message_mocked(*models):
153185
addupgradebootentry.get_boot_file_paths()
154186

155187

188+
@pytest.mark.parametrize(
189+
'firmware, mount, existing_efi_cfgs, expected',
190+
[
191+
# EFI firmware: hybrid path does not apply, even if /boot/efi is mounted
192+
('efi', True, ['/boot/efi/EFI/redhat/grub.cfg'], None),
193+
# BIOS firmware but /boot/efi not mounted
194+
('bios', False, ['/boot/efi/EFI/centos/grub.cfg'], None),
195+
# BIOS + /boot/efi mounted, redhat path (RHEL-derived systems)
196+
('bios', True, ['/boot/efi/EFI/redhat/grub.cfg'],
197+
['/boot/grub2/grub.cfg', '/boot/efi/EFI/redhat/grub.cfg']),
198+
# BIOS + /boot/efi mounted, centos path (CL7 hybrid setups - was missed before)
199+
('bios', True, ['/boot/efi/EFI/centos/grub.cfg'],
200+
['/boot/grub2/grub.cfg', '/boot/efi/EFI/centos/grub.cfg']),
201+
# BIOS + /boot/efi mounted, cloudlinux path (CL8+ hybrid setups - was missed before)
202+
('bios', True, ['/boot/efi/EFI/cloudlinux/grub.cfg'],
203+
['/boot/grub2/grub.cfg', '/boot/efi/EFI/cloudlinux/grub.cfg']),
204+
# BIOS + /boot/efi mounted, almalinux path
205+
('bios', True, ['/boot/efi/EFI/almalinux/grub.cfg'],
206+
['/boot/grub2/grub.cfg', '/boot/efi/EFI/almalinux/grub.cfg']),
207+
# BIOS + /boot/efi mounted, no known grub.cfg present
208+
('bios', True, [], None),
209+
# BIOS + /boot/efi mounted, multiple paths present - redhat wins (checked first)
210+
('bios', True, ['/boot/efi/EFI/redhat/grub.cfg', '/boot/efi/EFI/centos/grub.cfg'],
211+
['/boot/grub2/grub.cfg', '/boot/efi/EFI/redhat/grub.cfg']),
212+
]
213+
)
214+
def test_get_hybrid_bios_efi_configs(monkeypatch, firmware, mount, existing_efi_cfgs, expected):
215+
monkeypatch.setattr(os.path, 'ismount', lambda p: p == '/boot/efi' and mount)
216+
monkeypatch.setattr(os.path, 'isfile', lambda p: p in existing_efi_cfgs)
217+
assert addupgradebootentry.get_hybrid_bios_efi_configs(firmware) == expected
218+
219+
156220
@pytest.mark.skip("Broken test")
157221
@pytest.mark.parametrize(
158222
('error_type', 'test_file_name'),

0 commit comments

Comments
 (0)