marker: incremental apply, clear bridge map on uBridge stop

_ubridge_apply_markers now installs only markers not already on the bridge
(uBridge's reset_packet_filters preserves mark filters), so an NIO update no
longer re-adds — and reopens — sibling markers' pcaps. _stop_ubridge clears
_marker_filter_bridges so a node restart re-installs everything (the map would
otherwise keep stale entries pointing at a fresh, empty uBridge).
This commit is contained in:
YueGuobin 2026-08-04 22:19:43 +08:00
parent caec71aa71
commit 95824b1861
No known key found for this signature in database
2 changed files with 43 additions and 6 deletions

View File

@ -990,6 +990,10 @@ class BaseNode:
log.info(f"Stopping uBridge hypervisor at {self._ubridge_hypervisor.endpoint}")
await self._ubridge_hypervisor.stop()
self._ubridge_hypervisor = None
# uBridge is gone, so every marker filter (and its in-bridge state) is
# gone too — clear the map so the next apply re-installs them all rather
# than skipping them as "already installed".
self._marker_filter_bridges.clear()
async def add_ubridge_udp_connection(self, bridge_name, source_nio, destination_nio):
"""
@ -1187,11 +1191,13 @@ class BaseNode:
async def _ubridge_apply_markers(self, bridge_name, nio):
"""
(Re-)apply every traffic-insight marker carried by *nio* to the uBridge
bridge *bridge_name*. Called from ``add_ubridge_udp_connection`` (bridge
creation / node restart) and ``update_ubridge_udp_connection`` (NIO update
the preceding ``_ubridge_apply_filters`` has already issued
``reset_packet_filters``, so we must re-add markers to survive the reset).
Install the traffic-insight markers carried by *nio* onto bridge
*bridge_name* that aren't already there. uBridge's ``reset_packet_filters``
preserves mark filters (contract), so on an NIO update we add only the new
ones re-adding an existing marker would either duplicate it or
close/reopen its pcap. Called from ``add_ubridge_udp_connection`` (fresh
bridge, empty map installs all) and ``update_ubridge_udp_connection``
(incremental).
"""
from gns3server.compute.marker.marker_manager import MarkerManager
@ -1202,9 +1208,14 @@ class BaseNode:
manager = MarkerManager.instance()
markers_dir = self.project.markers_working_directory()
for name, spec in markers.items():
link_id = spec.get("link_id", "")
# Incremental: skip markers already on this bridge. uBridge keeps mark
# filters across reset_packet_filters, so re-adding would duplicate (or
# reopen the pcap). A fresh bridge has an empty map → installs all.
if (name, link_id) in self._marker_filter_bridges:
continue
bpf = spec.get("bpf", "")
tag = spec.get("tag")
link_id = spec.get("link_id", "")
pcap_path = os.path.join(
markers_dir, f"{self._id}_{link_id}_{name}.pcap"
)

View File

@ -289,3 +289,29 @@ async def test_rebuild_marker_filter_delete_then_add(compute_project, manager):
assert any("delete_packet_filter VPCS-10 m" in c for c in cmds)
assert any("add_packet_filter VPCS-10 m mark" in c and "tcp" in c for c in cmds)
assert any("enable_packet_filter VPCS-10 m off" in c for c in cmds)
@pytest.mark.asyncio
async def test_apply_markers_skips_already_installed(compute_project, manager):
# Incremental apply: a marker already in _marker_filter_bridges is not
# re-added (uBridge keeps it across reset), so its pcap isn't reopened.
node = VPCSVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", compute_project, manager)
node._ubridge_send = AsyncioMagicMock()
node._marker_filter_bridges["m", "L1"] = "VPCS-10" # already installed
nio = NIOUDP(1234, "127.0.0.1", 4321)
nio.markers = {"m": {"bpf": "icmp", "tag": None, "link_id": "L1", "direction": None, "enabled": True}}
with patch("gns3server.compute.marker.marker_manager.MarkerManager") as mm:
mm.instance.return_value.register = MagicMock()
await node._ubridge_apply_markers("VPCS-10", nio)
cmds = [c.args[0] for c in node._ubridge_send.call_args_list]
assert not any("add_packet_filter" in c for c in cmds) # skipped, not re-added
@pytest.mark.asyncio
async def test_stop_ubridge_clears_marker_bridges(compute_project, manager):
# uBridge stopping drops every marker filter — the map must clear so the next
# apply re-installs them instead of skipping as "already installed".
node = VPCSVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", compute_project, manager)
node._marker_filter_bridges["m", "L1"] = "VPCS-10"
await node._stop_ubridge()
assert node._marker_filter_bridges == {}