refactor(marker): converge to filter single-path model, remove dual-apply endpoints

Markers now follow exactly the same apply pattern as packet filters:
state lives in Link._markers, application goes through NIO
(update() -> PUT /nio -> _ubridge_apply_markers). The former
immediate-apply REST endpoints (/markers/start, /markers/stop on
the compute side) and the per-node start_marker/stop_marker methods
are removed — they were a legacy of the original capture-inspired
design and have been superseded by the NIO flow.

Changes:
- controller/udp_link: start_marker/stop_marker/update_marker now
  set _markers state + call self.update() (mirrors update_filters).
  Removed _marker_capture_nodes runtime dict and its helpers.
- controller/project: _create_link_from_topology_data restores
  _markers directly from persisted data (with BPF validation,
  like filter reload). No long calls start_marker during load.
- compute: _ubridge_apply_markers swallows BPF compile errors
  (warn+skip), matching _ubridge_apply_filters behaviour so a
  single bad expression cannot break link creation / node restart.
- Removed: /markers/start,stop endpoints (6 handlers across
  vpcs/qemu/docker route files), node start_marker/stop_marker
  methods (3 VM files), _ubridge_delete_marker_filter,
  _marker_capture_nodes, MarkerDelete schema.

Net: ~280 lines of dead code removed; marker and packet filter now
share a single, unified apply path via the NIO.
This commit is contained in:
YueGuobin committed 2026-07-16 00:39:51 +08:00
1 parent 88c03d1431
commit c62b9b0283
11 files changed
+73 -412

No files matched your search

@@ -29,8 +29,6 @@ from typing import Union
from gns3server import schemas
from gns3server.compute.docker import Docker
from gns3server.compute.docker.docker_vm import DockerVM
from gns3server.compute.marker.marker_manager import MarkerManager
from .dependencies.authentication import compute_authentication, ws_compute_authentication
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or Docker node"}}
@@ -355,60 +353,6 @@ async def stop_docker_node_capture(
await node.stop_capture(adapter_number)
@router.post(
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/markers/start",
dependencies=[Depends(compute_authentication)]
)
async def start_docker_node_marker(
*,
project_id: UUID,
adapter_number: int,
port_number: int,
marker_data: schemas.MarkerCreate,
node: DockerVM = Depends(dep_node)
) -> dict:
"""
Attach a traffic-insight ``mark`` filter to the Docker node's uBridge bridge.
"""
pcap_path = os.path.join(
node.project.markers_working_directory(),
f"{node.id}_{marker_data.link_id}_{marker_data.name}.pcap"
)
await node.start_marker(adapter_number, marker_data.name, marker_data.bpf, pcap_path, marker_data.tag)
MarkerManager.instance().register(
str(project_id), node.id, marker_data.name, marker_data.link_id, marker_data.tag
)
nio = node.get_nio(adapter_number)
if nio:
nio.markers[marker_data.name] = {
"bpf": marker_data.bpf, "tag": marker_data.tag, "link_id": marker_data.link_id
}
return {"pcap_file_path": str(pcap_path)}
@router.post(
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/markers/stop",
status_code=status.HTTP_204_NO_CONTENT,
dependencies=[Depends(compute_authentication)]
)
async def stop_docker_node_marker(
adapter_number: int,
port_number: int,
marker_data: schemas.MarkerDelete,
node: DockerVM = Depends(dep_node)
) -> None:
"""
Remove a traffic-insight ``mark`` filter from the Docker node's uBridge bridge.
"""
await node.stop_marker(adapter_number, marker_data.name)
MarkerManager.instance().unregister(node.id, marker_data.name)
nio = node.get_nio(adapter_number)
if nio:
nio.markers.pop(marker_data.name, None)
@router.get(
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream",
dependencies=[Depends(compute_authentication)]
@@ -30,8 +30,6 @@ from gns3server import schemas
from gns3server.compute import qemu
from gns3server.compute.qemu import Qemu
from gns3server.compute.qemu.qemu_vm import QemuVM
from gns3server.compute.marker.marker_manager import MarkerManager
from .dependencies.authentication import compute_authentication, ws_compute_authentication
import logging
@@ -384,60 +382,6 @@ async def stop_qemu_node_capture(
await node.stop_capture(adapter_number)
@router.post(
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/markers/start",
dependencies=[Depends(compute_authentication)]
)
async def start_qemu_node_marker(
*,
project_id: UUID,
adapter_number: int,
marker_data: schemas.MarkerCreate,
port_number: int = Path(..., ge=0, le=0),
node: QemuVM = Depends(dep_node)
) -> dict:
"""
Attach a traffic-insight ``mark`` filter to the QEMU node's uBridge bridge.
"""
pcap_path = os.path.join(
node.project.markers_working_directory(),
f"{node.id}_{marker_data.link_id}_{marker_data.name}.pcap"
)
await node.start_marker(adapter_number, marker_data.name, marker_data.bpf, pcap_path, marker_data.tag)
MarkerManager.instance().register(
str(project_id), node.id, marker_data.name, marker_data.link_id, marker_data.tag
)
nio = node.get_nio(adapter_number)
if nio:
nio.markers[marker_data.name] = {
"bpf": marker_data.bpf, "tag": marker_data.tag, "link_id": marker_data.link_id
}
return {"pcap_file_path": str(pcap_path)}
@router.post(
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/markers/stop",
status_code=status.HTTP_204_NO_CONTENT,
dependencies=[Depends(compute_authentication)]
)
async def stop_qemu_node_marker(
adapter_number: int,
marker_data: schemas.MarkerDelete,
port_number: int = Path(..., ge=0, le=0),
node: QemuVM = Depends(dep_node)
) -> None:
"""
Remove a traffic-insight ``mark`` filter from the QEMU node's uBridge bridge.
"""
await node.stop_marker(adapter_number, marker_data.name)
MarkerManager.instance().unregister(node.id, marker_data.name)
nio = node.get_nio(adapter_number)
if nio:
nio.markers.pop(marker_data.name, None)
@router.get(
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream",
dependencies=[Depends(compute_authentication)]
@@ -29,8 +29,6 @@ from uuid import UUID
from gns3server import schemas
from gns3server.compute.vpcs import VPCS
from gns3server.compute.vpcs.vpcs_vm import VPCSVM
from gns3server.compute.marker.marker_manager import MarkerManager
from .dependencies.authentication import compute_authentication, ws_compute_authentication
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or VMware node"}}
@@ -305,65 +303,6 @@ async def stop_vpcs_node_capture(
await node.stop_capture(port_number)
@router.post(
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/markers/start",
dependencies=[Depends(compute_authentication)]
)
async def start_vpcs_node_marker(
*,
project_id: UUID,
port_number: int,
marker_data: schemas.MarkerCreate,
adapter_number: int = Path(..., ge=0, le=0),
node: VPCSVM = Depends(dep_node)
) -> dict:
"""
Attach a traffic-insight ``mark`` filter to the VPCS node's uBridge bridge.
On BPF match uBridge emits a MARK signal and appends the packet to the pcap.
"""
pcap_path = os.path.join(
node.project.markers_working_directory(),
f"{node.id}_{marker_data.link_id}_{marker_data.name}.pcap"
)
await node.start_marker(port_number, marker_data.name, marker_data.bpf, pcap_path, marker_data.tag)
MarkerManager.instance().register(
str(project_id), node.id, marker_data.name, marker_data.link_id, marker_data.tag
)
# Mirror the marker spec onto the NIO so it survives a node restart (the NIO
# persists across stop/start and _ubridge_apply_markers re-applies its markers).
nio = node.get_nio(port_number)
if nio:
nio.markers[marker_data.name] = {
"bpf": marker_data.bpf,
"tag": marker_data.tag,
"link_id": marker_data.link_id,
}
return {"pcap_file_path": pcap_path}
@router.post(
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/markers/stop",
status_code=status.HTTP_204_NO_CONTENT,
dependencies=[Depends(compute_authentication)]
)
async def stop_vpcs_node_marker(
*,
port_number: int,
marker_data: schemas.MarkerDelete,
adapter_number: int = Path(..., ge=0, le=0),
node: VPCSVM = Depends(dep_node)
) -> None:
"""
Remove a traffic-insight ``mark`` filter from the VPCS node's uBridge bridge.
"""
await node.stop_marker(port_number, marker_data.name)
MarkerManager.instance().unregister(node.id, marker_data.name)
nio = node.get_nio(port_number)
if nio:
nio.markers.pop(marker_data.name, None)
@router.get(
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream",