From 557621ee667835e58778be1292cb75319c556857 Mon Sep 17 00:00:00 2001 From: Toshio Kuratomi Date: Tue, 30 Apr 2024 01:55:32 -0700 Subject: [PATCH] Refactor and fix to include root in kernelargs. --- .../actors/kernelcmdlineconfig/actor.py | 26 +--- .../libraries/kernelcmdlineconfig.py | 144 +++++++++++++++--- .../tests/test_kernelcmdlineconfig.py | 15 +- 3 files changed, 133 insertions(+), 52 deletions(-) diff --git a/repos/system_upgrade/common/actors/kernelcmdlineconfig/actor.py b/repos/system_upgrade/common/actors/kernelcmdlineconfig/actor.py index 754c036730..b44fd835eb 100644 --- a/repos/system_upgrade/common/actors/kernelcmdlineconfig/actor.py +++ b/repos/system_upgrade/common/actors/kernelcmdlineconfig/actor.py @@ -1,10 +1,8 @@ import os -from leapp import reporting from leapp.actors import Actor from leapp.exceptions import StopActorExecutionError from leapp.libraries.actor import kernelcmdlineconfig -from leapp.libraries.stdlib import api from leapp.models import FirmwareFacts, InstalledTargetKernelInfo, KernelCmdlineArg, TargetKernelCmdlineArgTasks from leapp.tags import FinalizationPhaseTag, IPUWorkflowTag @@ -32,26 +30,4 @@ def process(self): if ff.firmware == 'bios' and os.path.ismount('/boot/efi'): configs = ['/boot/grub2/grub.cfg', '/boot/efi/EFI/redhat/grub.cfg'] - try: - kernelcmdlineconfig.modify_kernel_args_in_boot_cfg(configs) - except kernelcmdlineconfig.ReadOfKernelArgsError as e: - api.current_logger().error(str(e)) - reporting.create_report([ - reporting.Title('Could not retrieve kernel command line arguments: {}'.format(e)), - reporting.Summary( - 'Unable to retrieve the existing kernel command line arguments in order' - ' to set the default value for future installed kernels. After the' - ' system has been rebooted into the new version of RHEL, you should' - ' check what kernel command line options are present in /proc/cmdline' - ' and copy them into /etc/kernel/cmdline before installing any new kernels.' - ), - reporting.Severity(reporting.Severity.MEDIUM), - reporting.Groups([ - reporting.Groups.BOOT, - reporting.Groups.KERNEL, - reporting.Groups.POST, - ]), - reporting.RelatedResource('file', '/etc/kernel/cmdline'), - reporting.RelatedResource('file', '/proc/cmdline'), - ]) - return + kernelcmdlineconfig.entrypoint(configs) diff --git a/repos/system_upgrade/common/actors/kernelcmdlineconfig/libraries/kernelcmdlineconfig.py b/repos/system_upgrade/common/actors/kernelcmdlineconfig/libraries/kernelcmdlineconfig.py index 482d37a129..e526476d0d 100644 --- a/repos/system_upgrade/common/actors/kernelcmdlineconfig/libraries/kernelcmdlineconfig.py +++ b/repos/system_upgrade/common/actors/kernelcmdlineconfig/libraries/kernelcmdlineconfig.py @@ -1,5 +1,6 @@ import re +from leapp import reporting from leapp.exceptions import StopActorExecutionError from leapp.libraries import stdlib from leapp.libraries.common.config import architecture, version @@ -15,6 +16,13 @@ class ReadOfKernelArgsError(Exception): """ +def use_cmdline_file(): + if (architecture.matches_architecture(architecture.ARCH_S390X) or + version.matches_target_version('>= 9.0')): + return True + return False + + def run_grubby_cmd(cmd): try: stdlib.run(cmd) @@ -25,6 +33,9 @@ def run_grubby_cmd(cmd): stdlib.run(['/usr/sbin/zipl']) except (OSError, stdlib.CalledProcessError) as e: + # In most cases we don't raise StopActorExecutionError in post-upgrade + # actors. + # raise StopActorExecutionError( "Failed to append extra arguments to kernel command line.", details={"details": str(e)}) @@ -42,8 +53,7 @@ def format_kernelarg_msgs_for_grubby_cmd(kernelarg_msgs): def set_default_kernel_args(kernel_args): - if (architecture.matches_architecture(architecture.ARCH_S390X) or - version.matches_target_version(">= 9.0")): + if use_cmdline_file(): # Put kernel_args into /etc/kernel/cmdline with open(KERNEL_CMDLINE_FILE, 'w') as f: f.write(kernel_args) @@ -52,25 +62,26 @@ def set_default_kernel_args(kernel_args): stdlib.run(['grub2-editenv', '-', 'set', 'kernelopts={}'.format(kernel_args)]) -def modify_kernel_args_in_boot_cfg(configs_to_modify_explicitly=None): - kernel_info = next(api.consume(InstalledTargetKernelInfo), None) - if not kernel_info: - return - - # Collect desired kernelopt modifications - +def retrieve_arguments_to_modify(): + """ + Retrieve the arguments other actors would like to add or remove from the kernel cmdline. + """ kernelargs_msgs_to_add = list(api.consume(KernelCmdlineArg)) kernelargs_msgs_to_remove = [] + for target_kernel_arg_task in api.consume(TargetKernelCmdlineArgTasks): kernelargs_msgs_to_add.extend(target_kernel_arg_task.to_add) kernelargs_msgs_to_remove.extend(target_kernel_arg_task.to_remove) - if not kernelargs_msgs_to_add and not kernelargs_msgs_to_remove: - return # There is no work to do + return kernelargs_msgs_to_add, kernelargs_msgs_to_remove - # Modify the kernel cmdline for the default kernel - grubby_modify_kernelargs_cmd = ['grubby', '--update-kernel={0}'.format(kernel_info.kernel_img_path)] +def modify_args_for_default_kernel(kernel_info, + kernelargs_msgs_to_add, + kernelargs_msgs_to_remove, + configs_to_modify_explicitly=None): + grubby_modify_kernelargs_cmd = ['grubby', + '--update-kernel={0}'.format(kernel_info.kernel_img_path)] if kernelargs_msgs_to_add: grubby_modify_kernelargs_cmd += [ @@ -89,22 +100,115 @@ def modify_kernel_args_in_boot_cfg(configs_to_modify_explicitly=None): else: run_grubby_cmd(grubby_modify_kernelargs_cmd) - # Copy the args for the default kernel to be for all kernels. +def _extract_grubby_value(record): + data = record.split('=', 1)[1] + matches = re.match(r'^([\'"]?)(.*)\1$', data) + return matches.group(2) + + +def retrieve_args_for_default_kernel(kernel_info): + # Copy the args for the default kernel to all kernels. kernel_args = None + kernel_root = None cmd = ['grubby', '--info', kernel_info.kernel_img_path] output = stdlib.run(cmd, split=False) for record in output['stdout'].splitlines(): # This could be done with one regex but it's cleaner to parse it as # structured data. if record.startswith('args='): - data = record.split("=", 1)[1] - matches = re.match(r'^([\'"]?)(.*)\1$', data) - kernel_args = matches.group(2) - break - else: + temp_kernel_args = _extract_grubby_value(record) + + if kernel_args: + api.current_logger().warning('Grubby output is malformed:' + ' `args=` is listed more than once.') + if kernel_args != temp_kernel_args: + raise ReadOfKernelArgsError('Grubby listed `args=` multiple' + ' times with different values.') + kernel_args = _extract_grubby_value(record) + elif record.startswith('root='): + api.current_logger().warning('Grubby output is malformed:' + ' `root=` is listed more than once.') + if kernel_root: + raise ReadOfKernelArgsError('Grubby listed `root=` multiple' + ' times with different values') + kernel_root = _extract_grubby_value(record) + + if not kernel_args or not kernel_root: raise ReadOfKernelArgsError( - "Failed to retrieve kernel command line to save for future installed kernels." + 'Failed to retrieve kernel command line to save for future installed' + ' kernels: root={}, args={}'.format(kernel_root, kernel_args) ) + return kernel_root, kernel_args + + +def modify_kernel_args_in_boot_cfg(configs_to_modify_explicitly=None): + kernel_info = next(api.consume(InstalledTargetKernelInfo), None) + if not kernel_info: + return + + # Collect desired kernelopt modifications + kernelargs_msgs_to_add, kernelargs_msgs_to_remove = retrieve_arguments_to_modify() + if not kernelargs_msgs_to_add and not kernelargs_msgs_to_remove: + # Nothing to do + return + + # Modify the kernel cmdline for the default kernel + modify_args_for_default_kernel(kernel_info, + kernelargs_msgs_to_add, + kernelargs_msgs_to_remove, + configs_to_modify_explicitly) + + # Copy kernel params from the default kernel to all the kernels + kernel_args, kernel_root = retrieve_args_for_default_kernel(kernel_info) + 'root={} {}'.format(kernel_root, kernel_args) set_default_kernel_args(kernel_args) + + +def entrypoint(configs): + try: + modify_kernel_args_in_boot_cfg(configs) + except ReadOfKernelArgsError as e: + api.current_logger().error(str(e)) + + if use_cmdline_file(): + report_hint = reporting.Hints( + 'After the system has been rebooted into the new version of RHEL, you' + ' should take the kernel cmdline arguments from /proc/cmdline (Everything' + ' except the BOOT_IMAGE entry and initrd entries) and copy them into' + ' /etc/kernel/cmdline before installing any new kernels.' + ) + else: + report_hint = reporting.Hints( + 'After the system has been rebooted into the new version of RHEL, you' + ' should take the kernel cmdline arguments from /proc/cmdline (Everything' + ' except the BOOT_IMAGE entry and initrd entries) and then use the' + ' grub2-editenv command to make them the default kernel args. For example,' + ' if /proc/cmdline contains:\n\n' + ' BOOT_IMAGE=(hd0,msdos1)/vmlinuz-4.18.0-425.3.1.el8.x86_64' + ' root=/dev/mapper/rhel_ibm--root ro console=tty0' + ' console=ttyS0,115200 rd_NO_PLYMOUTH\n\n' + ' then run the following grub2-editenv command:\n\n' + ' # grub2-editenv - set "kernelopts=root=/dev/mapper/rhel_ibm--root' + ' ro console=tty0 console=ttyS0,115200 rd_NO_PLYMOUTH"' + ) + + reporting.create_report([ + reporting.Title('Could not set the kernel arguments for future kernels'), + reporting.Summary( + 'During the upgrade we needed to modify the kernel command line arguments.' + ' We were able to change the arguments for the default kernel but we were' + ' not able to set the arguments as the default for kernels installed in' + ' the future.' + ), + report_hint, + reporting.Severity(reporting.Severity.HIGH), + reporting.Groups([ + reporting.Groups.BOOT, + reporting.Groups.KERNEL, + reporting.Groups.POST, + ]), + reporting.RelatedResource('file', '/etc/kernel/cmdline'), + reporting.RelatedResource('file', '/proc/cmdline'), + ]) diff --git a/repos/system_upgrade/common/actors/kernelcmdlineconfig/tests/test_kernelcmdlineconfig.py b/repos/system_upgrade/common/actors/kernelcmdlineconfig/tests/test_kernelcmdlineconfig.py index c9fc86ce2e..e4603b3757 100644 --- a/repos/system_upgrade/common/actors/kernelcmdlineconfig/tests/test_kernelcmdlineconfig.py +++ b/repos/system_upgrade/common/actors/kernelcmdlineconfig/tests/test_kernelcmdlineconfig.py @@ -15,20 +15,21 @@ TARGET_KERNEL_NEVRA = 'kernel-core-1.2.3-4.x86_64.el8.x64_64' # pylint: disable=E501 -SAMPLE_KERNEL_ARGS = ("ro rootflags=subvol=root" - " resume=/dev/mapper/luks-2c0df999-81ec-4a35-a1f9-b93afee8c6ad" - " rd.luks.uuid=luks-90a6412f-c588-46ca-9118-5aca35943d25" - " rd.luks.uuid=luks-2c0df999-81ec-4a35-a1f9-b93afee8c6ad rhgb quiet" +SAMPLE_KERNEL_ARGS = ('ro rootflags=subvol=root' + ' resume=/dev/mapper/luks-2c0df999-81ec-4a35-a1f9-b93afee8c6ad' + ' rd.luks.uuid=luks-90a6412f-c588-46ca-9118-5aca35943d25' + ' rd.luks.uuid=luks-2c0df999-81ec-4a35-a1f9-b93afee8c6ad rhgb quiet' ) +SAMPLE_KERNEL_ROOT = 'UUID=1aa15850-2685-418d-95a6-f7266a2de83a' TEMPLATE_GRUBBY_INFO_OUTPUT = """index=0 kernel="/boot/vmlinuz-6.5.13-100.fc37.x86_64" args="{0}" -root="UUID=1aa15850-2685-418d-95a6-f7266a2de83a" +root="{1}" initrd="/boot/initramfs-6.5.13-100.fc37.x86_64.img" title="Fedora Linux (6.5.13-100.fc37.x86_64) 37 (Thirty Seven)" id="a3018267cdd8451db7c77bb3e5b1403d-6.5.13-100.fc37.x86_64" """ # noqa: E501 -SAMPLE_GRUBBY_INFO_OUTPUT = TEMPLATE_GRUBBY_INFO_OUTPUT.format(SAMPLE_KERNEL_ARGS) +SAMPLE_GRUBBY_INFO_OUTPUT = TEMPLATE_GRUBBY_INFO_OUTPUT.format(SAMPLE_KERNEL_ARGS, SAMPLE_KERNEL_ROOT) # pylint: enable=E501 @@ -164,7 +165,7 @@ def test_kernelcmdline_config_no_args(monkeypatch): mocked_run = MockedRun( outputs={" ".join(("grubby", "--info", kernel_img_path)): - TEMPLATE_GRUBBY_INFO_OUTPUT.format("") + TEMPLATE_GRUBBY_INFO_OUTPUT.format("", "") } ) monkeypatch.setattr(stdlib, 'run', mocked_run)