From 823cf372870a0bbfc125bc306801e99db69fe0fc Mon Sep 17 00:00:00 2001 From: YueGuobin Date: Mon, 31 Aug 2026 23:04:00 +0800 Subject: [PATCH] fix: list every project in template/image in-use refusal, not just the first The usage scans broke out of the whole (project, node) iteration at the first matching node, so deleting a template or image used by several projects reported only one project name while the error message says 'one or more projects'. Keep scanning and list each project once. Caught on a live server: an IOU L3 template used by two projects (one of them closed) was refused citing only one of them. --- gns3server/controller/__init__.py | 6 ++---- tests/controller/test_controller.py | 29 +++++++++++++++++++++++++++-- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/gns3server/controller/__init__.py b/gns3server/controller/__init__.py index 99416e202..b1e16c87b 100644 --- a/gns3server/controller/__init__.py +++ b/gns3server/controller/__init__.py @@ -875,9 +875,8 @@ class Controller: node_template_id = node.template_id else: node_template_id = node.get("template_id") - if node_template_id and str(node_template_id) == template_id: + if node_template_id and str(node_template_id) == template_id and project.name not in project_names: project_names.append(project.name) - break return project_names def find_projects_using_image(self, image_filename: str) -> list: @@ -894,9 +893,8 @@ class Controller: node_properties = node.properties else: node_properties = node.get("properties") or {} - if _image_referenced(node_properties, image_filename): + if _image_referenced(node_properties, image_filename) and project.name not in project_names: project_names.append(project.name) - break return project_names def collect_referenced_image_filenames(self) -> set: diff --git a/tests/controller/test_controller.py b/tests/controller/test_controller.py index ca689edf5..dad07581e 100644 --- a/tests/controller/test_controller.py +++ b/tests/controller/test_controller.py @@ -575,11 +575,36 @@ async def test_find_projects_using_template_and_images(controller): assert "base.qcow2" in referenced assert "disk.qcow2" in referenced + # a second node from the same template in the same project must not + # list the project twice + await project1.add_node( + compute, + "n1-bis", + None, + node_type="vpcs", + template_id=template_id, + properties={}, + ) + assert controller.find_projects_using_template(template_id) == ["Test1"] + + # a second project using the same template and image must be listed too + await project2.add_node( + compute, + "n2-bis", + None, + node_type="vpcs", + template_id=template_id, + properties={"hda_disk_image_backing_file": "base.qcow2"}, + ) + assert controller.find_projects_using_template(template_id) == ["Test1", "Test2"] + assert controller.find_projects_using_image("base.qcow2") == ["Test1", "Test2"] + assert controller.find_projects_using_image("disk.qcow2") == ["Test1"] + # closed projects: same answers from the .gns3 file on disk await project1.close() await project2.close() - assert controller.find_projects_using_template(template_id) == ["Test1"] - assert controller.find_projects_using_image("base.qcow2") == ["Test1"] + assert controller.find_projects_using_template(template_id) == ["Test1", "Test2"] + assert controller.find_projects_using_image("base.qcow2") == ["Test1", "Test2"] assert controller.find_projects_using_image("disk.qcow2") == ["Test1"]