fix(mcp): let link_list return kernel_datapath

The tool descriptions have always advertised kernel_datapath as a
requestable field, but the handler's whitelist did not contain it: asking
for it was rejected as an invalid field, so the MCP clients could not see
which links run on kernel interfaces at all.

Also record what seeding a netem experiment on IOU costs: IOL emits its own
CDP/keepalive frames and they draw from the netem RNG stream, so a
seeded run only reproduces after `no cdp run` (verified bit-identical pcaps).
This commit is contained in:
YueGuobin 2026-10-01 01:36:57 +08:00
parent 4fa7f158fd
commit 6633304e97
No known key found for this signature in database
3 changed files with 25 additions and 0 deletions

View File

@ -152,6 +152,12 @@ Harmless on the relay, where suspend is emulated by the synthetic
frequency_drop filter, but fatal for a kernel link, whose suspend *is* the
anchor's admin state — a "suspended" link kept forwarding.
**Measuring seeded impairments on IOU**: IOL emits CDP/keepalive frames of
its own, and they draw from the netem RNG stream — a `seed`-reproducible run
never matches while they are on (measured: three rounds of mismatches, then
bit-identical pcaps after `no cdp run`). Turn the platform chatter off before
comparing seeded runs; the extra frames are visible in the capture.
## Configuration
```ini

View File

@ -105,6 +105,10 @@ VALID_LINK_FIELDS = {
"link_id",
"project_id",
"link_type",
# Runtime datapath fact (kernel bridge vs uBridge relay): the MCP tool
# descriptions have always advertised it, but it was missing here, so
# link_list could not return it at all.
"kernel_datapath",
"nodes",
"suspend",
"link_style",

View File

@ -425,6 +425,21 @@ class TestLink:
result = get_links_handler({"project_id": "p1", "fields": ["link_id"]}, ctx)
assert result["links"] == [{"link_id": "l1"}]
def test_list_can_ask_for_kernel_datapath(self, ctx):
"""
The datapath is a field the tool descriptions always advertised, so
it has to be requestable: a whitelist missing it rejected
fields=["kernel_datapath"] and made link_list unable to report which
links run on kernel interfaces.
"""
from gns3server.agent.gns3_copilot.gns3_client.api_handlers import get_links_handler
with patch(f"{AH}._get_connector") as m:
m.return_value = _mock_conn([{"link_id": "l1", "link_type": "ethernet", "kernel_datapath": True}])
result = get_links_handler({"project_id": "p1", "fields": ["link_id", "kernel_datapath"]}, ctx)
assert result["links"] == [{"link_id": "l1", "kernel_datapath": True}]
def test_get(self, ctx):
from gns3server.agent.gns3_copilot.gns3_client.api_handlers import get_link_handler