From 95bd76a212f363cba9878c3d72f0d92ff4b32d3f Mon Sep 17 00:00:00 2001 From: UmmmAGoodName Date: Mon, 16 Mar 2026 19:09:32 +0100 Subject: [PATCH 1/3] Fixed issue #1605 regarding Cisco slots causing configs to break --- gns3server/compute/base_manager.py | 3 +++ gns3server/compute/dynamips/__init__.py | 26 ++++++++++++++----------- gns3server/controller/node.py | 2 +- gns3server/controller/project.py | 3 --- 4 files changed, 19 insertions(+), 15 deletions(-) diff --git a/gns3server/compute/base_manager.py b/gns3server/compute/base_manager.py index 17a41fe7e..a3ee38cec 100644 --- a/gns3server/compute/base_manager.py +++ b/gns3server/compute/base_manager.py @@ -221,6 +221,9 @@ class BaseManager: if not hasattr(destination_node, "working_dir"): return destination_node + if hasattr(source_node, "status") and source_node.status != "stopped": + raise ComputeError("Cannot duplicate node data while the node is running") + destination_dir = destination_node.working_dir try: shutil.rmtree(destination_dir) diff --git a/gns3server/compute/dynamips/__init__.py b/gns3server/compute/dynamips/__init__.py index eae8ab61e..4362b9be1 100644 --- a/gns3server/compute/dynamips/__init__.py +++ b/gns3server/compute/dynamips/__init__.py @@ -478,36 +478,40 @@ class Dynamips(BaseManager): adapter = ADAPTER_MATRIX[adapter_name]() try: if vm.slots[slot_id] and not isinstance(vm.slots[slot_id], type(adapter)): - await vm.slot_remove_binding(slot_id) + if vm.slots[slot_id].removable(): + await vm.slot_remove_binding(slot_id) + else: + log.warning(f"Slot {slot_id} on router '{vm.name}' has a non-removable adapter, skipping replacement") + continue if not isinstance(vm.slots[slot_id], type(adapter)): await vm.slot_add_binding(slot_id, adapter) except IndexError: - raise DynamipsError(f"Slot {slot_id} doesn't exist on this router") + log.warning(f"Slot {slot_id} doesn't exist on router '{vm.name}', skipping") elif name.startswith("slot") and (value is None or value == ""): slot_id = int(name[-1]) try: - if vm.slots[slot_id]: + if vm.slots[slot_id] and vm.slots[slot_id].removable(): await vm.slot_remove_binding(slot_id) except IndexError: - raise DynamipsError(f"Slot {slot_id} doesn't exist on this router") + log.warning(f"Slot {slot_id} doesn't exist on router '{vm.name}', skipping") elif name.startswith("wic") and value in WIC_MATRIX: wic_slot_id = int(name[-1]) wic_name = value wic = WIC_MATRIX[wic_name]() try: - if vm.slots[0].wics[wic_slot_id] and not isinstance(vm.slots[0].wics[wic_slot_id], type(wic)): + if vm.slots[0] and vm.slots[0].wics[wic_slot_id] and not isinstance(vm.slots[0].wics[wic_slot_id], type(wic)): await vm.uninstall_wic(wic_slot_id) - if not isinstance(vm.slots[0].wics[wic_slot_id], type(wic)): + if vm.slots[0] and not isinstance(vm.slots[0].wics[wic_slot_id], type(wic)): await vm.install_wic(wic_slot_id, wic) - except IndexError: - raise DynamipsError(f"WIC slot {wic_slot_id} doesn't exist on this router") + except (IndexError, AttributeError): + log.warning(f"WIC slot {wic_slot_id} doesn't exist on router '{vm.name}', skipping") elif name.startswith("wic") and (value is None or value == ""): wic_slot_id = int(name[-1]) try: - if vm.slots[0].wics and vm.slots[0].wics[wic_slot_id]: + if vm.slots[0] and vm.slots[0].wics and vm.slots[0].wics[wic_slot_id]: await vm.uninstall_wic(wic_slot_id) - except IndexError: - raise DynamipsError(f"WIC slot {wic_slot_id} doesn't exist on this router") + except (IndexError, AttributeError): + log.warning(f"WIC slot {wic_slot_id} doesn't exist on router '{vm.name}', skipping") mmap_support = self.config.settings.Dynamips.mmap_support if mmap_support is False: diff --git a/gns3server/controller/node.py b/gns3server/controller/node.py index 37dcff665..ffd15a728 100644 --- a/gns3server/controller/node.py +++ b/gns3server/controller/node.py @@ -556,7 +556,7 @@ class Node: # None properties are not be sent because it can mean the emulator doesn't support it for key, value in list(data.items()): if value is None or value == {} or key in self.CONTROLLER_ONLY_PROPERTIES: - del value + del data[key] return data diff --git a/gns3server/controller/project.py b/gns3server/controller/project.py index e73602d54..98597649d 100644 --- a/gns3server/controller/project.py +++ b/gns3server/controller/project.py @@ -1280,9 +1280,6 @@ class Project: :returns: New node """ - if node.status != "stopped" and not node.is_always_running(): - raise ControllerError("Cannot duplicate node data while the node is running") - data = copy.deepcopy(node.asdict(topology_dump=True)) # Some properties like internal ID should not be duplicated for unique_property in ( From 0aa6566e50bcd0970f0ee09dc9ac8b9e7d14da2d Mon Sep 17 00:00:00 2001 From: grossmj Date: Tue, 17 Mar 2026 12:49:38 +0800 Subject: [PATCH 2/3] Prevent router to be duplicated when running and add tests --- gns3server/compute/base_manager.py | 1 - gns3server/compute/dynamips/__init__.py | 5 ++++- tests/compute/dynamips/test_dynamips_manager.py | 3 +++ tests/compute/test_manager.py | 4 ++++ 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/gns3server/compute/base_manager.py b/gns3server/compute/base_manager.py index a3ee38cec..076b802d1 100644 --- a/gns3server/compute/base_manager.py +++ b/gns3server/compute/base_manager.py @@ -35,7 +35,6 @@ from gns3server.utils.interfaces import is_interface_up from uuid import UUID, uuid4 from typing import Type from ..config import Config -from ..utils.asyncio import wait_run_in_executor from ..utils import force_unix_path from .project_manager import ProjectManager from .port_manager import PortManager diff --git a/gns3server/compute/dynamips/__init__.py b/gns3server/compute/dynamips/__init__.py index 4362b9be1..c9fa71483 100644 --- a/gns3server/compute/dynamips/__init__.py +++ b/gns3server/compute/dynamips/__init__.py @@ -32,7 +32,7 @@ import re log = logging.getLogger(__name__) -from gns3server.utils.interfaces import interfaces, is_interface_up +from gns3server.utils.interfaces import is_interface_up from gns3server.utils.asyncio import wait_run_in_executor, subprocess_check_output from gns3server.utils import parse_version from uuid import uuid4 @@ -644,6 +644,9 @@ class Dynamips(BaseManager): if not hasattr(source_node, "startup_config_path"): return await super().duplicate_node(source_node_id, destination_node_id) + if hasattr(source_node, "status") and source_node.status != "stopped": + raise DynamipsError("Cannot duplicate router data while the route is running") + try: with open(source_node.startup_config_path) as f: startup_config = f.read() diff --git a/tests/compute/dynamips/test_dynamips_manager.py b/tests/compute/dynamips/test_dynamips_manager.py index 94fd03e23..26fc280ca 100644 --- a/tests/compute/dynamips/test_dynamips_manager.py +++ b/tests/compute/dynamips/test_dynamips_manager.py @@ -128,3 +128,6 @@ async def test_duplicate_node(manager, compute_project): with open(destination_node.startup_config_path) as f: content = f.read() assert content == '!\nhostname R2\necho TEST' + with pytest.raises(DynamipsError): + source_node.status = "started" + await manager.duplicate_node(source_node.id, destination_node.id) diff --git a/tests/compute/test_manager.py b/tests/compute/test_manager.py index 0a2a81234..cddbeb3ea 100644 --- a/tests/compute/test_manager.py +++ b/tests/compute/test_manager.py @@ -26,6 +26,7 @@ from gns3server.compute.vpcs import VPCS from gns3server.compute.dynamips import Dynamips from gns3server.compute.qemu import Qemu from gns3server.compute.error import NodeError, ImageMissingError +from gns3server.compute.compute_error import ComputeError from gns3server.utils import force_unix_path @@ -290,6 +291,9 @@ async def test_duplicate_vpcs(vpcs, compute_project): with open(os.path.join(destination_node.working_dir, "startup.vpc")) as f: startup = f.read().strip() assert startup == "set pcname PC-2\nip dhcp\n".strip() + with pytest.raises(ComputeError): + source_node.status = "started" + await vpcs.duplicate_node(source_node_id, destination_node_id) @pytest.mark.asyncio From 4f863f37674a316474d015d5b49d5c757a36cff3 Mon Sep 17 00:00:00 2001 From: grossmj Date: Tue, 17 Mar 2026 12:51:36 +0800 Subject: [PATCH 3/3] Fix typo --- gns3server/compute/dynamips/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gns3server/compute/dynamips/__init__.py b/gns3server/compute/dynamips/__init__.py index c9fa71483..3d620b6a2 100644 --- a/gns3server/compute/dynamips/__init__.py +++ b/gns3server/compute/dynamips/__init__.py @@ -645,7 +645,7 @@ class Dynamips(BaseManager): return await super().duplicate_node(source_node_id, destination_node_id) if hasattr(source_node, "status") and source_node.status != "stopped": - raise DynamipsError("Cannot duplicate router data while the route is running") + raise DynamipsError("Cannot duplicate router data while the router is running") try: with open(source_node.startup_config_path) as f: