mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-04 22:41:12 +02:00
test: cover the kernel-datapath review fixes
Regression coverage added with the fixes: - capture-slot semantics: second-port refusal, failed-start rollback, and a non-owner port's stop not reaching `capture stop_kernel`; - QEMU stop/start teardown on a mid-body or pre-launch failure, IOU start cleanup and filter guards, Dynamips half-attach undo plus retry; - switch VLAN reconcile skipping a deferred anchor and rolling the ports_mapping back on failure; - Docker/QEMU occupied-port refusal, probe ctime invalidation, probe survival of a failing uBridge start; - env-marker dialects (mid-line mentions, falsy values) and the agent merge rejecting entries without a device_name. Test-body imports of product modules move to the shared header.
This commit is contained in:
13 files changed
+438
-40
No files matched your search
@@ -899,7 +899,6 @@ async def test_update_nio_kernel_clears_netem(vm):
|
||||
async def test_apply_netem_reset_tolerates_missing_qdisc(vm):
|
||||
|
||||
vm._ubridge_hypervisor = MagicMock()
|
||||
from gns3server.compute.ubridge.ubridge_error import UbridgeError
|
||||
|
||||
async def refuse(command):
|
||||
raise UbridgeError("207-Could not reset qdisc on gv0: No such file or directory")
|
||||
@@ -914,7 +913,6 @@ async def test_apply_netem_reset_tolerates_missing_qdisc(vm):
|
||||
async def test_apply_netem_reset_reraises_real_errors(vm):
|
||||
|
||||
vm._ubridge_hypervisor = MagicMock()
|
||||
from gns3server.compute.ubridge.ubridge_error import UbridgeError
|
||||
|
||||
async def refuse(command):
|
||||
raise UbridgeError("206-Could not reset qdisc on gv0: Operation not permitted")
|
||||
@@ -997,6 +995,79 @@ async def test_start_capture_second_concurrent_fails(vm):
|
||||
with pytest.raises(UbridgeError, match="already in progress"):
|
||||
await vm.start_capture(0, "/tmp/capture.pcap")
|
||||
|
||||
# the failed start must not leave the port marked as capturing: a stuck
|
||||
# flag refuses the retry and lets this port's stop kill the winner
|
||||
assert nio.capturing is False
|
||||
assert vm._kernel_capture_ifc is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_start_capture_second_port_on_the_same_node_is_refused(vm):
|
||||
"""
|
||||
uBridge serves one kernel capture per process while the server models
|
||||
captures per port: a second port's capture must be refused with a clear
|
||||
error and its flag rolled back — otherwise its stop would issue the
|
||||
process-wide, argument-less ``capture stop_kernel`` and kill the first
|
||||
port's capture while its own flag still says capturing.
|
||||
"""
|
||||
|
||||
vm._ubridge_hypervisor = MagicMock()
|
||||
vm._ubridge_send = AsyncioMagicMock()
|
||||
vm.status = "started"
|
||||
vm._ethernet_adapters.append(type(vm._ethernet_adapters[0])())
|
||||
host_ifc1, _ = vm._veth_names(0, 0)
|
||||
host_ifc2, _ = vm._veth_names(1, 0)
|
||||
vm._kernel_veths[(0, 0)] = host_ifc1
|
||||
vm._kernel_veths[(1, 0)] = host_ifc2
|
||||
nio1 = vm.manager.create_nio({"type": "nio_bridge", "bridge": BRIDGE})
|
||||
nio2 = vm.manager.create_nio({"type": "nio_bridge", "bridge": BRIDGE})
|
||||
vm._ethernet_adapters[0].add_nio(0, nio1)
|
||||
vm._ethernet_adapters[1].add_nio(0, nio2)
|
||||
|
||||
await vm.start_capture(0, "/tmp/capture.pcap")
|
||||
|
||||
with pytest.raises(DockerError, match="one kernel capture per node"):
|
||||
await vm.start_capture(1, "/tmp/capture2.pcap")
|
||||
|
||||
assert nio1.capturing is True
|
||||
assert nio2.capturing is False
|
||||
vm._ubridge_send.assert_any_call(f'capture start_kernel {host_ifc1} "/tmp/capture.pcap"')
|
||||
assert not any("stop_kernel" in str(c) for c in vm._ubridge_send.call_args_list)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_stop_capture_on_a_port_that_does_not_own_the_slot_skips_stop_kernel(vm):
|
||||
"""
|
||||
``capture stop_kernel`` is process-wide and takes no interface: a stop on
|
||||
a port whose flag says capturing while another port owns the slot must
|
||||
not reach uBridge — it would stop the owner's capture.
|
||||
"""
|
||||
|
||||
vm._ubridge_hypervisor = MagicMock()
|
||||
vm._ubridge_send = AsyncioMagicMock()
|
||||
vm.status = "started"
|
||||
vm._ethernet_adapters.append(type(vm._ethernet_adapters[0])())
|
||||
host_ifc1, _ = vm._veth_names(0, 0)
|
||||
host_ifc2, _ = vm._veth_names(1, 0)
|
||||
vm._kernel_veths[(0, 0)] = host_ifc1
|
||||
vm._kernel_veths[(1, 0)] = host_ifc2
|
||||
nio1 = vm.manager.create_nio({"type": "nio_bridge", "bridge": BRIDGE})
|
||||
nio2 = vm.manager.create_nio({"type": "nio_bridge", "bridge": BRIDGE})
|
||||
vm._ethernet_adapters[0].add_nio(0, nio1)
|
||||
vm._ethernet_adapters[1].add_nio(0, nio2)
|
||||
|
||||
await vm.start_capture(0, "/tmp/capture.pcap") # port 0 owns the slot
|
||||
nio2.start_packet_capture("/tmp/capture2.pcap") # a stale second flag
|
||||
|
||||
await vm.stop_capture(1)
|
||||
|
||||
assert nio2.capturing is False
|
||||
assert not any("stop_kernel" in str(c) for c in vm._ubridge_send.call_args_list)
|
||||
|
||||
# the owner's capture is untouched and still stoppable
|
||||
await vm.stop_capture(0)
|
||||
vm._ubridge_send.assert_any_call("capture stop_kernel")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_connect_nio_kernel_restarts_capture(vm):
|
||||
|
||||
@@ -1642,6 +1642,24 @@ async def test_adapter_add_nio_binding_1(vm):
|
||||
assert vm._ethernet_adapters[0].get_nio(0) == nio
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_adapter_add_nio_binding_refuses_an_occupied_port(vm):
|
||||
"""
|
||||
The compute-side backstop of the controller's duplicate-port guard: a
|
||||
second NIO for a port that already carries one is refused instead of
|
||||
silently overwriting the binding the first link's teardown still needs.
|
||||
"""
|
||||
|
||||
first = vm.manager.create_nio({"type": "nio_udp", "lport": 4242, "rport": 4343, "rhost": "127.0.0.1"})
|
||||
await vm.adapter_add_nio_binding(0, first)
|
||||
|
||||
second = vm.manager.create_nio({"type": "nio_udp", "lport": 4244, "rport": 4345, "rhost": "127.0.0.1"})
|
||||
with pytest.raises(DockerError, match="already has a link"):
|
||||
await vm.adapter_add_nio_binding(0, second)
|
||||
|
||||
assert vm._ethernet_adapters[0].get_nio(0) is first
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_adapter_add_nio_binding_sets_carrier(vm):
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import pytest_asyncio
|
||||
from gns3server.compute.docker import Docker
|
||||
from gns3server.compute.docker.docker_error import DockerError
|
||||
from gns3server.compute.docker.iol_docker_vm import IOLDockerVM
|
||||
from gns3server.utils.kernel_anchor import kernel_anchor_name, kernel_anchor_type
|
||||
from tests.utils import AsyncioMagicMock
|
||||
|
||||
NODE_ID = "00010203-0405-0607-0809-0a0b0c0d0e0f"
|
||||
@@ -385,8 +386,6 @@ def test_anchor_names_follow_the_iol_docker_key():
|
||||
never the docker veth namespace.
|
||||
"""
|
||||
|
||||
from gns3server.utils.kernel_anchor import kernel_anchor_name, kernel_anchor_type
|
||||
|
||||
environment = "GNS3_IOL_RUNNER=1\nGNS3_IOL_STARTUP_CONFIG=cfg.txt"
|
||||
assert kernel_anchor_type("docker", environment) == "iol_docker"
|
||||
assert kernel_anchor_type("docker", "GNS3_UNIX_SOCKET_NIO=1") == "docker"
|
||||
|
||||
Reference in new issue
Block a user