fix: reconcile marker filters in _ubridge_apply_markers (delete/update)

The batch marker-def fan-out (PR #2848) routed create/update/delete
marker_definition through memory_only + a batch PUT /nios/batch that
re-applies markers via _ubridge_apply_markers. But _ubridge_apply_markers
was strictly add-only: it skipped any (name, link_id) already in
_marker_filter_bridges, and reset_packet_filters preserves mark filters
(contract). So:

  * delete_marker_definition left the deleted marker's filter alive in
    uBridge (still matching / signalling / writing pcap) until node restart.
  * update_marker_definition (bpf/tag/direction change) never reached
    uBridge — the live filter kept the old expression until node restart.

Make _ubridge_apply_markers a real reconcile against the desired
nio.markers:
  - installed but no longer desired  → delete_packet_filter + unlink pcap
                                       + unregister
  - desired with changed filter field → rebuild (delete + re-add)
  - desired with only enabled changed  → instant toggle (pcap preserved)
  - desired and unchanged              → skip
  - desired and new                    → add

Track installed specs in a parallel _marker_specs dict so changes can be
detected. Both base_node and the IOU iol_bridge override are updated.
Added tests for the delete-removed and rebuild-changed-bpf paths.
This commit is contained in:
YueGuobin committed 2026-08-11 21:00:03 +08:00
1 parent f045cde0da
commit 958c45b6fc
3 files changed
+153 -56

No files matched your search

+43
View File
@@ -367,6 +367,49 @@ async def test_apply_markers_skips_already_installed(compute_project, manager):
assert not any("add_packet_filter" in c for c in cmds) # skipped, not re-added
@pytest.mark.asyncio
async def test_apply_markers_deletes_removed(compute_project, manager):
# Reconcile: a marker no longer in nio.markers is deleted from uBridge
# (filter + registry), not left as an orphan still matching packets.
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
node._marker_filter_bridges["m", "L1"] = "VPCS-10"
node._marker_specs["m", "L1"] = {"bpf": "icmp", "enabled": True}
nio = NIOUDP(1234, "127.0.0.1", 4321)
nio.markers = {} # marker removed
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)
assert ("m", "L1") not in node._marker_filter_bridges
assert ("m", "L1") not in node._marker_specs
@pytest.mark.asyncio
async def test_apply_markers_rebuilds_changed_bpf(compute_project, manager):
# Reconcile: a marker whose bpf changed is rebuilt (delete + re-add), so
# uBridge ends up with the new expression — not the stale original.
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
node._marker_filter_bridges["m", "L1"] = "VPCS-10"
node._marker_specs["m", "L1"] = {"bpf": "icmp", "tag": None, "direction": None,
"data_link_type": None, "enabled": True}
nio = NIOUDP(1234, "127.0.0.1", 4321)
nio.markers = {"m": {"bpf": "tcp", "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 any("delete_packet_filter VPCS-10 m" in c for c in cmds) # old removed
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_stop_ubridge_clears_marker_bridges(compute_project, manager):
# uBridge stopping drops every marker filter — the map must clear so the next