mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-03 05:51:15 +02:00
fix(qemu): release the datapath after the process is gone
QEMU is the guest side of every adapter TAP, and uBridge's tap delete
refuses a device another process holds open ('Device or resource busy') —
a best-effort delete whose failure is suppressed. QEMU's stop did the
delete first and stopped the process after, so every node stop or project
delete leaked the persistent TAPs: found by inspecting the host after a
project delete (16 taps left behind, carrier down, no holder), and the
held-tap behaviour reproduced against uBridge directly.
Stop the QEMU process first, then remove the TAPs and the per-link kernel
bridges — process side first, then the devices, the same lesson as IOU's
reverse stop order. Verified live: node stop and project delete now leave
no taps and no processes behind, while a restart recreates the TAPs.
The adapter anchor naming also goes through the shared
utils.kernel_anchor helper, like the other node types.
This commit is contained in:
3 files changed
+58
-12
No files matched your search
@@ -270,7 +270,7 @@ async def test_update_nio_binding_reapplies_filters_on_the_anchor(vm):
|
||||
|
||||
vm._ubridge_send.assert_any_call(f'tc reset "{TAP0}"')
|
||||
vm._ubridge_send.assert_any_call(f'tc netem set "{TAP0}" delay 10')
|
||||
assert not any(f'brctl addif' in str(c) for c in vm._ubridge_send.call_args_list)
|
||||
assert not any(f"brctl addif" in str(c) for c in vm._ubridge_send.call_args_list)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -346,3 +346,34 @@ async def test_capture_on_a_relay_link_uses_the_relay_bridge(vm):
|
||||
|
||||
await vm.stop_capture(0)
|
||||
vm._ubridge_send.assert_any_call(f"bridge stop_capture QEMU-{vm.id}-0")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_stop_stops_the_process_before_deleting_the_taps(vm):
|
||||
"""
|
||||
The QEMU process is the guest side of every adapter TAP, and uBridge's
|
||||
tap delete refuses a held device ("Device or resource busy") — that
|
||||
best-effort delete is suppressed, so removing the taps while QEMU still
|
||||
ran leaked the persistent TAPs. The process must be gone first (the same
|
||||
lesson as IOU's reverse stop order: process side, then the devices).
|
||||
"""
|
||||
|
||||
_running(vm)
|
||||
order = []
|
||||
|
||||
async def fake_termination(process, timeout=None):
|
||||
order.append("process")
|
||||
|
||||
async def fake_stop_ubridge():
|
||||
order.append("taps")
|
||||
|
||||
with (
|
||||
patch("gns3server.utils.asyncio.wait_for_process_termination", new=fake_termination),
|
||||
patch.object(vm, "_stop_ubridge", new=fake_stop_ubridge),
|
||||
asyncio_patch("gns3server.compute.qemu.qemu_vm.QemuVM._export_config"),
|
||||
asyncio_patch("gns3server.compute.qemu.qemu_vm.QemuVM._clear_save_vm_stated"),
|
||||
asyncio_patch("gns3server.compute.base_node.BaseNode.stop"),
|
||||
):
|
||||
await vm.stop()
|
||||
|
||||
assert order == ["process", "taps"]
|
||||
Reference in new issue
Block a user