|
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 |
|
39 | 41 | virt_vm, |
40 | 42 | xml_utils, |
41 | 43 | ) |
| 44 | +from virttest.utils_test import libvirt |
42 | 45 |
|
43 | 46 | # Using as lower capital is not the best way to do, but this is just a |
44 | 47 | # workaround to avoid changing the entire file. |
@@ -2726,37 +2729,115 @@ def deactivate_nic(self, nic_index_or_name): |
2726 | 2729 |
|
2727 | 2730 | @error_context.context_aware |
2728 | 2731 | def reboot( |
2729 | | - self, session=None, method="shell", nic_index=0, timeout=240, serial=False |
| 2732 | + self, |
| 2733 | + session=None, |
| 2734 | + method="shell", |
| 2735 | + nic_index=0, |
| 2736 | + timeout=virt_vm.BaseVM.REBOOT_TIMEOUT, |
| 2737 | + serial=False, |
2730 | 2738 | ): |
2731 | 2739 | """ |
2732 | 2740 | Reboot the VM and wait for it to come back up by trying to log in until |
2733 | 2741 | timeout expires. |
2734 | 2742 |
|
2735 | 2743 | :param session: A shell session object or None. |
2736 | | - :param method: Reboot method. Can be "shell" (send a shell reboot |
2737 | | - command). |
| 2744 | + :param method: Reboot method. Can be "shell" (send a shell reboot |
| 2745 | + command) or "libvirt_reset" (use virsh reset). |
2738 | 2746 | :param nic_index: Index of NIC to access in the VM, when logging in |
2739 | 2747 | after rebooting. |
2740 | 2748 | :param timeout: Time to wait for login to succeed (after rebooting). |
2741 | | - :param serial: Just use to unify api in virt_vm module. |
| 2749 | + :param serial: Whether to use serial console for login. |
2742 | 2750 | :return: A new shell session object. |
2743 | | - """ |
| 2751 | + :raises VMRebootError: If reboot fails or guest doesn't respond. |
| 2752 | + """ |
| 2753 | + |
| 2754 | + def _check_serial_console_down(console, timeout): |
| 2755 | + """Check if serial console indicates system is going down.""" |
| 2756 | + reboot_patterns = [ |
| 2757 | + # Linux reboot patterns |
| 2758 | + r".*[Rr]ebooting.*", |
| 2759 | + r".*[Rr]estarting system.*", |
| 2760 | + r".*[Mm]achine restart.*", |
| 2761 | + r".*Linux version.*", |
| 2762 | + # Windows reboot patterns |
| 2763 | + r".*Microsoft Windows \[Version.*", |
| 2764 | + r".*Microsoft Corporation\. All rights reserved\.*", |
| 2765 | + ] |
| 2766 | + try: |
| 2767 | + if console.read_until_any_line_matches( |
| 2768 | + reboot_patterns, timeout=timeout |
| 2769 | + ): |
| 2770 | + LOG.debug("Reboot pattern detected in serial console") |
| 2771 | + return True |
| 2772 | + LOG.debug(f"No reboot patterns detected within timeout {timeout} sec") |
| 2773 | + return False |
| 2774 | + except Exception as e: |
| 2775 | + LOG.debug(f"Unexpected error during serial console reboot check: {e}") |
| 2776 | + return False |
| 2777 | + |
| 2778 | + def _execute_shell_reboot(session): |
| 2779 | + """Execute reboot command via shell session.""" |
| 2780 | + reboot_cmd = self.params.get("reboot_command") |
| 2781 | + session.cmd(reboot_cmd, ignore_all_errors=True) |
| 2782 | + |
| 2783 | + def _check_system_event_reboot(timeout): |
| 2784 | + """Check if system is reboot via libvirt events.""" |
| 2785 | + try: |
| 2786 | + result = virsh.event( |
| 2787 | + domain=self.name, event="reboot", event_timeout=timeout |
| 2788 | + ) |
| 2789 | + libvirt.check_exit_status(result) |
| 2790 | + LOG.debug("Detected libvirt reboot event") |
| 2791 | + return True |
| 2792 | + except Exception as e: |
| 2793 | + LOG.debug(f"Libvirt reboot event check failed: {e}") |
| 2794 | + return False |
| 2795 | + |
| 2796 | + def _execute_libvirt_reset(): |
| 2797 | + """Execute libvirt reset via virsh.""" |
| 2798 | + reset_thread = threading.Thread( |
| 2799 | + target=virsh.reset, args=(self.name,), name=f"reset-{self.name}" |
| 2800 | + ) |
| 2801 | + reset_thread.daemon = True |
| 2802 | + reset_thread.start() |
| 2803 | + |
2744 | 2804 | error_context.base_context("rebooting '%s'" % self.name, LOG.info) |
2745 | 2805 | error_context.context("before reboot") |
2746 | | - session = session or self.login(timeout=timeout) |
2747 | 2806 | error_context.context() |
2748 | 2807 |
|
| 2808 | + serial_console = None |
2749 | 2809 | if method == "shell": |
2750 | | - session.sendline(self.params.get("reboot_command")) |
| 2810 | + if not session: |
| 2811 | + if not serial: |
| 2812 | + session = self.wait_for_login(nic_index=nic_index, timeout=timeout) |
| 2813 | + else: |
| 2814 | + session = self.wait_for_serial_login(timeout=timeout) |
| 2815 | + |
| 2816 | + if isinstance(session, remote.RemoteSession): |
| 2817 | + serial_console = self.wait_for_serial_login(timeout=timeout) |
| 2818 | + |
| 2819 | + _reboot = partial(_execute_shell_reboot, session) |
| 2820 | + _check_go_down = partial( |
| 2821 | + _check_serial_console_down, |
| 2822 | + serial_console if serial_console else session, |
| 2823 | + 30, |
| 2824 | + ) |
| 2825 | + elif method == "libvirt_reset": |
| 2826 | + _reboot = partial(_execute_libvirt_reset) |
| 2827 | + _check_go_down = partial(_check_system_event_reboot, 30) |
2751 | 2828 | else: |
2752 | 2829 | raise virt_vm.VMRebootError("Unknown reboot method: %s" % method) |
2753 | 2830 |
|
2754 | 2831 | error_context.context("waiting for guest to go down", LOG.info) |
2755 | | - if not utils_misc.wait_for( |
2756 | | - lambda: not session.is_responsive(timeout=30), 120, 0, 1 |
2757 | | - ): |
2758 | | - raise virt_vm.VMRebootError("Guest refuses to go down") |
2759 | | - session.close() |
| 2832 | + try: |
| 2833 | + _reboot() |
| 2834 | + if not utils_misc.wait_for(_check_go_down, timeout=timeout): |
| 2835 | + raise virt_vm.VMRebootError("Guest refuses to go down") |
| 2836 | + finally: |
| 2837 | + if session: |
| 2838 | + session.close() |
| 2839 | + if serial_console: |
| 2840 | + serial_console.close() |
2760 | 2841 |
|
2761 | 2842 | error_context.context("logging in after reboot", LOG.info) |
2762 | 2843 | if serial: |
|
0 commit comments