From b0567772f77b5fca10f7a0dc874a907f77119fc9 Mon Sep 17 00:00:00 2001
From: Julien Duponchelle <julien@gns3.net>
Date: Tue, 7 Feb 2017 17:04:29 +0100
Subject: [PATCH 1/3] Allow up to 275 adapters for qemu

See #889 for more details
---
 gns3server/compute/qemu/qemu_vm.py | 21 ++++++++++++++++---
 gns3server/schemas/qemu.py         |  6 +++---
 tests/compute/qemu/test_qemu_vm.py | 33 +++++++++++++++++++++++++++++-
 3 files changed, 53 insertions(+), 7 deletions(-)

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):

From 99f817392db82ae2f383748c7bc7a8d0d98a4d68 Mon Sep 17 00:00:00 2001
From: Julien Duponchelle <julien@gns3.net>
Date: Fri, 17 Feb 2017 09:55:50 +0100
Subject: [PATCH 2/3] Raise an error if you use Qemu < 2.4 and try to use large
 number of adapters

---
 gns3server/compute/qemu/qemu_vm.py |  6 ++++++
 tests/compute/qemu/test_qemu_vm.py | 15 +++++++++++++--
 2 files changed, 19 insertions(+), 2 deletions(-)

diff --git a/gns3server/compute/qemu/qemu_vm.py b/gns3server/compute/qemu/qemu_vm.py
index fd9e9360..30c4d061 100644
--- a/gns3server/compute/qemu/qemu_vm.py
+++ b/gns3server/compute/qemu/qemu_vm.py
@@ -1454,7 +1454,13 @@ class QemuVM(BaseNode):
             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)])
 
+        if bridge_id > 1:
+            qemu_version = yield from self.manager.get_qemu_version(self.qemu_path)
+            if qemu_version and parse_version(qemu_version) < parse_version("2.4.0"):
+                raise QemuError("You need to Qemu 2.4 or later in order to support large number of adapters")
+
         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)
 
diff --git a/tests/compute/qemu/test_qemu_vm.py b/tests/compute/qemu/test_qemu_vm.py
index 91e40d9c..854f47e4 100644
--- a/tests/compute/qemu/test_qemu_vm.py
+++ b/tests/compute/qemu/test_qemu_vm.py
@@ -611,6 +611,9 @@ def test_build_command_large_number_of_adapters(vm, loop, fake_qemu_binary, port
     additionnal interfaces
     """
 
+    # 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
@@ -626,8 +629,6 @@ def test_build_command_large_number_of_adapters(vm, loop, fake_qemu_binary, port
     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)
@@ -635,8 +636,18 @@ def test_build_command_large_number_of_adapters(vm, loop, fake_qemu_binary, port
     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
 
+    # Qemu < 2.4 doesn't support large number of adapters
+    vm.manager.get_qemu_version = AsyncioMagicMock(return_value="2.0.0")
+    with pytest.raises(QemuError):
+        with asyncio_patch("asyncio.create_subprocess_exec", return_value=MagicMock()) as process:
+            cmd = loop.run_until_complete(asyncio.async(vm._build_command()))
+    vm.adapters = 5
+    with asyncio_patch("asyncio.create_subprocess_exec", return_value=MagicMock()) as process:
+        cmd = loop.run_until_complete(asyncio.async(vm._build_command()))
 
 # 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):
 

From 635e163954a6141e5e49fc15436ccfb812ef0f52 Mon Sep 17 00:00:00 2001
From: Jeremy Grossmann <grossmj@gns3.net>
Date: Fri, 17 Feb 2017 17:37:06 +0800
Subject: [PATCH 3/3] Update qemu_vm.py

---
 gns3server/compute/qemu/qemu_vm.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gns3server/compute/qemu/qemu_vm.py b/gns3server/compute/qemu/qemu_vm.py
index 30c4d061..bfe2f2bf 100644
--- a/gns3server/compute/qemu/qemu_vm.py
+++ b/gns3server/compute/qemu/qemu_vm.py
@@ -1457,7 +1457,7 @@ class QemuVM(BaseNode):
         if bridge_id > 1:
             qemu_version = yield from self.manager.get_qemu_version(self.qemu_path)
             if qemu_version and parse_version(qemu_version) < parse_version("2.4.0"):
-                raise QemuError("You need to Qemu 2.4 or later in order to support large number of adapters")
+                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 + bridge_id  # Bridge consume PCI ports