feat(controller): offer Dynamips the kernel datapath

A Dynamips Ethernet port can anchor a kernel link whenever its compute's
uBridge has the tap module — the anchors are uBridge-created TAPs the
hypervisor merely opens, the same capability QEMU gates on, so no
Dynamips-specific capability is needed. Serial, ATM and POS ports are
excluded per port by the existing link-type check (the controller's port
matrix decides), keeping them on the relay whatever the capabilities.

Eligibility tests cover the mixed pairs (docker/qemu/iou endpoints on the
same compute), the strict tap-capability requirement, and the serial
exclusion.
This commit is contained in:
YueGuobin 2026-10-01 11:55:20 +08:00
parent 7201a870da
commit e5f65a510a
No known key found for this signature in database
2 changed files with 75 additions and 3 deletions

View File

@ -182,9 +182,12 @@ class UDPLink(Link):
datapath and cannot carry a kernel link at all. IOU's Ethernet datapath and cannot carry a kernel link at all. IOU's Ethernet
bays anchor on persistent TAPs too, bound to its IOL fabric bays anchor on persistent TAPs too, bound to its IOL fabric
(``iol_bridge add_nio_tap``) — a separate capability, since it can (``iol_bridge add_nio_tap``) — a separate capability, since it can
be present or absent independently of the tap module. A kernel link be present or absent independently of the tap module. Dynamips
is an Ethernet segment, so non-Ethernet ports (IOU serial) stay on Ethernet slot ports anchor on persistent TAPs the hypervisor opens
the relay whatever the node's capabilities. (the tap module again — the Dynamips binary itself creates nothing),
so it gates like QEMU. A kernel link is an Ethernet segment, so
non-Ethernet ports (IOU serial, Dynamips serial/ATM/POS adapters)
stay on the relay whatever the node's capabilities.
""" """
if not Config.instance().settings.Server.enable_kernel_datapath: if not Config.instance().settings.Server.enable_kernel_datapath:
@ -214,6 +217,11 @@ class UDPLink(Link):
return capabilities.get("ubridge_tap") is True return capabilities.get("ubridge_tap") is True
if node.node_type == "iou": if node.node_type == "iou":
return capabilities.get("ubridge_iol_tap") is True return capabilities.get("ubridge_iol_tap") is True
if node.node_type == "dynamips":
# The anchors are uBridge-created TAPs the Dynamips hypervisor
# merely opens (nio create_tap) — the same tap-module capability
# QEMU needs, not a Dynamips-specific one.
return capabilities.get("ubridge_tap") is True
return False return False
async def _prepare(self): async def _prepare(self):

View File

@ -169,6 +169,70 @@ async def test_iou_eligibility_is_its_own_capability(project):
assert link._kernel_datapath_eligible(node1, node2) is False assert link._kernel_datapath_eligible(node1, node2) is False
@pytest.mark.asyncio
async def test_kernel_datapath_eligible_for_dynamips_with_tap_support(project):
"""
Dynamips Ethernet slot ports anchor on persistent TAPs the hypervisor
opens (nio create_tap on a uBridge-created device), so they gate on the
tap module like QEMU — no Dynamips-specific capability — and mix with
docker/qemu/iou endpoints on the same compute.
"""
compute = MagicMock()
compute.id = "compute-1"
compute.capabilities = {"ubridge_tap": True, "ubridge_iol_tap": True}
node1 = _node(project, compute, "r1", node_type="dynamips")
node2 = _node(project, compute, "r2", node_type="dynamips")
link = await _link(project, node1, node2)
assert link._kernel_datapath_eligible(node1, node2) is True
for peer in (
_node(project, compute, "docker1"),
_node(project, compute, "qemu1", node_type="qemu"),
_node(project, compute, "iou1", node_type="iou"),
):
link = await _link(project, node1, peer)
assert link._kernel_datapath_eligible(node1, peer) is True
@pytest.mark.asyncio
async def test_dynamips_eligibility_follows_the_tap_capability(project):
"""
A compute whose uBridge cannot create persistent TAPs keeps Dynamips on
the relay (the hypervisor would have nothing to open), and so does an
unreported/failed probe.
"""
node2 = _node(project, _tap_capable_compute(), "docker1")
for tap_support in (None, False):
compute = _tap_capable_compute(tap_support=tap_support)
node1 = _node(project, compute, "r1", node_type="dynamips")
link = await _link(project, node1, node2)
assert link._kernel_datapath_eligible(node1, node2) is False
@pytest.mark.asyncio
async def test_kernel_datapath_not_eligible_for_dynamips_serial_ports(project):
"""
A Dynamips serial port (NM-4T, WIC-2T, ...) stays on the relay whatever
the compute's capabilities — the same per-port exclusion as IOU serial,
decided by the controller's port matrix.
"""
from gns3server.controller.ports.serial_port import SerialPort
compute = MagicMock()
compute.id = "compute-1"
compute.capabilities = {"ubridge_tap": True}
node1 = _node(project, compute, "r1", node_type="dynamips")
node1._ports = [SerialPort("Serial0/0", 0, 0, 0)]
node2 = _node(project, compute, "r2", node_type="dynamips")
node2._ports = [SerialPort("Serial0/0", 0, 0, 0)]
link = await _link(project, node1, node2)
assert link._kernel_datapath_eligible(node1, node2) is False
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_kernel_datapath_not_eligible_for_serial_ports(project): async def test_kernel_datapath_not_eligible_for_serial_ports(project):
""" """