controller: allow markers and packet filters on Ethernet switch links

The brctl Ethernet switch runs a per-port uBridge relay, so it can host
the mark filter and packet filters like any uBridge-backed node. Add
ethernet_switch to _MARKER_CAPABLE_TYPES and _get_filter_node, narrow the
UDPLink.update() NIO-PUT skip down to the Dynamips-hosted ethernet_hub,
and expose the matching compute endpoints: PUT nio (filter/marker
reapply) plus the per-marker toggle/pause/resume/delete/rebuild routes.

The ethernet_hub keeps its exclusion: its routes still wire into the
Dynamips hub, which has no uBridge of its own.
This commit is contained in:
YueGuobin committed 2026-08-21 21:41:51 +08:00
1 parent 210103058f
commit 72dc10a669
9 files changed
+282 -17

No files matched your search

+2 -2
View File
@@ -378,9 +378,9 @@ async def test_available_filters(project, compute):
link.create = AsyncioMagicMock()
assert link.available_filters() == []
# Ethernet switch is not supported should return 0 filters
# The brctl Ethernet switch hosts filters on its per-port uBridge relays
await link.add_node(node1, 0, 4)
assert link.available_filters() == []
assert len(link.available_filters()) > 0
node2 = Node(project, compute, "node2", node_type="vpcs")
node2._ports = [EthernetPort("E0", 0, 0, 4)]
+23 -5
View File
@@ -56,20 +56,21 @@ def _valid_bpf():
return stack
async def _make_link(project, port_cls=EthernetPort):
"""Build a created UDPLink between two VPCS nodes on a mocked compute.
async def _make_link(project, port_cls=EthernetPort, node_types=("vpcs", "vpcs")):
"""Build a created UDPLink between two nodes on a mocked compute.
``port_cls`` defaults to EthernetPort; pass SerialPort for a serial link
(the link's link_type follows the port).
(the link's link_type follows the port). ``node_types`` overrides the
endpoint node types (e.g. a switch-to-switch link).
"""
compute = MagicMock()
compute.id = "local"
compute.host = "example.com"
node1 = Node(project, compute, "n1", node_type="vpcs")
node1 = Node(project, compute, "n1", node_type=node_types[0])
node1._ports = [port_cls("E0", 0, 0, 0)]
node2 = Node(project, compute, "n2", node_type="vpcs")
node2 = Node(project, compute, "n2", node_type=node_types[1])
node2._ports = [port_cls("E0", 0, 0, 1)]
async def subnet(_other):
@@ -112,6 +113,23 @@ async def test_start_marker_stores_entry(project):
assert entry["highlight_duration"] == 800
assert entry["enabled"] is True
assert entry["capture_node_id"] in {n["node"].id for n in link._nodes}
@pytest.mark.asyncio
async def test_start_marker_on_ethernet_switch_link(project):
"""A switch-to-switch link can host a marker: the brctl Ethernet switch
runs a per-port uBridge relay the `mark` filter attaches to."""
with _valid_bpf():
link = await _make_link(project, node_types=("ethernet_switch", "ethernet_switch"))
await link.start_marker("icmp", "icmp")
entry = link.markers["icmp"]
switch_ids = {n["node"].id for n in link._nodes}
assert entry["capture_node_id"] in switch_ids
# the marker rides exactly one side's NIO and is pushed via update()
carrying = [d for d in link._link_data if "icmp" in d["markers"]]
assert len(carrying) == 1
assert "inherited_from" not in entry
+49
View File
@@ -451,6 +451,55 @@ async def test_update(project):
}, timeout=120)
@pytest.mark.asyncio
async def test_update_ethernet_switch_nio(project):
"""
Link updates must reach an Ethernet switch endpoint: the brctl switch has
a PUT NIO route, so only the Dynamips-hosted hub side stays skipped.
"""
compute1 = MagicMock()
node_vpcs = Node(project, compute1, "node1", node_type="vpcs")
node_vpcs._ports = [EthernetPort("E0", 0, 0, 4)]
node_switch = Node(project, compute1, "node2", node_type="ethernet_switch")
node_switch._ports = [EthernetPort("E0", 0, 3, 1)]
async def subnet_callback(compute2):
return ("192.168.1.1", "192.168.1.2")
compute1.get_ip_on_same_subnet.side_effect = subnet_callback
async def compute1_callback(path, data={}, **kwargs):
if "/ports/udp" in path:
response = MagicMock()
response.json = {"udp_port": 1024}
return response
compute1.post.side_effect = compute1_callback
compute1.put = AsyncioMagicMock()
compute1.host = "example.com"
link = UDPLink(project)
await link.add_node(node_vpcs, 0, 4)
await link.add_node(node_switch, 3, 1)
assert link.created
await link.update_filters({"delay": [10, 0]})
compute1.put.assert_any_call(
"/projects/{}/ethernet_switch/nodes/{}/adapters/3/ports/1/nio".format(project.id, node_switch.id),
data={
"lport": 1024,
"rhost": "192.168.1.1",
"rport": 1024,
"type": "nio_udp",
"suspend": False,
"markers": {},
"filters": {}
}, timeout=221
)
@pytest.mark.asyncio
async def test_update_suspend(project):
compute1 = MagicMock()