From 4e30b6905a8d1b39d0f47d2742e0936146e533f7 Mon Sep 17 00:00:00 2001 From: YueGuobin Date: Tue, 11 Aug 2026 01:00:01 +0800 Subject: [PATCH] fix: await Dynamips.create_nio (it is async, unlike sync base) Dynamips.create_nio is async def while BaseManager.create_nio is a sync def. The previous fix only added the extra 'node' argument but did not await the resulting coroutine, causing 'was never awaited' warnings and passing a coroutine object instead of an NIO instance to the binding dispatch. Add 'await' on the Dynamips branch. Test updated to verify both the async nature and the parameter count. --- gns3server/api/routes/compute/projects.py | 11 ++++------- tests/api/routes/compute/test_projects.py | 23 +++++++++++++++-------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/gns3server/api/routes/compute/projects.py b/gns3server/api/routes/compute/projects.py index 76c88d2f0..ab55443fe 100644 --- a/gns3server/api/routes/compute/projects.py +++ b/gns3server/api/routes/compute/projects.py @@ -189,15 +189,12 @@ async def create_batch_nios( for entry in batch.nios: node = project.get_node(entry.node_id) nio_settings = jsonable_encoder(entry.nio, exclude_unset=True) - # Dynamips.create_nio takes an extra positional `node` argument that - # the base signature does not include. Detect it via parameter count. - # Dynamips.create_nio(self, node, nio_settings) exposes one extra - # positional on the bound method compared to the standard signature - # (self, nio_settings). Detect it: standard == 1 bound param, - # Dynamips == 2 bound params (node + nio_settings). + # Dynamips.create_nio(self, node, nio_settings) is async and takes an + # extra positional 'node'. Detect via bound-method parameter count: + # standard == 1, Dynamips == 2. Await the async variant. sig = inspect.signature(node.manager.create_nio) if len(sig.parameters) >= 2: - nio = node.manager.create_nio(node, nio_settings) + nio = await node.manager.create_nio(node, nio_settings) else: nio = node.manager.create_nio(nio_settings) await _add_nio_binding(node, entry.adapter_number, entry.port_number, nio) diff --git a/tests/api/routes/compute/test_projects.py b/tests/api/routes/compute/test_projects.py index b64053d7d..949c0d891 100644 --- a/tests/api/routes/compute/test_projects.py +++ b/tests/api/routes/compute/test_projects.py @@ -258,27 +258,34 @@ class TestBatchNIOEdgeCases: node.add_nio.assert_called_once_with(nio, 0) @pytest.mark.asyncio - async def test_dynamips_create_nio_passes_node_arg(self): + async def test_dynamips_create_nio_is_async_and_needs_await(self): """ - Dynamips.create_nio(self, node, nio_settings) exposes 2 bound-method - params vs. the standard 1. The batch handler detects this via the - parameter count on the bound method and passes the extra 'node' - argument. + Dynamips.create_nio is async (returns a coroutine) unlike the sync + base version. The batch handler must await it. """ import inspect + import asyncio as _asyncio class _FakeDynamips: async def create_nio(self, node, nio_settings): - pass + return {"type": "nio_udp", "node": node} class _FakeBase: - async def create_nio(self, nio_settings): - pass + def create_nio(self, nio_settings): + return {"type": "nio_udp"} dyn = _FakeDynamips() base = _FakeBase() assert len(inspect.signature(dyn.create_nio).parameters) == 2 # Dynamips assert len(inspect.signature(base.create_nio).parameters) == 1 # standard + assert inspect.iscoroutinefunction(dyn.create_nio) + assert not inspect.iscoroutinefunction(base.create_nio) + + # Verify the batch logic: 2 params → await, 1 param → no await + d_result = await dyn.create_nio("r1", {"type": "nio_udp"}) + b_result = base.create_nio({"type": "nio_udp"}) + assert d_result["node"] == "r1" + assert b_result["type"] == "nio_udp" @pytest.mark.asyncio async def test_qemu_dispatch_to_adapter_add_nio_binding(self):