diff --git a/gns3server/compute/qemu/qemu_vm.py b/gns3server/compute/qemu/qemu_vm.py index 9ac7eb21..fd9e9360 100644 --- a/gns3server/compute/qemu/qemu_vm.py +++ b/gns3server/compute/qemu/qemu_vm.py @@ -23,12 +23,13 @@ order to run a QEMU VM. import sys import os import re +import math import shutil -import subprocess import shlex import asyncio import socket import gns3server +import subprocess from gns3server.utils import parse_version from .qemu_error import QemuError @@ -1446,6 +1447,14 @@ class QemuVM(BaseNode): # this is a patched Qemu if version is below 1.1.0 patched_qemu = True + # Each 32 PCI device we need to add a PCI bridge with max 9 bridges + pci_devices = 4 + len(self._ethernet_adapters) # 4 PCI devices are use by default by qemu + bridge_id = 0 + for bridge_id in range(1, math.floor(pci_devices / 32) + 1): + network_options.extend(["-device", "i82801b11-bridge,id=dmi_pci_bridge{bridge_id}".format(bridge_id=bridge_id)]) + network_options.extend(["-device", "pci-bridge,id=pci-bridge{bridge_id},bus=dmi_pci_bridge{bridge_id},chassis_nr=0x1,addr=0x{bridge_id},shpc=off".format(bridge_id=bridge_id)]) + + pci_device_id = 4 + bridge_id # Bridge consume PCI ports for adapter_number, adapter in enumerate(self._ethernet_adapters): mac = int_to_macaddress(macaddress_to_int(self._mac_address) + adapter_number) @@ -1483,8 +1492,14 @@ class QemuVM(BaseNode): else: # newer QEMU networking syntax + device_string = "{},mac={}".format(self._adapter_type, mac) + bridge_id = math.floor(pci_device_id / 32) + if bridge_id > 0: + addr = pci_device_id % 32 + device_string = "{},bus=pci-bridge{bridge_id},addr=0x{addr:02x}".format(device_string, bridge_id=bridge_id, addr=addr) + pci_device_id += 1 if nio: - network_options.extend(["-device", "{},mac={},netdev=gns3-{}".format(self._adapter_type, mac, adapter_number)]) + network_options.extend(["-device", "{},netdev=gns3-{}".format(device_string, adapter_number)]) if isinstance(nio, NIOUDP): network_options.extend(["-netdev", "socket,id=gns3-{},udp={}:{},localaddr={}:{}".format(adapter_number, nio.rhost, @@ -1494,7 +1509,7 @@ class QemuVM(BaseNode): elif isinstance(nio, NIOTAP): network_options.extend(["-netdev", "tap,id=gns3-{},ifname={},script=no,downscript=no".format(adapter_number, nio.tap_device)]) else: - network_options.extend(["-device", "{},mac={}".format(self._adapter_type, mac)]) + network_options.extend(["-device", device_string]) return network_options diff --git a/gns3server/schemas/qemu.py b/gns3server/schemas/qemu.py index 36e2fa6b..aa16d594 100644 --- a/gns3server/schemas/qemu.py +++ b/gns3server/schemas/qemu.py @@ -147,7 +147,7 @@ QEMU_CREATE_SCHEMA = { "description": "Number of adapters", "type": ["integer", "null"], "minimum": 0, - "maximum": 32, + "maximum": 275, }, "adapter_type": { "description": "QEMU adapter type", @@ -332,7 +332,7 @@ QEMU_UPDATE_SCHEMA = { "description": "Number of adapters", "type": ["integer", "null"], "minimum": 0, - "maximum": 32, + "maximum": 275, }, "adapter_type": { "description": "QEMU adapter type", @@ -520,7 +520,7 @@ QEMU_OBJECT_SCHEMA = { "description": "Number of adapters", "type": "integer", "minimum": 0, - "maximum": 32, + "maximum": 275, }, "adapter_type": { "description": "QEMU adapter type", diff --git a/tests/compute/qemu/test_qemu_vm.py b/tests/compute/qemu/test_qemu_vm.py index 8b01381d..91e40d9c 100644 --- a/tests/compute/qemu/test_qemu_vm.py +++ b/tests/compute/qemu/test_qemu_vm.py @@ -588,7 +588,7 @@ def test_build_command_two_adapters_mac_address(vm, loop, fake_qemu_binary, port vm.adapters = 2 vm.mac_address = "00:00:ab:0e:0f:09" mac_0 = vm._mac_address - mac_1 = int_to_macaddress(macaddress_to_int(vm._mac_address)) + mac_1 = int_to_macaddress(macaddress_to_int(vm._mac_address) + 1) assert mac_0[:8] == "00:00:ab" with asyncio_patch("asyncio.create_subprocess_exec", return_value=MagicMock()) as process: cmd = loop.run_until_complete(asyncio.async(vm._build_command())) @@ -605,6 +605,37 @@ def test_build_command_two_adapters_mac_address(vm, loop, fake_qemu_binary, port assert "e1000,mac={}".format(mac_1) in cmd +def test_build_command_large_number_of_adapters(vm, loop, fake_qemu_binary, port_manager): + """ + When we have more than 28 interface we need to add a pci bridge for + additionnal interfaces + """ + + vm.adapters = 100 + vm.mac_address = "00:00:ab:0e:0f:09" + mac_0 = vm._mac_address + mac_1 = int_to_macaddress(macaddress_to_int(vm._mac_address) + 1) + assert mac_0[:8] == "00:00:ab" + with asyncio_patch("asyncio.create_subprocess_exec", return_value=MagicMock()) as process: + cmd = loop.run_until_complete(asyncio.async(vm._build_command())) + + assert "e1000,mac={}".format(mac_0) in cmd + assert "e1000,mac={}".format(mac_1) in cmd + assert "pci-bridge,id=pci-bridge0,bus=dmi_pci_bridge0,chassis_nr=0x1,addr=0x0,shpc=off" not in cmd + assert "pci-bridge,id=pci-bridge1,bus=dmi_pci_bridge1,chassis_nr=0x1,addr=0x1,shpc=off" in cmd + assert "pci-bridge,id=pci-bridge2,bus=dmi_pci_bridge2,chassis_nr=0x1,addr=0x2,shpc=off" in cmd + assert "i82801b11-bridge,id=dmi_pci_bridge1" in cmd + + print(cmd) + + mac_29 = int_to_macaddress(macaddress_to_int(vm._mac_address) + 29) + assert "e1000,mac={},bus=pci-bridge1,addr=0x04".format(mac_29) in cmd + mac_30 = int_to_macaddress(macaddress_to_int(vm._mac_address) + 30) + assert "e1000,mac={},bus=pci-bridge1,addr=0x05".format(mac_30) in cmd + mac_74 = int_to_macaddress(macaddress_to_int(vm._mac_address) + 74) + assert "e1000,mac={},bus=pci-bridge2,addr=0x11".format(mac_74) in cmd + + # Windows accept this kind of mistake @pytest.mark.skipif(sys.platform.startswith("win"), reason="Not supported on Windows") def test_build_command_with_invalid_options(vm, loop, fake_qemu_binary):