Merge pull request #2640 from UmmmAGoodName/Dymamips_fix-3.0

Fixed issue #1605 regarding Cisco slots causing configs to break
This commit is contained in:
Jeremy Grossmann 2026-03-17 12:54:45 +08:00 committed by GitHub
commit 0d1341b4e0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 30 additions and 17 deletions

View File

@ -35,7 +35,6 @@ from gns3server.utils.interfaces import is_interface_up
from uuid import UUID, uuid4 from uuid import UUID, uuid4
from typing import Type from typing import Type
from ..config import Config from ..config import Config
from ..utils.asyncio import wait_run_in_executor
from ..utils import force_unix_path from ..utils import force_unix_path
from .project_manager import ProjectManager from .project_manager import ProjectManager
from .port_manager import PortManager from .port_manager import PortManager
@ -221,6 +220,9 @@ class BaseManager:
if not hasattr(destination_node, "working_dir"): if not hasattr(destination_node, "working_dir"):
return destination_node 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 destination_dir = destination_node.working_dir
try: try:
shutil.rmtree(destination_dir) shutil.rmtree(destination_dir)

View File

@ -32,7 +32,7 @@ import re
log = logging.getLogger(__name__) 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.asyncio import wait_run_in_executor, subprocess_check_output
from gns3server.utils import parse_version from gns3server.utils import parse_version
from uuid import uuid4 from uuid import uuid4
@ -478,36 +478,40 @@ class Dynamips(BaseManager):
adapter = ADAPTER_MATRIX[adapter_name]() adapter = ADAPTER_MATRIX[adapter_name]()
try: try:
if vm.slots[slot_id] and not isinstance(vm.slots[slot_id], type(adapter)): 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)): if not isinstance(vm.slots[slot_id], type(adapter)):
await vm.slot_add_binding(slot_id, adapter) await vm.slot_add_binding(slot_id, adapter)
except IndexError: 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 == ""): elif name.startswith("slot") and (value is None or value == ""):
slot_id = int(name[-1]) slot_id = int(name[-1])
try: try:
if vm.slots[slot_id]: if vm.slots[slot_id] and vm.slots[slot_id].removable():
await vm.slot_remove_binding(slot_id) await vm.slot_remove_binding(slot_id)
except IndexError: 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: elif name.startswith("wic") and value in WIC_MATRIX:
wic_slot_id = int(name[-1]) wic_slot_id = int(name[-1])
wic_name = value wic_name = value
wic = WIC_MATRIX[wic_name]() wic = WIC_MATRIX[wic_name]()
try: 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) 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) await vm.install_wic(wic_slot_id, wic)
except IndexError: except (IndexError, AttributeError):
raise DynamipsError(f"WIC slot {wic_slot_id} doesn't exist on this router") 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 == ""): elif name.startswith("wic") and (value is None or value == ""):
wic_slot_id = int(name[-1]) wic_slot_id = int(name[-1])
try: 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) await vm.uninstall_wic(wic_slot_id)
except IndexError: except (IndexError, AttributeError):
raise DynamipsError(f"WIC slot {wic_slot_id} doesn't exist on this router") log.warning(f"WIC slot {wic_slot_id} doesn't exist on router '{vm.name}', skipping")
mmap_support = self.config.settings.Dynamips.mmap_support mmap_support = self.config.settings.Dynamips.mmap_support
if mmap_support is False: if mmap_support is False:
@ -640,6 +644,9 @@ class Dynamips(BaseManager):
if not hasattr(source_node, "startup_config_path"): if not hasattr(source_node, "startup_config_path"):
return await super().duplicate_node(source_node_id, destination_node_id) 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: try:
with open(source_node.startup_config_path) as f: with open(source_node.startup_config_path) as f:
startup_config = f.read() startup_config = f.read()

View File

@ -556,7 +556,7 @@ class Node:
# None properties are not be sent because it can mean the emulator doesn't support it # None properties are not be sent because it can mean the emulator doesn't support it
for key, value in list(data.items()): for key, value in list(data.items()):
if value is None or value == {} or key in self.CONTROLLER_ONLY_PROPERTIES: if value is None or value == {} or key in self.CONTROLLER_ONLY_PROPERTIES:
del value del data[key]
return data return data

View File

@ -1280,9 +1280,6 @@ class Project:
:returns: New node :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)) data = copy.deepcopy(node.asdict(topology_dump=True))
# Some properties like internal ID should not be duplicated # Some properties like internal ID should not be duplicated
for unique_property in ( for unique_property in (

View File

@ -128,3 +128,6 @@ async def test_duplicate_node(manager, compute_project):
with open(destination_node.startup_config_path) as f: with open(destination_node.startup_config_path) as f:
content = f.read() content = f.read()
assert content == '!\nhostname R2\necho TEST' assert content == '!\nhostname R2\necho TEST'
with pytest.raises(DynamipsError):
source_node.status = "started"
await manager.duplicate_node(source_node.id, destination_node.id)

View File

@ -26,6 +26,7 @@ from gns3server.compute.vpcs import VPCS
from gns3server.compute.dynamips import Dynamips from gns3server.compute.dynamips import Dynamips
from gns3server.compute.qemu import Qemu from gns3server.compute.qemu import Qemu
from gns3server.compute.error import NodeError, ImageMissingError from gns3server.compute.error import NodeError, ImageMissingError
from gns3server.compute.compute_error import ComputeError
from gns3server.utils import force_unix_path 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: with open(os.path.join(destination_node.working_dir, "startup.vpc")) as f:
startup = f.read().strip() startup = f.read().strip()
assert startup == "set pcname PC-2\nip dhcp\n".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 @pytest.mark.asyncio