fix: scope marker reconcile to the current bridge/NIO

The reconcile pass in _ubridge_apply_markers walked the node-wide
_marker_filter_bridges map but compared against `desired`, which only
carries the markers of the NIO being updated. Updating any one link
therefore deleted every other link's markers (and their pcaps) on that
node — a regression from the add-only→reconcile switch. IOU's override
had the same flaw across its ports.

Guard the delete pass with the current bridge (base_node) / IOL location
(IOU) so only markers on the NIO being reconciled can be removed. Added
a regression test that fails without the guard.
This commit is contained in:
YueGuobin committed 2026-08-11 22:11:44 +08:00
1 parent 5de902c8b1
commit 958aa59d7e
3 files changed
+41

No files matched your search

+30
View File
@@ -410,6 +410,36 @@ async def test_apply_markers_rebuilds_changed_bpf(compute_project, manager):
assert any("add_packet_filter VPCS-10 m mark" in c and "tcp" in c for c in cmds) # new added
@pytest.mark.asyncio
async def test_apply_markers_preserves_markers_on_other_bridges(compute_project, manager):
# Regression: reconciling one NIO must not delete markers installed on this
# node's OTHER bridges/NIOs. _marker_filter_bridges is node-wide, but
# `desired` only carries the current NIO's markers — the delete pass must be
# scoped to the current bridge, or updating one link wipes every other link's
# markers + pcaps.
node = VPCSVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", compute_project, manager)
node._ubridge_send = AsyncioMagicMock()
node._ubridge_hypervisor = MagicMock()
node._ubridge_hypervisor.is_running.return_value = True
# Two links, each with a marker on its own bridge:
node._marker_filter_bridges["m", "L1"] = "VPCS-10"
node._marker_specs["m", "L1"] = {"bpf": "icmp", "enabled": True}
node._marker_filter_bridges["m", "L2"] = "VPCS-20"
node._marker_specs["m", "L2"] = {"bpf": "tcp", "enabled": True}
# Update only the VPCS-10 NIO; "m" is gone from this link — but the marker
# on VPCS-20 (L2) must survive untouched.
nio = NIOUDP(1234, "127.0.0.1", 4321)
nio.markers = {}
with patch("gns3server.compute.marker.marker_manager.MarkerManager") as mm:
mm.instance.return_value.unregister = MagicMock()
await node._ubridge_apply_markers("VPCS-10", nio)
cmds = [c.args[0] for c in node._ubridge_send.call_args_list]
assert any("delete_packet_filter VPCS-10 m" in c for c in cmds) # L1 removed on its bridge
assert not any("VPCS-20" in c for c in cmds) # other bridge untouched
assert ("m", "L1") not in node._marker_filter_bridges
assert ("m", "L2") in node._marker_filter_bridges # L2 preserved
@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