diff --git a/gns3server/compute/qemu/__init__.py b/gns3server/compute/qemu/__init__.py index d377fd6e..562f684b 100644 --- a/gns3server/compute/qemu/__init__.py +++ b/gns3server/compute/qemu/__init__.py @@ -214,7 +214,7 @@ class Qemu(BaseManager): return version except (UnicodeDecodeError, OSError) as e: log.warning("could not read {}: {}".format(version_file, e)) - return "" + return None else: try: output = await subprocess_check_output(qemu_path, "-version", "-nographic") diff --git a/gns3server/compute/qemu/qemu_vm.py b/gns3server/compute/qemu/qemu_vm.py index 8b611fea..94b50114 100644 --- a/gns3server/compute/qemu/qemu_vm.py +++ b/gns3server/compute/qemu/qemu_vm.py @@ -86,6 +86,7 @@ class QemuVM(BaseNode): self._local_udp_tunnels = {} self._guest_cid = None self._command_line_changed = False + self._qemu_version = None # QEMU VM settings if qemu_path: @@ -1871,8 +1872,7 @@ class QemuVM(BaseNode): # special case, sata controller doesn't exist in Qemu options.extend(["-device", 'ahci,id=ahci{}'.format(disk_index)]) options.extend(["-drive", 'file={},if=none,id=drive{},index={},media=disk{}'.format(disk, disk_index, disk_index, extra_drive_options)]) - qemu_version = await self.manager.get_qemu_version(self.qemu_path) - if qemu_version and parse_version(qemu_version) >= parse_version("4.2.0"): + if self._qemu_version and parse_version(self._qemu_version) >= parse_version("4.2.0"): # The ‘ide-drive’ device is deprecated since version 4.2.0 # https://qemu.readthedocs.io/en/latest/system/deprecated.html#ide-drive-since-4-2 options.extend(["-device", 'ide-hd,drive=drive{},bus=ahci{}.0,id=drive{}'.format(disk_index, disk_index, disk_index)]) @@ -2131,11 +2131,10 @@ class QemuVM(BaseNode): patched_qemu = False if self._legacy_networking: - qemu_version = await self.manager.get_qemu_version(self.qemu_path) - if qemu_version: - if parse_version(qemu_version) >= parse_version("2.9.0"): + if self._qemu_version: + if parse_version(self._qemu_version) >= parse_version("2.9.0"): raise QemuError("Qemu version 2.9.0 and later doesn't support legacy networking mode") - if parse_version(qemu_version) < parse_version("1.1.0"): + if parse_version(self._qemu_version) < parse_version("1.1.0"): # this is a patched Qemu if version is below 1.1.0 patched_qemu = True @@ -2144,8 +2143,7 @@ class QemuVM(BaseNode): pci_bridges = math.floor(pci_devices / 32) pci_bridges_created = 0 if pci_bridges >= 1: - qemu_version = await self.manager.get_qemu_version(self.qemu_path) - if qemu_version and parse_version(qemu_version) < parse_version("2.4.0"): + if self._qemu_version and parse_version(self._qemu_version) < parse_version("2.4.0"): raise QemuError("Qemu version 2.4 or later is required to run this VM with a large number of network adapters") pci_device_id = 4 + pci_bridges # Bridge consume PCI ports @@ -2191,7 +2189,9 @@ class QemuVM(BaseNode): else: # newer QEMU networking syntax device_string = "{},mac={}".format(adapter_type, mac) - if adapter_type == "virtio-net-pci": + if adapter_type == "virtio-net-pci" and \ + self._qemu_version and parse_version(self._qemu_version) >= parse_version("2.12"): + # speed and duplex support was added in Qemu 2.12 device_string = "{},speed=10000,duplex=full".format(device_string) bridge_id = math.floor(pci_device_id / 32) if bridge_id > 0: @@ -2224,8 +2224,7 @@ class QemuVM(BaseNode): if any(opt in self._options for opt in ["-display", "-nographic", "-curses", "-sdl" "-spice", "-vnc"]): return [] - version = await self.manager.get_qemu_version(self.qemu_path) - if version and parse_version(version) >= parse_version("3.0"): + if self._qemu_version and parse_version(self._qemu_version) >= parse_version("3.0"): return ["-display", "none"] else: return ["-nographic"] @@ -2270,9 +2269,8 @@ class QemuVM(BaseNode): elif sys.platform.startswith("win"): if require_hardware_accel: # HAXM is only available starting with Qemu version 2.9.0 - version = await self.manager.get_qemu_version(self.qemu_path) - if version and parse_version(version) < parse_version("2.9.0"): - raise QemuError("HAXM acceleration can only be enable for Qemu version 2.9.0 and above (current version: {})".format(version)) + if self._qemu_version and parse_version(self._qemu_version) < parse_version("2.9.0"): + raise QemuError("HAXM acceleration can only be enable for Qemu version 2.9.0 and above (current version: {})".format(self._qemu_version)) # check if HAXM is installed version = self.manager.get_haxm_windows_version() @@ -2372,6 +2370,7 @@ class QemuVM(BaseNode): (to be passed to subprocess.Popen()) """ + self._qemu_version = await self.manager.get_qemu_version(self.qemu_path) vm_name = self._name.replace(",", ",,") project_path = self.project.path.replace(",", ",,") additional_options = self._options.strip() @@ -2389,10 +2388,9 @@ class QemuVM(BaseNode): if await self._run_with_hardware_acceleration(self.qemu_path, self._options): if sys.platform.startswith("linux"): command.extend(["-enable-kvm"]) - version = await self.manager.get_qemu_version(self.qemu_path) # Issue on some combo Intel CPU + KVM + Qemu 2.4.0 # https://github.com/GNS3/gns3-server/issues/685 - if version and parse_version(version) >= parse_version("2.4.0") and self.platform == "x86_64": + if self._qemu_version and parse_version(self._qemu_version) >= parse_version("2.4.0") and self.platform == "x86_64": command.extend(["-machine", "smm=off"]) elif sys.platform.startswith("win") or sys.platform.startswith("darwin"): command.extend(["-enable-hax"]) diff --git a/tests/compute/qemu/test_qemu_vm.py b/tests/compute/qemu/test_qemu_vm.py index f9447f9e..dcd4eac0 100644 --- a/tests/compute/qemu/test_qemu_vm.py +++ b/tests/compute/qemu/test_qemu_vm.py @@ -78,6 +78,7 @@ async def vm(compute_project, manager, fake_qemu_binary, fake_qemu_img_binary): vm._start_ubridge = AsyncioMagicMock() vm._ubridge_hypervisor = MagicMock() vm._ubridge_hypervisor.is_running.return_value = True + vm.manager.get_qemu_version = AsyncioMagicMock(return_value="6.2.0") vm.manager.config.set("Qemu", "enable_hardware_acceleration", False) return vm @@ -141,7 +142,6 @@ async def test_is_running(vm, running_subprocess_mock): async def test_start(vm, running_subprocess_mock): - vm.manager.get_qemu_version = AsyncioMagicMock(return_value="3.1.0") with asyncio_patch("gns3server.compute.qemu.QemuVM.start_wrap_console"): with asyncio_patch("asyncio.create_subprocess_exec", return_value=running_subprocess_mock) as mock: await vm.start() @@ -156,7 +156,6 @@ async def test_stop(vm, running_subprocess_mock): future = asyncio.Future() future.set_result(True) process.wait.return_value = future - vm.manager.get_qemu_version = AsyncioMagicMock(return_value="3.1.0") with asyncio_patch("gns3server.compute.qemu.QemuVM.start_wrap_console"): with asyncio_patch("asyncio.create_subprocess_exec", return_value=process): @@ -241,7 +240,6 @@ async def test_port_remove_nio_binding(vm): async def test_close(vm, port_manager): - vm.manager.get_qemu_version = AsyncioMagicMock(return_value="3.1.0") with asyncio_patch("gns3server.compute.qemu.QemuVM.start_wrap_console"): with asyncio_patch("asyncio.create_subprocess_exec", return_value=MagicMock()): await vm.start() @@ -369,7 +367,6 @@ async def test_disk_options(vm, tmpdir, fake_qemu_img_binary): async def test_cdrom_option(vm, tmpdir, fake_qemu_img_binary): - vm.manager.get_qemu_version = AsyncioMagicMock(return_value="3.1.0") vm._cdrom_image = str(tmpdir / "test.iso") open(vm._cdrom_image, "w+").close() @@ -380,7 +377,6 @@ async def test_cdrom_option(vm, tmpdir, fake_qemu_img_binary): async def test_bios_option(vm, tmpdir, fake_qemu_img_binary): - vm.manager.get_qemu_version = AsyncioMagicMock(return_value="3.1.0") vm._bios_image = str(tmpdir / "test.img") open(vm._bios_image, "w+").close() options = await vm._build_command() @@ -390,7 +386,6 @@ async def test_bios_option(vm, tmpdir, fake_qemu_img_binary): @pytest.mark.skipif(sys.platform.startswith("win"), reason="Test not working on Windows") async def test_uefi_boot_mode_option(vm, tmpdir, images_dir, fake_qemu_img_binary): - vm.manager.get_qemu_version = AsyncioMagicMock(return_value="3.1.0") vm._uefi = True # create fake OVMF files @@ -408,7 +403,6 @@ async def test_uefi_boot_mode_option(vm, tmpdir, images_dir, fake_qemu_img_binar async def test_uefi_with_bios_image_already_configured(vm, tmpdir, fake_qemu_img_binary): - vm.manager.get_qemu_version = AsyncioMagicMock(return_value="3.1.0") vm._bios_image = str(tmpdir / "test.img") vm._uefi = True with pytest.raises(QemuError): @@ -434,7 +428,6 @@ async def test_spice_option(vm, fake_qemu_img_binary): async def test_tpm_option(vm, tmpdir, fake_qemu_img_binary): - vm.manager.get_qemu_version = AsyncioMagicMock(return_value="3.1.0") vm._tpm = True tpm_sock = os.path.join(vm.temporary_directory, "swtpm.sock") with patch("os.path.exists", return_value=True) as os_path: @@ -528,7 +521,6 @@ async def test_control_vm_expect_text(vm, running_subprocess_mock): async def test_build_command(vm, fake_qemu_binary): - vm.manager.get_qemu_version = AsyncioMagicMock(return_value="3.1.0") os.environ["DISPLAY"] = "0:0" with asyncio_patch("asyncio.create_subprocess_exec", return_value=MagicMock()): cmd = await vm._build_command() @@ -563,7 +555,6 @@ async def test_build_command_manual_uuid(vm): If user has set a uuid we keep it """ - vm.manager.get_qemu_version = AsyncioMagicMock(return_value="3.1.0") vm.options = "-uuid e1c307a4-896f-11e6-81a5-3c07547807cc" os.environ["DISPLAY"] = "0:0" with asyncio_patch("asyncio.create_subprocess_exec", return_value=MagicMock()): @@ -698,7 +689,6 @@ async def test_build_command_two_adapters_mac_address(vm): Should support multiple base vmac address """ - vm.manager.get_qemu_version = AsyncioMagicMock(return_value="2.5.0") vm.adapters = 2 vm.mac_address = "00:00:ab:0e:0f:09" mac_0 = vm._mac_address @@ -723,12 +713,9 @@ async def test_build_command_two_adapters_mac_address(vm): async def test_build_command_large_number_of_adapters(vm): """ When we have more than 28 interface we need to add a pci bridge for - additional interfaces + additional interfaces (supported only with Qemu 2.4 and later) """ - # It's supported only with Qemu 2.4 and later - vm.manager.get_qemu_version = AsyncioMagicMock(return_value="2.4.0") - vm.adapters = 100 vm.mac_address = "00:00:ab:0e:0f:09" mac_0 = vm._mac_address @@ -770,7 +757,6 @@ async def test_build_command_with_virtio_net_pci_adapter(vm): Test virtio-net-pci adapter which has parameters speed=1000 & duplex=full hard-coded """ - vm.manager.get_qemu_version = AsyncioMagicMock(return_value="2.4.0") vm.adapters = 1 vm.mac_address = "00:00:ab:0e:0f:09" vm._adapter_type = "virtio-net-pci"