Skip to content

Commit 1e87c50

Browse files
author
Yongxue Hong
committed
libvirt_vm: expand reboot methods and improve state detection
The current reboot verification logic relies on session responsiveness, which can be unreliable. To ensure a more robust verification, this update introduces hardware-level reset capabilities and transitions to event-driven monitoring. Key Enhancements: 1. Introduced system_reset Method: Added support for the virsh reset command. This provides a "hard" reboot option by mimicking a physical power button reset, bypassing the guest OS shutdown signals when necessary. 2. Heuristic Reboot Detection: Rather than polling for an active session, the system now monitors the serial console output for specific boot patterns. 3. Libvirt Event Integration: Leveraged libvirt lifecycle events to detect state transitions in real-time, providing a more accurate confirmation that the VM has successfully cycled. Signed-off-by: Yongxue Hong <yhong@redhat.com>
1 parent 3e617c9 commit 1e87c50

1 file changed

Lines changed: 93 additions & 12 deletions

File tree

virttest/libvirt_vm.py

Lines changed: 93 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
import shutil
1616
import string
1717
import tempfile
18+
import threading
1819
import time
20+
from functools import partial
1921

2022
import aexpect
2123
from aexpect import remote
@@ -38,6 +40,7 @@
3840
virt_vm,
3941
xml_utils,
4042
)
43+
from virttest.utils_test import libvirt
4144

4245
# Using as lower capital is not the best way to do, but this is just a
4346
# workaround to avoid changing the entire file.
@@ -2716,37 +2719,115 @@ def deactivate_nic(self, nic_index_or_name):
27162719

27172720
@error_context.context_aware
27182721
def reboot(
2719-
self, session=None, method="shell", nic_index=0, timeout=240, serial=False
2722+
self,
2723+
session=None,
2724+
method="shell",
2725+
nic_index=0,
2726+
timeout=virt_vm.BaseVM.REBOOT_TIMEOUT,
2727+
serial=False,
27202728
):
27212729
"""
27222730
Reboot the VM and wait for it to come back up by trying to log in until
27232731
timeout expires.
27242732
27252733
:param session: A shell session object or None.
2726-
:param method: Reboot method. Can be "shell" (send a shell reboot
2727-
command).
2734+
:param method: Reboot method. Can be "shell" (send a shell reboot
2735+
command) or "libvirt_reset" (use virsh reset).
27282736
:param nic_index: Index of NIC to access in the VM, when logging in
27292737
after rebooting.
27302738
:param timeout: Time to wait for login to succeed (after rebooting).
2731-
:param serial: Just use to unify api in virt_vm module.
2739+
:param serial: Whether to use serial console for login.
27322740
:return: A new shell session object.
2733-
"""
2741+
:raises VMRebootError: If reboot fails or guest doesn't respond.
2742+
"""
2743+
2744+
def _check_serial_console_down(console, timeout):
2745+
"""Check if serial console indicates system is going down."""
2746+
reboot_patterns = [
2747+
# Linux reboot patterns
2748+
r".*[Rr]ebooting.*",
2749+
r".*[Rr]estarting system.*",
2750+
r".*[Mm]achine restart.*",
2751+
r".*Linux version.*",
2752+
# Windows reboot patterns
2753+
r".*Microsoft Windows \[Version.*",
2754+
r".*Microsoft Corporation\. All rights reserved\.*",
2755+
]
2756+
try:
2757+
if console.read_until_any_line_matches(
2758+
reboot_patterns, timeout=timeout
2759+
):
2760+
LOG.debug("Reboot pattern detected in serial console")
2761+
return True
2762+
LOG.debug(f"No reboot patterns detected within timeout {timeout} sec")
2763+
return False
2764+
except Exception as e:
2765+
LOG.debug(f"Unexpected error during serial console reboot check: {e}")
2766+
return False
2767+
2768+
def _execute_shell_reboot(session):
2769+
"""Execute reboot command via shell session."""
2770+
reboot_cmd = self.params.get("reboot_command")
2771+
session.cmd(reboot_cmd, ignore_all_errors=True)
2772+
2773+
def _check_system_event_reboot(timeout):
2774+
"""Check if system is reboot via libvirt events."""
2775+
try:
2776+
result = virsh.event(
2777+
domain=self.name, event="reboot", event_timeout=timeout
2778+
)
2779+
libvirt.check_exit_status(result)
2780+
LOG.debug("Detected libvirt reboot event")
2781+
return True
2782+
except Exception as e:
2783+
LOG.debug(f"Libvirt reboot event check failed: {e}")
2784+
return False
2785+
2786+
def _execute_libvirt_reset():
2787+
"""Execute libvirt reset via virsh."""
2788+
reset_thread = threading.Thread(
2789+
target=virsh.reset, args=(self.name,), name=f"reset-{self.name}"
2790+
)
2791+
reset_thread.daemon = True
2792+
reset_thread.start()
2793+
27342794
error_context.base_context("rebooting '%s'" % self.name, LOG.info)
27352795
error_context.context("before reboot")
2736-
session = session or self.login(timeout=timeout)
27372796
error_context.context()
27382797

2798+
serial_console = None
27392799
if method == "shell":
2740-
session.sendline(self.params.get("reboot_command"))
2800+
if not session:
2801+
if not serial:
2802+
session = self.wait_for_login(nic_index=nic_index, timeout=timeout)
2803+
else:
2804+
session = self.wait_for_serial_login(timeout=timeout)
2805+
2806+
if isinstance(session, remote.RemoteSession):
2807+
serial_console = self.wait_for_serial_login(timeout=timeout)
2808+
2809+
_reboot = partial(_execute_shell_reboot, session)
2810+
_check_go_down = partial(
2811+
_check_serial_console_down,
2812+
serial_console if serial_console else session,
2813+
30,
2814+
)
2815+
elif method == "libvirt_reset":
2816+
_reboot = partial(_execute_libvirt_reset)
2817+
_check_go_down = partial(_check_system_event_reboot, 30)
27412818
else:
27422819
raise virt_vm.VMRebootError("Unknown reboot method: %s" % method)
27432820

27442821
error_context.context("waiting for guest to go down", LOG.info)
2745-
if not utils_misc.wait_for(
2746-
lambda: not session.is_responsive(timeout=30), 120, 0, 1
2747-
):
2748-
raise virt_vm.VMRebootError("Guest refuses to go down")
2749-
session.close()
2822+
try:
2823+
_reboot()
2824+
if not utils_misc.wait_for(_check_go_down, timeout=timeout):
2825+
raise virt_vm.VMRebootError("Guest refuses to go down")
2826+
finally:
2827+
if session:
2828+
session.close()
2829+
if serial_console:
2830+
serial_console.close()
27502831

27512832
error_context.context("logging in after reboot", LOG.info)
27522833
if serial:

0 commit comments

Comments
 (0)