Skip to content
Merged
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
103 changes: 103 additions & 0 deletions virttest/utils_test/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,109 @@ def update_boot_option(
session.close()


def update_vm_default_kernel(
vm,
kernel_version,
reboot,
guest_arch_name,
timeout="",
serial_login=False,
):
"""
Update guest default kernel.

:param vm: The VM object.
:param kernel_version: The full kernel version that should be set as the new default.
Can use the entire kernel path or just the full kernel name.
get_available_kernel_paths(session, pattern) is a useful helper function.
:param reboot: Whether to reboot the VM and update the kernel used or not.
:param guest_arch_name: Guest architecture, e.g. x86_64, s390x, aarch64.
:param timeout: Timeout for login and reboot.
:param serial_login: Login guest via serial session.
:raise NotImplementedError: Raised if Windows guest, not Linux.
:raise exceptions.TestError: Raised if failed to update the guest kernel.
"""
if vm.params.get("os_type") == "windows":
raise NotImplementedError(
"update_vm_default_kernel() is supported only for Linux guest"
)

timeout = int(timeout) if timeout else int(vm.params.get("login_timeout"))
session = vm.wait_for_login(
timeout=timeout, serial=serial_login, restart_network=True
)
try:
if not utils_package.package_install("grubby", session=session):
raise exceptions.TestError("Failed to install grubby package")

cmd = "grubby --default-kernel"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Grubby will use the kernel image path

# grubby --default-kernel
/boot/vmlinuz-6.13.5-200.fc41.x86_64

That makes me wonder which format you use for kernel_version, especially considering that there's some arch-specific info in that image file name.
From line 299 I assume you expect the full kernel path as there's no manipulation.
How would a user retrieve that value before calling this function? Would it make sense for this function to construct part of the full path, e.g. expecting only 6.13.5-200.fc41?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Below is some experimentation with what --set-kernel can and can't take. So some variation is accepted. However, I wouldn't be sure how to take in "6.13.5-200.fc41" as, for aarch64, there is the 64k option, so wouldn't be sure how that would work, especially if multiple kernels match the same partial string - such as differentiating between "vmlinuz-5.14.0-570.4.1.el9_6.aarch64+64k" and "vmlinuz-5.14.0-570.4.1.el9_6.aarch64" if only "5.14.0-570.4.1.el9_6" is given.

I added "Can use the entire kernel path or just be the full kernel name." to hopefully clarify that it can be either the path or just the name, but I don't know how to use just part of the kernel name without overcomplicating the function and making incorrect assumptions.

[root@ampere-mtsnow-altramax-47 /]# grubby --default-kernel
/boot/vmlinuz-5.14.0-570.4.1.el9_6.aarch64+64k

# using full output from grubby
[root@ampere-mtsnow-altramax-47 /]# grubby --set-default="kernel="/boot/vmlinuz-5.14.0-570.4.1.el9_6.aarch64""
The default is /boot/loader/entries/0eae46187a3c4734ad721259b7d1c2b5-5.14.0-570.4.1.el9_6.aarch64.conf with index 1 and kernel /boot/vmlinuz-5.14.0-570.4.1.el9_6.aarch64
[root@ampere-mtsnow-altramax-47 /]# grubby --default-kernel
/boot/vmlinuz-5.14.0-570.4.1.el9_6.aarch64

# using full path
[root@ampere-mtsnow-altramax-47 /]# grubby --set-default="/boot/vmlinuz-5.14.0-570.4.1.el9_6.aarch64+64k"
The default is /boot/loader/entries/0eae46187a3c4734ad721259b7d1c2b5-5.14.0-570.4.1.el9_6.aarch64+64k.conf with index 0 and kernel /boot/vmlinuz-5.14.0-570.4.1.el9_6.aarch64+64k
[root@ampere-mtsnow-altramax-47 /]# grubby --default-kernel
/boot/vmlinuz-5.14.0-570.4.1.el9_6.aarch64+64k

# using just full name 
[root@ampere-mtsnow-altramax-47 /]# grubby --set-default="vmlinuz-5.14.0-570.4.1.el9_6.aarch64"
The default is /boot/loader/entries/0eae46187a3c4734ad721259b7d1c2b5-5.14.0-570.4.1.el9_6.aarch64.conf with index 1 and kernel /boot/vmlinuz-5.14.0-570.4.1.el9_6.aarch64
[root@ampere-mtsnow-altramax-47 /]# grubby --default-kernel
/boot/vmlinuz-5.14.0-570.4.1.el9_6.aarch64

# using partial name (not including vmlinuz)
[root@ampere-mtsnow-altramax-47 /]# grubby --set-default="5.14.0-570.4.1.el9_6.aarch64+64k"
The param 5.14.0-570.4.1.el9_6.aarch64+64k is incorrect

Copy link
Copy Markdown
Contributor

@smitterl smitterl Mar 27, 2025

Choose a reason for hiding this comment

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

To me it looks like /boot is not necessary but the full kernel image file name is.

In any case, I believe you don't have to worry about that, grubby can return all kernel paths and you can implement a function that helps select one and define it here right next to it and maybe also mention in the docstrings that it can be used to select the right kernel path:

def get_available_kernel_path(session, kernel_pattern):
    """
    Looks up all available kernels on the system and returns the first one whose image file name matches a pattern
    ...
    """
    s, output = utils_misc.cmd_status_output("grubby --info ALL", session=session ...)
    ...
    for line in output.split('\n'):
        if line.startswith("kernel=") and re.match(kernel_pattern, line):
            return line.split("=")[1]
    ...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Sounds good! I did make it so the function returns all of the kernels it found in a list in case there is some reason to find all --> perhaps might make the function more useful.

status, output = session.cmd_status_output(cmd)
if status != 0:
LOG.error(output)
else:
if kernel_version in output:
LOG.info(
"%s is already the default kernel version (%s)"
% (kernel_version, output)
)
return

msg = "Set default guest kernel"
cmd = 'grubby --set-default="%s"' % kernel_version
__run_cmd_and_handle_error(
msg, cmd, session, "Failed to set default guest kernel"
)

if guest_arch_name == "s390x":
msg = "Update boot media with zipl"
cmd = "zipl"
__run_cmd_and_handle_error(
msg, cmd, session, "Failed to update boot media with zipl"
)

if reboot:
LOG.info("Rebooting guest ...")
session = vm.reboot(session=session, timeout=timeout, serial=serial_login)

finally:
if session:
session.close()


def get_available_kernel_paths(session, pattern):
"""
Get the full kernel paths given an identifying pattern

:params session: the vm session
:params pattern: regex pattern to identify specific kernel
:returns: list of kernel paths
:raises exceptions.TestError: raised if no matching kernels could be found
"""
try:
if not utils_package.package_install("grubby", session=session):
raise exceptions.TestError("Failed to install grubby package")
cmd = "grubby --info=ALL"
lines = session.cmd_output(cmd).splitlines()

kernel_paths = []
for line in lines:
if line.startswith("kernel=") and re.match(pattern, line):
kernel = line.split("=")[1].strip('"')
kernel_paths.append(kernel)
LOG.info("kernel path(s) found: %s" % kernel_paths)

if not kernel_paths:
LOG.debug("Full %s output: %s" % (cmd, lines))
raise exceptions.TestError(
"No kernels with %s pattern could be found" % pattern
)
return kernel_paths
except:
raise exceptions.TestError(
"No kernels with %s pattern could be found" % pattern
)


def stop_windows_service(session, service, timeout=120):
"""
Stop a Windows service using sc.
Expand Down