mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-03 14:01:15 +02:00
fix: handle client disconnect in compute console WebSocket forwarding
The compute-side telnet_forward (and vnc_forward) let the
WebSocketDisconnect that starlette raises from send_bytes once the peer
is gone escape to the generic task-exception handler. Its str() is
empty, so every client disconnect during active node output logged a
message-less warning:
WARNING gns3server.compute.base_node:658 Exception while forwarding
WebSocket data to TELNET server:
Catch WebSocketDisconnect in both forwarders and log it at INFO as the
normal end of a session, and format the remaining task exceptions with
!r so their type stays visible. Mirrors the controller-side fix for
the same scenario (2324dcd74).
This commit is contained in:
2 files changed
+79
-7
No files matched your search
@@ -453,3 +453,57 @@ async def test_stop_ubridge_clears_marker_bridges(compute_project, manager):
|
||||
node._marker_filter_bridges["m", "L1"] = "VPCS-10"
|
||||
await node._stop_ubridge()
|
||||
assert node._marker_filter_bridges == {}
|
||||
|
||||
|
||||
class _GoneConsoleWebsocket:
|
||||
"""
|
||||
Stand-in for the compute-side WebSocket when the peer (the controller, or a
|
||||
browser) is already gone: receive() yields the disconnect message and
|
||||
send_bytes raises WebSocketDisconnect — what starlette raises from send
|
||||
after the transport reports OSError.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
from types import SimpleNamespace
|
||||
self.client = SimpleNamespace(host="127.0.0.1", port=5000)
|
||||
|
||||
async def receive(self):
|
||||
return {"type": "websocket.disconnect"}
|
||||
|
||||
async def send_bytes(self, data):
|
||||
from starlette.websockets import WebSocketDisconnect
|
||||
raise WebSocketDisconnect(code=1006)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_console_websocket_client_disconnect_while_node_output_streams(
|
||||
compute_project, manager, port_manager, monkeypatch, caplog):
|
||||
# regression test: the client disconnects while the node is still streaming
|
||||
# console output. telnet_forward used to let the (empty-str) WebSocketDisconnect
|
||||
# from send_bytes escape, logging a message-less WARNING.
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
node = VPCSVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", compute_project, manager)
|
||||
node.status = "started"
|
||||
|
||||
telnet_reader = MagicMock()
|
||||
telnet_reader.at_eof.return_value = False
|
||||
telnet_reader.read = AsyncioMagicMock(return_value=b"device output")
|
||||
telnet_writer = MagicMock()
|
||||
telnet_writer.wait_closed = AsyncioMagicMock()
|
||||
|
||||
async def fake_open_connection(*args, **kwargs):
|
||||
return telnet_reader, telnet_writer
|
||||
|
||||
monkeypatch.setattr(asyncio, "open_connection", fake_open_connection)
|
||||
|
||||
with caplog.at_level(logging.INFO):
|
||||
await node.start_websocket_console(_GoneConsoleWebsocket())
|
||||
|
||||
assert telnet_writer.close.called
|
||||
assert not [r for r in caplog.records if r.levelno >= logging.WARNING]
|
||||
assert any(
|
||||
"has disconnected from compute console WebSocket while node output" in r.message
|
||||
for r in caplog.records
|
||||
)
|
||||
Reference in new issue
Block a user