Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RHELC-1329] Port pkghandler.preserve_only_rhel_kernel() to Action framework #1250

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
258 changes: 258 additions & 0 deletions convert2rhel/actions/conversion/preserve_only_rhel_kernel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
# Copyright(C) 2024 Red Hat, Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

__metaclass__ = type

import glob
import logging
import os
import re

from convert2rhel import actions, pkghandler, pkgmanager, utils
from convert2rhel.systeminfo import system_info


loggerinst = logging.getLogger(__name__)


class InstallRhelKernel(actions.Action):
id = "INSTALL_RHEL_KERNEL"
dependencies = ("CONVERT_SYSTEM_PACKAGES",)

def run(self):
"""Install and update the RHEL kernel."""
super(InstallRhelKernel, self).run()
loggerinst.task("Convert: Prepare kernel")

loggerinst.info("Installing RHEL kernel ...")
output, ret_code = pkgmanager.call_yum_cmd(command="install", args=["kernel"])
kernel_update_needed = False

if ret_code != 0:
self.set_result(
level="ERROR",
id="FAILED_TO_INSTALL_RHEL_KERNEL",
title="Failed to install RHEL kernel",
description="There was an error while attempting to install the RHEL kernel from yum.",
remediations="Please check that you can access the repositories that provide the RHEL kernel.",
)
pr-watson marked this conversation as resolved.
Show resolved Hide resolved
return

# Check if kernel with same version is already installed.
# Example output from yum and dnf:
# "Package kernel-4.18.0-193.el8.x86_64 is already installed."
already_installed = re.search(r" (.*?)(?: is)? already installed", output, re.MULTILINE)
if already_installed:
rhel_kernel_nevra = already_installed.group(1)
non_rhel_kernels = pkghandler.get_installed_pkgs_w_different_fingerprint(
system_info.fingerprints_rhel, "kernel"
)
for non_rhel_kernel in non_rhel_kernels:
# We're comparing to NEVRA since that's what yum/dnf prints out
if rhel_kernel_nevra == pkghandler.get_pkg_nevra(non_rhel_kernel):
# If the installed kernel is from a third party (non-RHEL) and has the same NEVRA as the one available
# from RHEL repos, it's necessary to install an older version RHEL kernel and the third party one will
# be removed later in the conversion process. It's because yum/dnf is unable to reinstall a kernel.
info_message = (
"Conflict of kernels: One of the installed kernels"
" has the same version as the latest RHEL kernel."
)
loggerinst.info("\n%s" % info_message)
self.add_message(
level="INFO",
id="CONFLICT_OF_KERNELS",
title="Conflict of installed kernel versions",
description=info_message,
)
pkghandler.handle_no_newer_rhel_kernel_available()
kernel_update_needed = True

if kernel_update_needed:
pkghandler.update_rhel_kernel()
Comment on lines +56 to +83
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have a discussion tomorrow about this code, and if we decide that @danmyway solution is the way to go, then I think we can incorporate his changes here: https://github.com/oamg/convert2rhel/pull/1323/files

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good to me!



class VerifyRhelKernelInstalled(actions.Action):
id = "VERIFY_RHEL_KERNEL_INSTALLED"
dependencies = ("INSTALL_RHEL_KERNEL",)

def run(self):
"""Verify that the RHEL kernel has been successfully installed and raise an ERROR if not"""
super(VerifyRhelKernelInstalled, self).run()

loggerinst.info("Verifying that RHEL kernel has been installed")
installed_rhel_kernels = pkghandler.get_installed_pkgs_by_fingerprint(
system_info.fingerprints_rhel, name="kernel"
)
if len(installed_rhel_kernels) <= 0:
self.set_result(
level="ERROR",
id="NO_RHEL_KERNEL_INSTALLED",
title="No RHEL kernel installed",
description="There is no RHEL kernel installed on the system.",
remediations="Verify that the repository used for installing kernel contains RHEL packages.",
)
return

loggerinst.info("RHEL kernel has been verified to be on the system.")
self.add_message(
level="INFO",
id="RHEL_KERNEL_INSTALL_VERIFIED",
title="RHEL kernel install verified",
description="The RHEL kernel has been verified to be on the system.",
)


class FixInvalidGrub2Entries(actions.Action):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks to me that it should probably belongs to a grub.py module

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True I could see it as apart of the grub refactor we were going to do - seems important to preserving the rhel kernel though

id = "FIX_INVALID_GRUB2_ENTRIES"
dependencies = ("KERNEL_PACKAGES_INSTALLATION",)

def run(self):
"""
On systems derived from RHEL 8 and later, /etc/machine-id is being used to identify grub2 boot loader entries per
the Boot Loader Specification.
However, at the time of executing convert2rhel, the current machine-id can be different from the machine-id from the
time when the kernels were installed. If that happens:
- convert2rhel installs the RHEL kernel, but it's not set as default
- convert2rhel removes the original OS kernels, but for these the boot entries are not removed
The solution handled by this function is to remove the non-functioning boot entries upon the removal of the original
OS kernels, and set the RHEL kernel as default.
"""
super(FixInvalidGrub2Entries, self).run()

if system_info.version.major < 8:
# Applicable only on systems derived from RHEL 8 and later, and systems using GRUB2 (s390x uses zipl)
return

pr-watson marked this conversation as resolved.
Show resolved Hide resolved
loggerinst.info("Fixing GRUB boot loader entries.")

machine_id = utils.get_file_content("/etc/machine-id").strip()
boot_entries = glob.glob("/boot/loader/entries/*.conf")
for entry in boot_entries:
# The boot loader entries in /boot/loader/entries/<machine-id>-<kernel-version>.conf
if machine_id not in os.path.basename(entry):
loggerinst.debug("Removing boot entry %s" % entry)
os.remove(entry)

# Removing a boot entry that used to be the default makes grubby to choose a different entry as default, but we will
# call grub --set-default to set the new default on all the proper places, e.g. for grub2-editenv
output, ret_code = utils.run_subprocess(["/usr/sbin/grubby", "--default-kernel"], print_output=False)
if ret_code:
# Not setting the default entry shouldn't be a deal breaker and the reason to stop the conversions, grub should
# pick one entry in any case.
description = "Couldn't get the default GRUB2 boot loader entry:\n%s" % output
loggerinst.warning(description)
self.add_message(
level="WARNING",
id="UNABLE_TO_GET_GRUB2_BOOT_LOADER_ENTRY",
title="Unable to get the GRUB2 boot loader entry",
description=description,
)
return
loggerinst.debug("Setting RHEL kernel %s as the default boot loader entry." % output.strip())
output, ret_code = utils.run_subprocess(["/usr/sbin/grubby", "--set-default", output.strip()])
if ret_code:
description = "Couldn't set the default GRUB2 boot loader entry:\n%s" % output
loggerinst.warning(description)
self.add_message(
level="WARNING",
id="UNABLE_TO_SET_GRUB2_BOOT_LOADER_ENTRY",
title="Unable to set the GRUB2 boot loader entry",
description=description,
)


class FixDefaultKernel(actions.Action):
id = "FIX_DEFAULT_KERNEL"
dependencies = ("FIX_INVALID_GRUB2_ENTRIES",)

def run(self):
"""
Systems converted from Oracle Linux or CentOS Linux may have leftover kernel-uek or kernel-plus in
/etc/sysconfig/kernel as DEFAULTKERNEL.
This function fixes that by replacing the DEFAULTKERNEL setting from kernel-uek or kernel-plus to kernel for
RHEL7 and kernel-core for RHEL8.
"""
super(FixDefaultKernel, self).run()

loggerinst = logging.getLogger(__name__)

loggerinst.info("Checking for incorrect boot kernel")
kernel_sys_cfg = utils.get_file_content("/etc/sysconfig/kernel")

possible_kernels = ["kernel-uek", "kernel-plus"]
kernel_to_change = next(
iter(kernel for kernel in possible_kernels if kernel in kernel_sys_cfg),
None,
)
if kernel_to_change:
description = "Detected leftover boot kernel, changing to RHEL kernel"
loggerinst.warning(description)
self.add_message(
level="WARNING",
id="LEFTOVER_BOOT_KERNEL_DETECTED",
title="Leftover boot kernel detected",
description=description,
)
# need to change to "kernel" in rhel7 and "kernel-core" in rhel8
new_kernel_str = "DEFAULTKERNEL=" + ("kernel" if system_info.version.major == 7 else "kernel-core")

kernel_sys_cfg = kernel_sys_cfg.replace("DEFAULTKERNEL=" + kernel_to_change, new_kernel_str)
utils.store_content_to_file("/etc/sysconfig/kernel", kernel_sys_cfg)
loggerinst.info("Boot kernel %s was changed to %s" % (kernel_to_change, new_kernel_str))
else:
loggerinst.debug("Boot kernel validated.")


class KernelPkgsInstall(actions.Action):
id = "KERNEL_PACKAGES_INSTALLATION"
dependencies = ("VERIFY_RHEL_KERNEL_INSTALLED",)

def run(self):
"""Install kernel packages and remove non-RHEL kernels."""
super(KernelPkgsInstall, self).run()

kernel_pkgs_to_install = self.remove_non_rhel_kernels()
if kernel_pkgs_to_install:
self.install_additional_rhel_kernel_pkgs(kernel_pkgs_to_install)

def remove_non_rhel_kernels(self):
github-advanced-security[bot] marked this conversation as resolved.
Fixed
Show resolved Hide resolved
loggerinst.info("Searching for non-RHEL kernels ...")
non_rhel_kernels = pkghandler.get_installed_pkgs_w_different_fingerprint(
system_info.fingerprints_rhel, "kernel*"
)
if not non_rhel_kernels:
loggerinst.info("None found.")
return None

Check warning on line 237 in convert2rhel/actions/conversion/preserve_only_rhel_kernel.py

View check run for this annotation

Codecov / codecov/patch

convert2rhel/actions/conversion/preserve_only_rhel_kernel.py#L236-L237

Added lines #L236 - L237 were not covered by tests

loggerinst.info("Removing non-RHEL kernels\n")
pkghandler.print_pkg_info(non_rhel_kernels)
pkgs_to_remove = [pkghandler.get_pkg_nvra(pkg) for pkg in non_rhel_kernels]
utils.remove_pkgs(pkgs_to_remove)
return non_rhel_kernels

def install_additional_rhel_kernel_pkgs(self, additional_pkgs):
"""Convert2rhel removes all non-RHEL kernel packages, including kernel-tools, kernel-headers, etc. This function
tries to install back all of these from RHEL repositories.
"""
# OL renames some of the kernel packages by adding "-uek" (Unbreakable
# Enterprise Kernel), e.g. kernel-uek-devel instead of kernel-devel. Such
# package names need to be mapped to the RHEL kernel package names to have
# them installed on the converted system.
ol_kernel_ext = "-uek"
pkg_names = [p.nevra.name.replace(ol_kernel_ext, "", 1) for p in additional_pkgs]
for name in set(pkg_names):
if name != "kernel":
loggerinst.info("Installing RHEL %s" % name)
pkgmanager.call_yum_cmd("install", args=[name])
2 changes: 0 additions & 2 deletions convert2rhel/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -394,8 +394,6 @@ def post_ponr_changes():

def post_ponr_conversion():
"""Perform main steps for system conversion."""
loggerinst.task("Convert: Prepare kernel")
pkghandler.preserve_only_rhel_kernel()
loggerinst.task("Convert: List remaining non-Red Hat packages")
pkghandler.list_non_red_hat_pkgs_left()
loggerinst.task("Convert: Configure the bootloader")
Expand Down
Loading
Loading