feat: add QEMU support documentation and enforce minimum version requirement

This commit is contained in:
grossmj 2026-10-20 18:01:25 +07:00
parent aab5432a5f
commit a82b06d676
No known key found for this signature in database
GPG Key ID: 1E7DD6DBB53FF3D7
3 changed files with 43 additions and 75 deletions

28
docs/dependencies/qemu.md Normal file
View File

@ -0,0 +1,28 @@
# QEMU support
GNS3 server requires QEMU 6.2 or newer. This is the minimum QEMU version that the server actively supports on Linux.
For compatibility checks, the project evaluates the Linux distributions that are commonly used in server deployments and that still have supported releases: CentOS Stream, Debian, Fedora, openSUSE, RHEL, SLES, and Ubuntu LTS. Other distributions are assumed to ship similar package versions and are treated by the same rule: if the distro ships QEMU 6.2 or newer, it is considered compatible; if it ships an older version, it is not supported by GNS3 server.
This document reflects the currently supported release set as of October 2026, based on the active releases listed by the major distro end-of-life trackers and package versions shipped by those releases.
## Minimum supported version
- Minimum supported QEMU version: 6.2
- QEMU 6.2 was first shipped in Ubuntu 22.04 LTS and is the baseline used to determine whether a Linux distribution is suitable for GNS3 server.
- Distributions shipping QEMU 6.2 or later are generally acceptable.
- Older package versions are considered unsupported by policy.
## Currently supported Linux releases and shipped QEMU versions
The following supported releases are the current reference points used by the project. They are based on the active release lists published by the distributions' end-of-life trackers and the QEMU versions typically shipped by those releases.
| Distribution | Current supported OS release(s) | QEMU version shipped | Source |
| --- | --- | --- | --- |
| Ubuntu LTS | 24.04 LTS, 22.04 LTS | 8.2 for 24.04, 6.2 for 22.04 | https://endoflife.date/ubuntu |
| Debian | 13 (trixie), 12 (bookworm) | 9.x for 13, 7.2 for 12 | https://endoflife.date/debian |
| Fedora | 42, 41, 40 | 9.x | https://endoflife.date/fedora |
| openSUSE | Leap 15.6, Tumbleweed | 8.x to 9.x | https://endoflife.date/opensuse |
| CentOS Stream | 10, 9 | 9.x / 8.x | https://endoflife.date/centos |
| RHEL | 10, 9.5, 9.4 | 9.x / 8.x | https://endoflife.date/redhat-enterprise-linux |
| SLES | 15 SP7, 15 SP6 | 8.x | https://endoflife.date/sles |

View File

@ -2316,16 +2316,7 @@ class QemuVM(KernelDatapathMixin, BaseNode):
f"file={disk},if=none,id=drive{disk_index},index={disk_index},media=disk{extra_drive_options}", f"file={disk},if=none,id=drive{disk_index},index={disk_index},media=disk{extra_drive_options}",
] ]
) )
if self._qemu_version and parse_version(self._qemu_version) >= parse_version("4.2.0"): options.extend(["-device", f"ide-hd,drive=drive{disk_index},bus=ahci{disk_index}.0,id=drive{disk_index}"])
# 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", f"ide-hd,drive=drive{disk_index},bus=ahci{disk_index}.0,id=drive{disk_index}"]
)
else:
options.extend(
["-device", f"ide-drive,drive=drive{disk_index},bus=ahci{disk_index}.0,id=drive{disk_index}"]
)
elif interface == "nvme": elif interface == "nvme":
options.extend( options.extend(
[ [
@ -2721,15 +2712,12 @@ class QemuVM(KernelDatapathMixin, BaseNode):
async def _disable_graphics(self): async def _disable_graphics(self):
""" """
Disable graphics depending of the QEMU version Disable graphics for Qemu if no graphics options are specified.
""" """
if any(opt in self._options for opt in ["-display", "-nographic", "-curses", "-sdl-spice", "-vnc"]): if any(opt in self._options for opt in ["-display", "-nographic", "-curses", "-sdl-spice", "-vnc"]):
return [] return []
if self._qemu_version and parse_version(self._qemu_version) >= parse_version("3.0"): return ["-display", "none"]
return ["-display", "none"]
else:
return ["-nographic"]
async def _run_with_hardware_acceleration(self, qemu_path, options): async def _run_with_hardware_acceleration(self, qemu_path, options):
""" """
@ -2854,8 +2842,8 @@ class QemuVM(KernelDatapathMixin, BaseNode):
""" """
self._qemu_version = await self.manager.get_qemu_version(self.qemu_path) self._qemu_version = await self.manager.get_qemu_version(self.qemu_path)
if self._qemu_version and parse_version(self._qemu_version) < parse_version("2.4.0"): if self._qemu_version and parse_version(self._qemu_version) < parse_version("6.2.0"):
raise QemuError("Qemu version 2.4 or later is required to run Qemu VMs") raise QemuError("Qemu version 6.2 or later is required to run Qemu VMs")
vm_name = self._name.replace(",", ",,") vm_name = self._name.replace(",", ",,")
project_path = self.project.path.replace(",", ",,") project_path = self.project.path.replace(",", ",,")
additional_options = self._options.strip() additional_options = self._options.strip()
@ -2877,14 +2865,6 @@ class QemuVM(KernelDatapathMixin, BaseNode):
if await self._run_with_hardware_acceleration(self.qemu_path, self._options): if await self._run_with_hardware_acceleration(self.qemu_path, self._options):
if sys.platform.startswith("linux"): if sys.platform.startswith("linux"):
command.extend(["-enable-kvm"]) command.extend(["-enable-kvm"])
# Issue on some combo Intel CPU + KVM + Qemu 2.4.0
# https://github.com/GNS3/gns3-server/issues/685
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("darwin"): elif sys.platform.startswith("darwin"):
command.extend(["-enable-hax"]) command.extend(["-enable-hax"])
command.extend(["-boot", f"order={self._boot_priority}"]) command.extend(["-boot", f"order={self._boot_priority}"])

View File

@ -645,7 +645,7 @@ async def test_build_command_console_none(vm):
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_build_command_manual_uuid(vm): async def test_build_command_manual_uuid(vm):
""" """
If user has set a uuid we keep it If user has set a UUID we keep it
""" """
vm.options = "-uuid e1c307a4-896f-11e6-81a5-3c07547807cc" vm.options = "-uuid e1c307a4-896f-11e6-81a5-3c07547807cc"
@ -657,75 +657,34 @@ async def test_build_command_manual_uuid(vm):
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_build_command_kvm_below_2_4(linux_platform, vm, fake_qemu_binary): async def test_build_command_kvm_below_6_2(linux_platform, vm, fake_qemu_binary):
""" """
Qemu 2.4 introduce an issue with KVM Qemu 2.4 introduce an issue with KVM
""" """
vm.manager.get_qemu_version = AsyncioMagicMock(return_value="2.3.2") vm.manager.get_qemu_version = AsyncioMagicMock(return_value="6.1.0")
with asyncio_patch("gns3server.compute.qemu.qemu_vm.QemuVM._run_with_hardware_acceleration", return_value=True): with asyncio_patch("gns3server.compute.qemu.qemu_vm.QemuVM._run_with_hardware_acceleration", return_value=True):
with asyncio_patch("asyncio.create_subprocess_exec", return_value=MagicMock()): with asyncio_patch("asyncio.create_subprocess_exec", return_value=MagicMock()):
with pytest.raises(QemuError): with pytest.raises(QemuError):
await vm._build_command() await vm._build_command()
@pytest.mark.asyncio
async def test_build_command_kvm_2_4(linux_platform, vm, fake_qemu_binary):
"""
Qemu 2.4 introduce an issue with KVM
"""
vm.manager.get_qemu_version = AsyncioMagicMock(return_value="2.4.2")
os.environ["DISPLAY"] = "0:0"
with asyncio_patch("gns3server.compute.qemu.qemu_vm.QemuVM._run_with_hardware_acceleration", return_value=True):
with asyncio_patch("asyncio.create_subprocess_exec", return_value=MagicMock()) as process:
cmd = await vm._build_command()
nio = vm._local_udp_tunnels[0][0]
assert cmd == [
fake_qemu_binary,
"-name",
"test",
"-m",
"256M",
"-smp",
"cpus=1,maxcpus=1,sockets=1",
"-enable-kvm",
"-machine",
"smm=off",
"-boot",
"order=c",
"-uuid",
vm.id,
"-serial",
f"telnet:127.0.0.1:{vm._internal_console_port},server,nowait",
"-net",
"none",
"-device",
"i82801b11-bridge,id=dmi_pci_bridge1",
"-device",
"pci-bridge,id=pci-bridge1,bus=dmi_pci_bridge1,chassis_nr=0x1,addr=0x1,shpc=off",
"-device",
f"e1000,mac={vm._mac_address},bus=pci-bridge1,addr=0x00,netdev=gns3-0",
"-netdev",
f"socket,id=gns3-0,udp=127.0.0.1:{nio.rport},localaddr=127.0.0.1:{nio.lport}",
"-nographic",
]
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_build_command_without_display(vm): async def test_build_command_without_display(vm):
vm.manager.get_qemu_version = AsyncioMagicMock(return_value="2.5.0") vm.manager.get_qemu_version = AsyncioMagicMock(return_value="6.2.0")
os.environ["DISPLAY"] = "" os.environ["DISPLAY"] = ""
with asyncio_patch("asyncio.create_subprocess_exec", return_value=MagicMock()): with asyncio_patch("asyncio.create_subprocess_exec", return_value=MagicMock()):
cmd = await vm._build_command() cmd = await vm._build_command()
assert "-nographic" in cmd print(cmd)
assert "-display" in cmd
assert "none" in cmd
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_build_command_two_adapters(vm, fake_qemu_binary): async def test_build_command_two_adapters(vm, fake_qemu_binary):
vm.manager.get_qemu_version = AsyncioMagicMock(return_value="2.5.0") vm.manager.get_qemu_version = AsyncioMagicMock(return_value="6.2.0")
os.environ["DISPLAY"] = "0:0" os.environ["DISPLAY"] = "0:0"
vm.adapters = 2 vm.adapters = 2
with asyncio_patch("asyncio.create_subprocess_exec", return_value=MagicMock()): with asyncio_patch("asyncio.create_subprocess_exec", return_value=MagicMock()):
@ -760,7 +719,8 @@ async def test_build_command_two_adapters(vm, fake_qemu_binary):
f"e1000,mac={int_to_macaddress(macaddress_to_int(vm._mac_address) + 1)},bus=pci-bridge1,addr=0x01,netdev=gns3-1", f"e1000,mac={int_to_macaddress(macaddress_to_int(vm._mac_address) + 1)},bus=pci-bridge1,addr=0x01,netdev=gns3-1",
"-netdev", "-netdev",
f"socket,id=gns3-1,udp=127.0.0.1:{nio2.rport},localaddr=127.0.0.1:{nio2.lport}", f"socket,id=gns3-1,udp=127.0.0.1:{nio2.rport},localaddr=127.0.0.1:{nio2.lport}",
"-nographic", "-display",
"none",
] ]