From a40fdb3641de792b36820fa91af90fad563eea90 Mon Sep 17 00:00:00 2001 From: grossmj Date: Fri, 23 Mar 2018 00:07:32 +0700 Subject: [PATCH] Fix bug and add optimizations when connecting and sending commands to QEMU monitor after starting a VM. Fixes #2336. --- .../compute/dynamips/dynamips_hypervisor.py | 2 +- gns3server/compute/qemu/qemu_vm.py | 70 ++++++++++++++++--- 2 files changed, 63 insertions(+), 9 deletions(-) diff --git a/gns3server/compute/dynamips/dynamips_hypervisor.py b/gns3server/compute/dynamips/dynamips_hypervisor.py index 936707d6..e59ab2dc 100644 --- a/gns3server/compute/dynamips/dynamips_hypervisor.py +++ b/gns3server/compute/dynamips/dynamips_hypervisor.py @@ -90,7 +90,7 @@ class DynamipsHypervisor: if not connection_success: raise DynamipsError("Couldn't connect to hypervisor on {}:{} :{}".format(host, self._port, last_exception)) else: - log.info("Connected to Dynamips hypervisor after {:.4f} seconds".format(time.time() - begin)) + log.info("Connected to Dynamips hypervisor on {}:{} after {:.4f} seconds".format(host, self._port, time.time() - begin)) try: version = yield from self.send("hypervisor version") diff --git a/gns3server/compute/qemu/qemu_vm.py b/gns3server/compute/qemu/qemu_vm.py index 3aa4390f..13801fde 100644 --- a/gns3server/compute/qemu/qemu_vm.py +++ b/gns3server/compute/qemu/qemu_vm.py @@ -30,6 +30,7 @@ import asyncio import socket import gns3server import subprocess +import time from gns3server.utils import parse_version from gns3server.utils.asyncio import subprocess_check_output, cancellable_wait_run_in_executor @@ -956,6 +957,7 @@ class QemuVM(BaseNode): self._hw_virtualization = True yield from self._start_ubridge() + set_link_commands = [] for adapter_number, adapter in enumerate(self._ethernet_adapters): nio = adapter.get_nio(0) if nio: @@ -963,9 +965,10 @@ class QemuVM(BaseNode): self._local_udp_tunnels[adapter_number][1], nio) if nio.suspend: - yield from self._control_vm("set_link gns3-{} off".format(adapter_number)) + set_link_commands.append("set_link gns3-{} off".format(adapter_number)) else: - yield from self._control_vm("set_link gns3-{} off".format(adapter_number)) + set_link_commands.append("set_link gns3-{} off".format(adapter_number)) + yield from self._control_vm_commands(set_link_commands) try: yield from self.start_wrap_console() @@ -1020,6 +1023,37 @@ class QemuVM(BaseNode): self._stop_cpulimit() yield from super().stop() + @asyncio.coroutine + def _open_qemu_monitor_connection_vm(self, timeout=10): + """ + Opens a connection to the QEMU monitor. + + :param timeout: timeout to connect to the monitor TCP server + :returns: The reader returned is a StreamReader instance; the writer is a StreamWriter instance + """ + + begin = time.time() + connection_success = False + last_exception = None + reader = writer = None + while time.time() - begin < timeout: + yield from asyncio.sleep(0.01) + try: + log.debug("Connecting to Qemu monitor on {}:{}".format(self._monitor_host, self._monitor)) + reader, writer = yield from asyncio.open_connection(self._monitor_host, self._monitor) + except (asyncio.TimeoutError, OSError) as e: + last_exception = e + continue + connection_success = True + break + + if not connection_success: + log.warning("Could not connect to QEMU monitor on {}:{}: {}".format(self._monitor_host, self._monitor, + last_exception)) + else: + log.info("Connected to QEMU monitor on {}:{} after {:.4f} seconds".format(self._monitor_host, self._monitor, time.time() - begin)) + return reader, writer + @asyncio.coroutine def _control_vm(self, command, expected=None): """ @@ -1033,13 +1067,11 @@ class QemuVM(BaseNode): result = None if self.is_running() and self._monitor: - log.debug("Execute QEMU monitor command: {}".format(command)) - try: - log.debug("Connecting to Qemu monitor on {}:{}".format(self._monitor_host, self._monitor)) - reader, writer = yield from asyncio.open_connection(self._monitor_host, self._monitor) - except OSError as e: - log.warning("Could not connect to QEMU monitor on {}:{}: {}".format(self._monitor_host, self._monitor, e)) + log.info("Execute QEMU monitor command: {}".format(command)) + reader, writer = yield from self._open_qemu_monitor_connection_vm() + if reader is None and writer is None: return result + try: writer.write(command.encode('ascii') + b"\n") except OSError as e: @@ -1061,6 +1093,28 @@ class QemuVM(BaseNode): writer.close() return result + @asyncio.coroutine + def _control_vm_commands(self, commands): + """ + Executes commands with QEMU monitor when this VM is running. + + :param commands: a list of QEMU monitor commands (e.g. info status, stop etc.) + """ + + if self.is_running() and self._monitor: + + reader, writer = yield from self._open_qemu_monitor_connection_vm() + if reader is None and writer is None: + return + + for command in commands: + log.info("Execute QEMU monitor command: {}".format(command)) + try: + writer.write(command.encode('ascii') + b"\n") + except OSError as e: + log.warning("Could not write to QEMU monitor: {}".format(e)) + writer.close() + @asyncio.coroutine def close(self): """