mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-04 22:41:12 +02:00
feat(controller): offer QEMU the kernel datapath, gated per compute
Kernel eligibility becomes "both endpoints on one compute, each able to anchor": Docker always (adapters are born as veth pairs), QEMU when that compute's uBridge can create the persistent TAPs its adapters anchor on. The question is asked per compute, because the link needs both ends anchored -- so a mixed or half-upgraded deployment degrades to the relay instead of failing an attach. probe_tap_support() creates and deletes one throwaway persistent TAP through a scratch uBridge, cached by binary identity like the tc report; /capabilities exposes `ubridge_tap` (absent = unknown, which keeps links on the relay). The compute route for a QEMU adapter NIO took UDPNIO only, so a kernel link was rejected with 422: its body and response now accept Union[UDPNIO, BridgeNIO] exactly like Docker's. Stale "Docker-to-Docker" wording in the config, link and MCP descriptions now talks about anchors, and docs/features/qemu-kernel-datapath.md documents the datapath -- verified end-to-end on a real instance: 32/32 kernel-datapath checks and 29/29 on the relay variant.
This commit is contained in:
15 files changed
+444
-40
No files matched your search
@@ -381,7 +381,7 @@ async def test_json(compute):
|
||||
"disk_usage_percent": 0,
|
||||
"connected": True,
|
||||
"last_error": None,
|
||||
"capabilities": {"version": "", "platform": "", "cpus": 0, "memory": 0, "disk_size": 0, "node_types": [], "ubridge_tc": None},
|
||||
"capabilities": {"version": "", "platform": "", "cpus": 0, "memory": 0, "disk_size": 0, "node_types": [], "ubridge_tc": None, "ubridge_tap": None},
|
||||
}
|
||||
assert compute.asdict(topology_dump=True) == {
|
||||
"compute_id": "my_compute_id",
|
||||
|
||||
@@ -90,18 +90,101 @@ async def test_kernel_datapath_eligible(project):
|
||||
assert link._kernel_datapath_eligible(node1, node2) is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_kernel_datapath_not_eligible_for_other_node_types(project):
|
||||
|
||||
def _tap_capable_compute(tap_support=True, compute_id="compute-1"):
|
||||
compute = MagicMock()
|
||||
compute.id = "compute-1"
|
||||
node1 = _node(project, compute, "docker1")
|
||||
node2 = _node(project, compute, "qemu1", node_type="qemu")
|
||||
compute.id = compute_id
|
||||
compute.capabilities = {"ubridge_tap": tap_support}
|
||||
return compute
|
||||
|
||||
|
||||
async def _link(project, node1, node2):
|
||||
link = UDPLink(project)
|
||||
await link.add_node(node1, 0, 0, batch=True, dump=False)
|
||||
await link.add_node(node2, 0, 0, batch=True, dump=False)
|
||||
return link
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_kernel_datapath_not_eligible_for_other_node_types(project):
|
||||
"""
|
||||
A node type with no anchor of its own (IOU, VPCS, cloud, ...) keeps the
|
||||
relay however capable the compute is — and a QEMU node on a compute whose
|
||||
uBridge cannot create persistent TAPs does too, because it runs on the
|
||||
legacy relay datapath there.
|
||||
"""
|
||||
|
||||
compute = _tap_capable_compute(tap_support=None)
|
||||
node1 = _node(project, compute, "docker1")
|
||||
node2 = _node(project, compute, "qemu1", node_type="qemu")
|
||||
link = await _link(project, node1, node2)
|
||||
assert link._kernel_datapath_eligible(node1, node2) is False
|
||||
|
||||
compute = _tap_capable_compute()
|
||||
node1 = _node(project, compute, "iou1", node_type="iou")
|
||||
node2 = _node(project, compute, "docker1")
|
||||
link = await _link(project, node1, node2)
|
||||
assert link._kernel_datapath_eligible(node1, node2) is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_kernel_datapath_eligible_for_qemu_with_tap_support(project):
|
||||
"""
|
||||
QEMU adapters anchor on a persistent TAP, so a compute whose uBridge has
|
||||
the tap module gets kernel links too — including the mixed docker-to-qemu
|
||||
case (both anchors end up in the same per-link kernel bridge).
|
||||
"""
|
||||
|
||||
compute = _tap_capable_compute()
|
||||
node1 = _node(project, compute, "qemu1", node_type="qemu")
|
||||
node2 = _node(project, compute, "qemu2", node_type="qemu")
|
||||
link = await _link(project, node1, node2)
|
||||
assert link._kernel_datapath_eligible(node1, node2) is True
|
||||
|
||||
node2 = _node(project, compute, "docker1")
|
||||
link = await _link(project, node1, node2)
|
||||
assert link._kernel_datapath_eligible(node1, node2) is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_kernel_datapath_capability_is_asked_per_compute(project):
|
||||
"""
|
||||
A QEMU node on a compute without the tap module keeps the link on the
|
||||
relay even when its peer sits on a capable compute — the link needs both
|
||||
endpoints anchored, so the capability is a two-end AND.
|
||||
"""
|
||||
|
||||
capable = _tap_capable_compute(compute_id="compute-1")
|
||||
plain = _tap_capable_compute(tap_support=None, compute_id="compute-2")
|
||||
node1 = _node(project, capable, "qemu1", node_type="qemu")
|
||||
node2 = _node(project, plain, "qemu2", node_type="qemu")
|
||||
link = await _link(project, node1, node2)
|
||||
assert link._kernel_datapath_eligible(node1, node2) is False
|
||||
|
||||
# same compute, but the compute cannot anchor: still the relay
|
||||
node2 = _node(project, _tap_capable_compute(tap_support=None), "qemu2", node_type="qemu")
|
||||
link = await _link(project, node1, node2)
|
||||
assert link._kernel_datapath_eligible(node1, node2) is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_prepare_qemu_kernel_link_emits_bridge_nios(project):
|
||||
"""
|
||||
The prepared link data is the same NIOBridge spec Docker links use — no
|
||||
UDP ports, no peer resolution — so nothing else in the controller has to
|
||||
know which node type the endpoint is.
|
||||
"""
|
||||
|
||||
compute = _tap_capable_compute()
|
||||
node1 = _node(project, compute, "qemu1", node_type="qemu")
|
||||
node2 = _node(project, compute, "qemu2", node_type="qemu")
|
||||
link = await _link(project, node1, node2)
|
||||
|
||||
await link._prepare()
|
||||
|
||||
assert link.kernel_datapath is True
|
||||
assert [d["type"] for d in link._link_data] == ["nio_bridge", "nio_bridge"]
|
||||
assert link._link_data[0]["bridge"] == link._link_data[1]["bridge"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_kernel_datapath_not_eligible_across_computes(project):
|
||||
|
||||
Reference in new issue
Block a user