mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-03 05:51:15 +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
@@ -22,7 +22,7 @@ payload: spawn a throwaway uBridge, ask once, cache by binary identity.
|
||||
import pytest
|
||||
|
||||
from gns3server.compute.ubridge import tc_probe
|
||||
from gns3server.compute.ubridge.tc_probe import probe_tc_capabilities
|
||||
from gns3server.compute.ubridge.tc_probe import probe_tc_capabilities, probe_tap_support
|
||||
from gns3server.compute.ubridge.ubridge_error import UbridgeError
|
||||
|
||||
pytestmark = pytest.mark.asyncio
|
||||
@@ -31,6 +31,8 @@ pytestmark = pytest.mark.asyncio
|
||||
class FakeHypervisor:
|
||||
"""Records spawns; answers `tc capabilities` with a canned reply."""
|
||||
|
||||
last = None
|
||||
|
||||
reply = ["netem=delay,rate;ebpf=1;cbpf=1;ebpf_modes=nth,quota,window,flow"]
|
||||
error = None
|
||||
spawned = 0
|
||||
@@ -38,6 +40,7 @@ class FakeHypervisor:
|
||||
def __init__(self, project, path, working_dir, transport, host, node_id):
|
||||
|
||||
self.commands = []
|
||||
FakeHypervisor.last = self
|
||||
FakeHypervisor.spawned += 1
|
||||
|
||||
async def start(self):
|
||||
@@ -66,6 +69,8 @@ def probe_env(monkeypatch, tmp_path):
|
||||
FakeHypervisor.error = None
|
||||
FakeHypervisor.reply = ["netem=delay,rate;ebpf=1;cbpf=1;ebpf_modes=nth,quota,window,flow"]
|
||||
tc_probe._cache.clear()
|
||||
tc_probe._tap_cache.clear()
|
||||
FakeHypervisor.last = None
|
||||
# a real file on disk: the cache is keyed on its (path, mtime, size)
|
||||
binary = tmp_path / "ubridge"
|
||||
binary.write_bytes(b"")
|
||||
@@ -113,3 +118,28 @@ async def test_probe_empty_reply_means_unknown():
|
||||
|
||||
FakeHypervisor.reply = []
|
||||
assert await probe_tc_capabilities() is None
|
||||
|
||||
|
||||
async def test_probe_tap_support_creates_and_deletes_a_probe_tap():
|
||||
|
||||
assert await probe_tap_support() is True
|
||||
assert FakeHypervisor.spawned == 1
|
||||
commands = FakeHypervisor.last.commands
|
||||
assert commands[0].startswith("tap create ")
|
||||
assert commands[1].startswith("tap delete ")
|
||||
|
||||
|
||||
async def test_probe_tap_support_unknown_on_an_old_build():
|
||||
|
||||
FakeHypervisor.error = "202-Unknown command 'create'"
|
||||
assert await probe_tap_support() is None
|
||||
# cached like the tc report: no respawn on every /capabilities hit
|
||||
assert await probe_tap_support() is None
|
||||
assert FakeHypervisor.spawned == 1
|
||||
|
||||
|
||||
async def test_probe_tap_support_missing_binary(monkeypatch):
|
||||
|
||||
monkeypatch.setattr(tc_probe.shutil, "which", lambda name: None)
|
||||
assert await probe_tap_support() is None
|
||||
assert FakeHypervisor.spawned == 0
|
||||
Reference in new issue
Block a user