From 0624c1b9457bc65ba5c324c3dd566addb91c8082 Mon Sep 17 00:00:00 2001 From: grossmj Date: Tue, 10 Mar 2026 20:28:00 +0800 Subject: [PATCH 1/4] Deactivate 'use default IOU values' by default and update RAM/NVRAM values --- gns3server/compute/iou/iou_vm.py | 6 +++--- gns3server/schemas/iou_template.py | 6 +++--- tests/compute/iou/test_iou_vm.py | 2 +- tests/handlers/api/compute/test_iou.py | 8 ++++---- tests/handlers/api/controller/test_template.py | 6 +++--- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/gns3server/compute/iou/iou_vm.py b/gns3server/compute/iou/iou_vm.py index d82776ba6..9089d8e68 100644 --- a/gns3server/compute/iou/iou_vm.py +++ b/gns3server/compute/iou/iou_vm.py @@ -87,11 +87,11 @@ class IOUVM(BaseNode): self._serial_adapters = [] self.ethernet_adapters = 2 # one adapter = 4 interfaces self.serial_adapters = 2 # one adapter = 4 interfaces - self._use_default_iou_values = True # for RAM & NVRAM values - self._nvram = 128 # Kilobytes + self._use_default_iou_values = False # for RAM & NVRAM values + self._nvram = 256 # Kilobytes self._startup_config = "" self._private_config = "" - self._ram = 256 # Megabytes + self._ram = 1024 # Megabytes self._application_id = application_id self._l1_keepalives = False # used to overcome the always-up Ethernet interfaces (not supported by all IOSes). diff --git a/gns3server/schemas/iou_template.py b/gns3server/schemas/iou_template.py index 769234ee5..0a5602658 100644 --- a/gns3server/schemas/iou_template.py +++ b/gns3server/schemas/iou_template.py @@ -43,17 +43,17 @@ IOU_TEMPLATE_PROPERTIES = { "ram": { "description": "RAM in MB", "type": "integer", - "default": 256 + "default": 1024 }, "nvram": { "description": "NVRAM in KB", "type": "integer", - "default": 128 + "default": 256 }, "use_default_iou_values": { "description": "Use default IOU values", "type": "boolean", - "default": True + "default": False }, "startup_config": { "description": "Startup-config of IOU", diff --git a/tests/compute/iou/test_iou_vm.py b/tests/compute/iou/test_iou_vm.py index d3026295a..835f91fcf 100644 --- a/tests/compute/iou/test_iou_vm.py +++ b/tests/compute/iou/test_iou_vm.py @@ -262,7 +262,7 @@ def test_create_netmap_config(vm): async def test_build_command(vm): - assert await vm._build_command() == [vm.path, str(vm.application_id)] + assert await vm._build_command() == [vm.path, "-n", "256", "-m", "1024", str(vm.application_id)] def test_get_startup_config(vm): diff --git a/tests/handlers/api/compute/test_iou.py b/tests/handlers/api/compute/test_iou.py index aadb4fa04..97230143b 100644 --- a/tests/handlers/api/compute/test_iou.py +++ b/tests/handlers/api/compute/test_iou.py @@ -69,8 +69,8 @@ async def test_iou_create(compute_api, compute_project, base_params): assert response.json["project_id"] == compute_project.id assert response.json["serial_adapters"] == 2 assert response.json["ethernet_adapters"] == 2 - assert response.json["ram"] == 256 - assert response.json["nvram"] == 128 + assert response.json["ram"] == 1024 + assert response.json["nvram"] == 256 assert response.json["l1_keepalives"] is False @@ -130,8 +130,8 @@ async def test_iou_get(compute_api, compute_project, vm): assert response.json["project_id"] == compute_project.id assert response.json["serial_adapters"] == 2 assert response.json["ethernet_adapters"] == 2 - assert response.json["ram"] == 256 - assert response.json["nvram"] == 128 + assert response.json["ram"] == 1024 + assert response.json["nvram"] == 256 assert response.json["l1_keepalives"] is False diff --git a/tests/handlers/api/controller/test_template.py b/tests/handlers/api/controller/test_template.py index a80d7d090..2accbffde 100644 --- a/tests/handlers/api/controller/test_template.py +++ b/tests/handlers/api/controller/test_template.py @@ -592,14 +592,14 @@ async def test_iou_template_create(controller_api): "default_name_format": "IOU{0}", "ethernet_adapters": 2, "name": "IOU template", - "nvram": 128, + "nvram": 256, "path": "/path/to/i86bi_linux-ipbase-ms-12.4.bin", "private_config": "", - "ram": 256, + "ram": 1024, "serial_adapters": 2, "startup_config": "iou_l3_base_startup-config.txt", "symbol": ":/symbols/multilayer_switch.svg", - "use_default_iou_values": True, + "use_default_iou_values": False, "l1_keepalives": False} for item, value in expected_response.items(): From f2fd725e07f29d1aefbeade13acce9e716094d87 Mon Sep 17 00:00:00 2001 From: YueGuobin Date: Wed, 11 Mar 2026 23:23:52 +0800 Subject: [PATCH 2/4] fix(acl): correct endpoint paths for users, groups, and roles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix the path mismatch between /acl/endpoints API and actual routes: - Users: /users/{id} → /access/users/{id} - Groups: /groups/{id} → /access/groups/{id} - Roles: /roles/{id} → /access/roles/{id} This fixes the error where creating ACE entries fails with: "Path '/groups/{id}' doesn't match any existing endpoint" The actual routes are registered under /access/ prefix, but the endpoints API was returning paths without the prefix. Co-Authored-By: Yue Guobin --- gns3server/api/routes/controller/acl.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gns3server/api/routes/controller/acl.py b/gns3server/api/routes/controller/acl.py index ae33bd09e..f4b0e8897 100644 --- a/gns3server/api/routes/controller/acl.py +++ b/gns3server/api/routes/controller/acl.py @@ -115,19 +115,19 @@ async def endpoints( add_to_endpoints("/access/users", "All users", "user") users = await users_repo.get_users() for user in users: - add_to_endpoints(f"/users/{user.user_id}", f'User "{user.username}"', "user") + add_to_endpoints(f"/access/users/{user.user_id}", f'User "{user.username}"', "user") # groups add_to_endpoints("/access/groups", "All groups", "group") groups = await users_repo.get_user_groups() for group in groups: - add_to_endpoints(f"/groups/{group.user_group_id}", f'Group "{group.name}"', "group") + add_to_endpoints(f"/access/groups/{group.user_group_id}", f'Group "{group.name}"', "group") # roles add_to_endpoints("/access/roles", "All roles", "role") roles = await rbac_repo.get_roles() for role in roles: - add_to_endpoints(f"/roles/{role.role_id}", f'Role "{role.name}"', "role") + add_to_endpoints(f"/access/roles/{role.role_id}", f'Role "{role.name}"', "role") # images add_to_endpoints("/images", "All images", "image") From f41cc24383ae621b16941248384c87e528569915 Mon Sep 17 00:00:00 2001 From: grossmj Date: Thu, 12 Mar 2026 13:24:30 +0800 Subject: [PATCH 3/4] Fix for duplicating Qemu nodes --- gns3server/api/routes/compute/qemu_nodes.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/gns3server/api/routes/compute/qemu_nodes.py b/gns3server/api/routes/compute/qemu_nodes.py index e8be9b003..4b9da897f 100644 --- a/gns3server/api/routes/compute/qemu_nodes.py +++ b/gns3server/api/routes/compute/qemu_nodes.py @@ -32,6 +32,9 @@ from gns3server.compute.qemu.qemu_vm import QemuVM from .dependencies.authentication import compute_authentication, ws_compute_authentication +import logging +log = logging.getLogger(__name__) + responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or Qemu node"}} router = APIRouter(responses=responses) @@ -74,6 +77,15 @@ async def create_qemu_node(project_id: UUID, node_data: schemas.QemuCreate) -> s platform=node_data.pop("platform", None), ) + # update the disk image with the backing file if provided + # this is needed when duplicating a node that uses backed disk images + drives = ["a", "b", "c", "d"] + for disk_index, drive in enumerate(drives): + disk_image_backing_file = node_data.get(f"hd{drive}_disk_image_backing_file") + if disk_image_backing_file: + log.info(f"Updating disk image for drive {drive} with backing file {disk_image_backing_file}") + node_data[f"hd{drive}_disk_image"] = disk_image_backing_file + for name, value in node_data.items(): if hasattr(vm, name) and getattr(vm, name) != value: setattr(vm, name, value) From 3abecc849299c11d778704643ea03cafef57d66a Mon Sep 17 00:00:00 2001 From: grossmj Date: Thu, 12 Mar 2026 14:18:35 +0800 Subject: [PATCH 4/4] Fix IOU tests --- gns3server/schemas/compute/iou_nodes.py | 2 +- gns3server/schemas/controller/templates/iou_templates.py | 6 +++--- tests/api/routes/compute/test_iou_nodes.py | 8 ++++---- tests/api/routes/controller/test_templates.py | 6 +++--- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/gns3server/schemas/compute/iou_nodes.py b/gns3server/schemas/compute/iou_nodes.py index 7e3d3a91b..fe3fe8570 100644 --- a/gns3server/schemas/compute/iou_nodes.py +++ b/gns3server/schemas/compute/iou_nodes.py @@ -39,7 +39,7 @@ class IOUBase(BaseModel): ram: Optional[int] = Field(None, gt=0, description="Amount of RAM in MB") nvram: Optional[int] = Field(None, gt=0, description="Amount of NVRAM in KB") l1_keepalives: Optional[bool] = Field(None, description="Use default IOU values") - use_default_iou_values: Optional[bool] = Field(None, description="Always up Ethernet interfaces") + use_default_iou_values: Optional[bool] = Field(None, description="Use default IOU values") startup_config_content: Optional[str] = Field(None, description="Content of IOU startup configuration file") private_config_content: Optional[str] = Field(None, description="Content of IOU private configuration file") diff --git a/gns3server/schemas/controller/templates/iou_templates.py b/gns3server/schemas/controller/templates/iou_templates.py index e906f74f6..6dd83d14b 100644 --- a/gns3server/schemas/controller/templates/iou_templates.py +++ b/gns3server/schemas/controller/templates/iou_templates.py @@ -30,9 +30,9 @@ class IOUTemplate(TemplateBase): path: str = Field(..., description="Path of IOU executable") ethernet_adapters: Optional[int] = Field(2, ge=0, description="Number of ethernet adapters") serial_adapters: Optional[int] = Field(2, ge=0, description="Number of serial adapters") - ram: Optional[int] = Field(256, gt=0, description="Amount of RAM in MB") - nvram: Optional[int] = Field(128, gt=0, description="Amount of NVRAM in KB") - use_default_iou_values: Optional[bool] = Field(True, description="Use default IOU values") + ram: Optional[int] = Field(1024, gt=0, description="Amount of RAM in MB") + nvram: Optional[int] = Field(256, gt=0, description="Amount of NVRAM in KB") + use_default_iou_values: Optional[bool] = Field(False, description="Use default IOU values") startup_config: Optional[str] = Field("iou_l3_base_startup-config.txt", description="Startup-config of IOU") private_config: Optional[str] = Field("", description="Private-config of IOU") l1_keepalives: Optional[bool] = Field(False, description="Always keep up Ethernet interface (does not always work)") diff --git a/tests/api/routes/compute/test_iou_nodes.py b/tests/api/routes/compute/test_iou_nodes.py index b0de85b57..75fa6bdf0 100644 --- a/tests/api/routes/compute/test_iou_nodes.py +++ b/tests/api/routes/compute/test_iou_nodes.py @@ -86,8 +86,8 @@ class TestIOUNodesRoutes: assert response.json()["project_id"] == compute_project.id assert response.json()["serial_adapters"] == 2 assert response.json()["ethernet_adapters"] == 2 - assert response.json()["ram"] == 256 - assert response.json()["nvram"] == 128 + assert response.json()["ram"] == 1024 + assert response.json()["nvram"] == 256 assert response.json()["l1_keepalives"] is False @@ -190,8 +190,8 @@ class TestIOUNodesRoutes: assert response.json()["project_id"] == compute_project.id assert response.json()["serial_adapters"] == 2 assert response.json()["ethernet_adapters"] == 2 - assert response.json()["ram"] == 256 - assert response.json()["nvram"] == 128 + assert response.json()["ram"] == 1024 + assert response.json()["nvram"] == 256 assert response.json()["l1_keepalives"] is False diff --git a/tests/api/routes/controller/test_templates.py b/tests/api/routes/controller/test_templates.py index 38f01fb59..ba7055755 100644 --- a/tests/api/routes/controller/test_templates.py +++ b/tests/api/routes/controller/test_templates.py @@ -718,14 +718,14 @@ class TestIOUTemplate: "default_name_format": "IOU{0}", "ethernet_adapters": 2, "name": "IOU template", - "nvram": 128, + "nvram": 256, "path": image_path, "private_config": "", - "ram": 256, + "ram": 1024, "serial_adapters": 2, "startup_config": "iou_l3_base_startup-config.txt", "symbol": unittest.mock.ANY, - "use_default_iou_values": True, + "use_default_iou_values": False, "l1_keepalives": False} for item, value in expected_response.items():