diff --git a/gns3server/handlers/api/qemu_handler.py b/gns3server/handlers/api/qemu_handler.py index 659d1b03..14d12d07 100644 --- a/gns3server/handlers/api/qemu_handler.py +++ b/gns3server/handlers/api/qemu_handler.py @@ -51,12 +51,11 @@ class QEMUHandler: request.match_info["project_id"], request.json.get("vm_id"), qemu_path=request.json.get("qemu_path"), - console=request.json.get("console"), - monitor=request.json.get("monitor")) + console=request.json.get("console")) # Clear already used keys map(request.json.__delitem__, ["name", "project_id", "vm_id", - "qemu_path", "console", "monitor"]) + "qemu_path", "console"]) for field in request.json: setattr(vm, field, request.json[field]) diff --git a/gns3server/modules/qemu/qemu_vm.py b/gns3server/modules/qemu/qemu_vm.py index 7d1675ee..0949e04c 100644 --- a/gns3server/modules/qemu/qemu_vm.py +++ b/gns3server/modules/qemu/qemu_vm.py @@ -26,6 +26,7 @@ import random import subprocess import shlex import asyncio +import socket from .qemu_error import QemuError from ..adapters.ethernet_adapter import EthernetAdapter @@ -51,7 +52,6 @@ class QemuVM(BaseVM): :param qemu_path: path to the QEMU binary :param qemu_id: QEMU VM instance ID :param console: TCP console port - :param monitor: TCP monitor port """ def __init__(self, @@ -60,8 +60,7 @@ class QemuVM(BaseVM): project, manager, qemu_path=None, - console=None, - monitor=None): + console=None): super().__init__(name, vm_id, project, manager, console=console) @@ -72,6 +71,7 @@ class QemuVM(BaseVM): self._started = False self._process = None self._cpulimit_process = None + self._monitor = None self._stdout_file = "" # QEMU settings @@ -82,7 +82,6 @@ class QemuVM(BaseVM): self._hdd_disk_image = "" self._options = "" self._ram = 256 - self._monitor = monitor self._ethernet_adapters = [] self._adapter_type = "e1000" self._initrd = "" @@ -92,11 +91,6 @@ class QemuVM(BaseVM): self._cpu_throttling = 0 # means no CPU throttling self._process_priority = "low" - if self._monitor is not None: - self._monitor = self._manager.port_manager.reserve_tcp_port(self._monitor, self._project) - else: - self._monitor = self._manager.port_manager.get_free_tcp_port(self._project) - self.adapters = 1 # creates 1 adapter by default log.info("QEMU VM {name} [id={id}] has been created".format(name=self._name, id=self._id)) @@ -111,25 +105,6 @@ class QemuVM(BaseVM): return self._monitor - @monitor.setter - def monitor(self, monitor): - """ - Sets the TCP monitor port. - - :param monitor: monitor port (integer) - """ - - if monitor == self._monitor: - return - if self._monitor: - self._manager.port_manager.release_monitor_port(self._monitor, self._project) - self._monitor = self._manager.port_manager.reserve_monitor_port(monitor, self._project) - log.info("{module}: '{name}' [{id}]: monitor port set to {port}".format( - module=self.manager.module_name, - name=self.name, - id=self.id, - port=monitor)) - @property def qemu_path(self): """ @@ -610,6 +585,16 @@ class QemuVM(BaseVM): return else: + + if self._manager.config.get_section_config("Qemu").getboolean("monitor", True): + try: + # let the OS find an unused port for the Qemu monitor + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + sock.bind((self._monitor_host, 0)) + self._monitor = sock.getsockname()[1] + except OSError as e: + raise QemuError("Could not find free port for the Qemu monitor: {}".format(e)) + self._command = yield from self._build_command() try: log.info("starting QEMU: {}".format(self._command)) @@ -701,9 +686,6 @@ class QemuVM(BaseVM): if self._console: self._manager.port_manager.release_tcp_port(self._console, self._project) self._console = None - if self._monitor: - self._manager.port_manager.release_tcp_port(self._monitor, self._project) - self._monitor = None @asyncio.coroutine def _get_vm_status(self): @@ -732,14 +714,15 @@ class QemuVM(BaseVM): Suspends this QEMU VM. """ - vm_status = yield from self._get_vm_status() - if vm_status is None: - raise QemuError("Suspending a QEMU VM is not supported") - elif vm_status == "running": - yield from self._control_vm("stop") - log.debug("QEMU VM has been suspended") - else: - log.info("QEMU VM is not running to be suspended, current status is {}".format(vm_status)) + if self.is_running(): + vm_status = yield from self._get_vm_status() + if vm_status is None: + raise QemuError("Suspending a QEMU VM is not supported") + elif vm_status == "running": + yield from self._control_vm("stop") + log.debug("QEMU VM has been suspended") + else: + log.info("QEMU VM is not running to be suspended, current status is {}".format(vm_status)) @asyncio.coroutine def reload(self): diff --git a/gns3server/schemas/qemu.py b/gns3server/schemas/qemu.py index 7632525a..1a77d3ef 100644 --- a/gns3server/schemas/qemu.py +++ b/gns3server/schemas/qemu.py @@ -47,12 +47,6 @@ QEMU_CREATE_SCHEMA = { "maximum": 65535, "type": ["integer", "null"] }, - "monitor": { - "description": "monitor TCP port", - "minimum": 1, - "maximum": 65535, - "type": ["integer", "null"] - }, "hda_disk_image": { "description": "QEMU hda disk image path", "type": ["string", "null"], @@ -146,12 +140,6 @@ QEMU_UPDATE_SCHEMA = { "maximum": 65535, "type": ["integer", "null"] }, - "monitor": { - "description": "monitor TCP port", - "minimum": 1, - "maximum": 65535, - "type": ["integer", "null"] - }, "hda_disk_image": { "description": "QEMU hda disk image path", "type": ["string", "null"], @@ -341,12 +329,6 @@ QEMU_OBJECT_SCHEMA = { "maximum": 65535, "type": "integer" }, - "monitor": { - "description": "monitor TCP port", - "minimum": 1, - "maximum": 65535, - "type": "integer" - }, "initrd": { "description": "QEMU initrd path", "type": "string", @@ -386,7 +368,7 @@ QEMU_OBJECT_SCHEMA = { "additionalProperties": False, "required": ["vm_id", "project_id", "name", "qemu_path", "hda_disk_image", "hdb_disk_image", "hdc_disk_image", "hdd_disk_image", "ram", "adapters", "adapter_type", "console", - "monitor", "initrd", "kernel_image", "kernel_command_line", + "initrd", "kernel_image", "kernel_command_line", "legacy_networking", "cpu_throttling", "process_priority", "options" ] } diff --git a/tests/modules/qemu/test_qemu_vm.py b/tests/modules/qemu/test_qemu_vm.py index 1e078c26..f05e78c4 100644 --- a/tests/modules/qemu/test_qemu_vm.py +++ b/tests/modules/qemu/test_qemu_vm.py @@ -151,14 +151,11 @@ def test_close(vm, port_manager, loop): loop.run_until_complete(asyncio.async(vm.start())) console_port = vm.console - monitor_port = vm.monitor loop.run_until_complete(asyncio.async(vm.close())) # Raise an exception if the port is not free port_manager.reserve_tcp_port(console_port, vm.project) - # Raise an exception if the port is not free - port_manager.reserve_tcp_port(monitor_port, vm.project) assert vm.is_running() is False @@ -226,7 +223,6 @@ def test_json(vm, project): def test_control_vm(vm, loop): vm._process = MagicMock() - vm._monitor = 4242 reader = MagicMock() writer = MagicMock() with asyncio_patch("asyncio.open_connection", return_value=(reader, writer)) as open_connect: @@ -238,7 +234,6 @@ def test_control_vm(vm, loop): def test_control_vm_expect_text(vm, loop, running_subprocess_mock): vm._process = running_subprocess_mock - vm._monitor = 4242 reader = MagicMock() writer = MagicMock() with asyncio_patch("asyncio.open_connection", return_value=(reader, writer)) as open_connect: @@ -247,6 +242,7 @@ def test_control_vm_expect_text(vm, loop, running_subprocess_mock): future.set_result(b"epic product") reader.readline.return_value = future + vm._monitor = 4242 res = loop.run_until_complete(asyncio.async(vm._control_vm("test", [b"epic"]))) assert writer.write.called_with("test") @@ -269,8 +265,6 @@ def test_build_command(vm, loop, fake_qemu_binary, port_manager): os.path.join(vm.working_dir, "flash.qcow2"), "-serial", "telnet:127.0.0.1:{},server,nowait".format(vm.console), - "-monitor", - "tcp:127.0.0.1:{},server,nowait".format(vm.monitor), "-device", "e1000,mac=00:00:ab:7e:b5:00,netdev=gns3-0", "-netdev",