diff --git a/gns3server/compute/base_manager.py b/gns3server/compute/base_manager.py index 17a41fe7e..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 @@ -221,6 +220,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..3d620b6a2 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 @@ -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: @@ -640,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 router is running") + try: with open(source_node.startup_config_path) as f: startup_config = f.read() 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 ( 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