|
15 | 15 | import shutil |
16 | 16 | import string |
17 | 17 | import tempfile |
| 18 | +import threading |
18 | 19 | import time |
| 20 | +from functools import partial |
19 | 21 |
|
20 | 22 | import aexpect |
21 | 23 | from aexpect import remote |
|
38 | 40 | virt_vm, |
39 | 41 | xml_utils, |
40 | 42 | ) |
| 43 | +from virttest.utils_test import libvirt |
41 | 44 |
|
42 | 45 | # Using as lower capital is not the best way to do, but this is just a |
43 | 46 | # workaround to avoid changing the entire file. |
@@ -2716,37 +2719,115 @@ def deactivate_nic(self, nic_index_or_name): |
2716 | 2719 |
|
2717 | 2720 | @error_context.context_aware |
2718 | 2721 | 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, |
2720 | 2728 | ): |
2721 | 2729 | """ |
2722 | 2730 | Reboot the VM and wait for it to come back up by trying to log in until |
2723 | 2731 | timeout expires. |
2724 | 2732 |
|
2725 | 2733 | :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). |
2728 | 2736 | :param nic_index: Index of NIC to access in the VM, when logging in |
2729 | 2737 | after rebooting. |
2730 | 2738 | :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. |
2732 | 2740 | :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 | + |
2734 | 2794 | error_context.base_context("rebooting '%s'" % self.name, LOG.info) |
2735 | 2795 | error_context.context("before reboot") |
2736 | | - session = session or self.login(timeout=timeout) |
2737 | 2796 | error_context.context() |
2738 | 2797 |
|
| 2798 | + serial_console = None |
2739 | 2799 | 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) |
2741 | 2818 | else: |
2742 | 2819 | raise virt_vm.VMRebootError("Unknown reboot method: %s" % method) |
2743 | 2820 |
|
2744 | 2821 | 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() |
2750 | 2831 |
|
2751 | 2832 | error_context.context("logging in after reboot", LOG.info) |
2752 | 2833 | if serial: |
|
0 commit comments