From 1d715e146b9fdd40218b2fb7e409b758e728ecdd Mon Sep 17 00:00:00 2001 From: YueGuobin Date: Sun, 3 May 2026 11:06:02 +0800 Subject: [PATCH 1/6] fix: add duplicate name validation when updating templates Fixes #1658 Add validation in the update_template method to check if a template with the same name already exists before updating. This prevents users from creating duplicate template names by editing existing templates. The check excludes the current template being edited to allow updating other properties without changing the name. --- gns3server/services/templates.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/gns3server/services/templates.py b/gns3server/services/templates.py index d2856c0d8..92b78633e 100644 --- a/gns3server/services/templates.py +++ b/gns3server/services/templates.py @@ -281,6 +281,14 @@ class TemplatesService: if not db_template: raise ControllerNotFoundError(f"Template '{template_id}' not found") + # Check for duplicate name (excluding current template) + if template_update.name is not None: + existing_template = await self._templates_repo.get_template_by_name_and_version( + template_update.name, db_template.version + ) + if existing_template and existing_template.template_id != template_id: + raise ControllerError(f"A template with name '{template_update.name}' already exists") + try: # validate the update settings update_settings = jsonable_encoder(template_update, exclude_unset=True) From 952ef66a6b1c3686764c61ea66b603e634485d9f Mon Sep 17 00:00:00 2001 From: YueGuobin Date: Sun, 3 May 2026 17:21:03 +0800 Subject: [PATCH 2/6] fix: handle directory access for static web-ui routes When accessing /static/web-ui without trailing slash, the request would fail with "RuntimeError: File at path ... is not a file" because: 1. The route /static/web-ui/{file_path:path} doesn't match paths without trailing slash (Starlette's path regex requires the /) 2. The request falls through to StaticFiles mount, which tries to serve the directory as a file This fix: - Sets html=True on StaticFiles mount to automatically redirect directory URLs to trailing slash versions - Adds os.path.isdir() check to handle empty file_path gracefully Fixes #2680 Co-Authored-By: Claude Sonnet 4.6 --- gns3server/api/routes/index.py | 2 +- gns3server/api/server.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gns3server/api/routes/index.py b/gns3server/api/routes/index.py index ebd22c096..365840b8c 100644 --- a/gns3server/api/routes/index.py +++ b/gns3server/api/routes/index.py @@ -52,7 +52,7 @@ async def web_ui(file_path: str): static = get_resource(file_path) - if static is None or not os.path.exists(static): + if static is None or not os.path.exists(static) or os.path.isdir(static): static = get_resource(os.path.join("static", "web-ui", "index.html")) if static is None: diff --git a/gns3server/api/server.py b/gns3server/api/server.py index 224a8314b..196ddb504 100644 --- a/gns3server/api/server.py +++ b/gns3server/api/server.py @@ -73,7 +73,7 @@ def get_application() -> FastAPI: application.include_router(index.router, tags=["Index"]) application.include_router(controller.router, prefix="/v3") - application.mount("/static", StaticFiles(packages=[('gns3server', 'static')]), name="static") + application.mount("/static", StaticFiles(packages=[('gns3server', 'static')], html=True), name="static") application.mount("/v3/compute", compute_api, name="compute") return application From 11fb5de84e0634a131b786cccb77ae682f72e531 Mon Sep 17 00:00:00 2001 From: YueGuobin Date: Sun, 3 May 2026 17:44:47 +0800 Subject: [PATCH 3/6] fix: send relative image path to remote compute nodes When creating a node on a remote compute, the controller now sends only the image filename instead of the absolute local path. The remote compute will search for the image in its own configured images_directories. If the image is not found, the compute returns ImageMissingError, which triggers the controller's automatic image upload mechanism. This fixes issue #2676 where remote computes reject paths from the controller due to mismatched directory prefixes. Co-Authored-By: Claude Sonnet 4.6 --- gns3server/controller/node.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/gns3server/controller/node.py b/gns3server/controller/node.py index a047ad253..02c7f2cf0 100644 --- a/gns3server/controller/node.py +++ b/gns3server/controller/node.py @@ -539,6 +539,12 @@ class Node: del data[k] del self._properties[k] # We send the file only one time data["name"] = self._name + + # For remote computes, convert absolute image paths to relative paths + # The remote compute will search for the image in its own configured directories + if self._compute.id != "local" and "path" in data and os.path.isabs(data["path"]): + data["path"] = os.path.basename(data["path"]) + if self._console: # console is optional for builtin nodes data["console"] = self._console From 6ff4f575bb997dd7d0fd3dbecdb9686ecd7979ef Mon Sep 17 00:00:00 2001 From: YueGuobin Date: Sun, 3 May 2026 17:47:25 +0800 Subject: [PATCH 4/6] docs: add analysis for issue #2676 remote compute image path validation Add detailed documentation explaining: - Problem description and root cause - Architecture overview (Controller vs Compute) - Why automatic upload is not triggered - Affected node types - Solution implementation - Testing scenarios Co-Authored-By: Claude Sonnet 4.6 --- ...76-remote-compute-image-path-validation.md | 172 ++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 docs/bugs/issue-2676-remote-compute-image-path-validation.md diff --git a/docs/bugs/issue-2676-remote-compute-image-path-validation.md b/docs/bugs/issue-2676-remote-compute-image-path-validation.md new file mode 100644 index 000000000..db6589aa8 --- /dev/null +++ b/docs/bugs/issue-2676-remote-compute-image-path-validation.md @@ -0,0 +1,172 @@ +# Issue #2676: 远程计算节点镜像路径验证失败 + +## 问题描述 + +当在远程计算节点上创建 IOU 节点时,控制器将其本地绝对镜像路径发送给计算节点,但计算节点拒绝该路径,因为它与计算节点配置的镜像目录不匹配。 + +## 错误信息 + +``` +'/home/gns3/GNS3/images/IOU/i86bi-linux-l3-adventerprisek9-15.4.1T.bin' is not allowed on this remote server. Please only use a file from '/home/yueguobin/GNS3/images/IOU' +``` + +## 根本原因 + +### 架构说明 + +GNS3 采用控制器-计算节点分离架构: +- **Controller(控制节点)**:管理拓扑,负责节点创建、连接等逻辑 +- **Compute(计算节点)**:实际运行虚拟机,执行节点操作 + +### 问题详细分析 + +#### 1. 控制器发送绝对路径 + +在 `gns3server/controller/node.py` 的 `_node_data()` 方法中,控制器准备节点数据时,直接将本地镜像的绝对路径发送给计算节点: + +```python +# 控制器端的镜像路径是控制器的本地路径 +path = "/home/gns3/GNS3/images/IOU/i86bi-linux-l3-adventerprisek9-15.4.1T.bin" +data["path"] = path # 发送给计算节点 +``` + +#### 2. 计算节点验证路径 + +在 `gns3server/compute/base_manager.py` 的 `get_abs_image_path()` 方法中(第448-456行): + +```python +# 如果是绝对路径,检查是否在计算节点的允许目录中 +if os.path.isabs(orig_path): + for directory in valid_directory_prefices: + if os.path.commonprefix([directory, path]) == directory: + if os.path.exists(path): + return path + raise ImageMissingError(orig_path) + # 路径前缀不匹配,抛出错误 + raise NodeError(f"'{path}' is not allowed on this remote server...") +``` + +#### 3. 路径不匹配 + +- **控制器** 的镜像目录:`/home/gns3/GNS3/images` +- **计算节点** 的镜像目录:`/home/yueguobin/GNS3/images` +- 路径前缀检查 `os.path.commonprefix([directory, path]) == directory` 失败 +- 抛出 `NodeError`,节点创建失败 + +### 为什么不触发镜像上传? + +控制器有自动上传缺失镜像的机制(`_upload_missing_image()`),但这个机制只在收到 `ImageMissingError` 时触发。 + +由于计算节点抛出的是 `NodeError` 而不是 `ImageMissingError`,控制器无法触发自动上传逻辑。 + +## 影响范围 + +此问题影响所有使用镜像文件的节点类型: + +| 节点类型 | 镜像路径字段 | 状态 | +|---------|------------|------| +| **IOU** | `path` | ❌ 受影响 | +| **QEMU** | `hda_disk_image`, `hdb_disk_image`, `hdc_disk_image`, `hdd_disk_image`
`cdrom_image`, `bios_image`, `initrd`, `kernel_image` | ❌ 受影响 | +| **Dynamips (IOS)** | `image` | ❌ 受影响 | +| **VMware** | `vmx_path` | ❌ 受影响 | +| **Docker** | `image` (Docker 镜像名称,非文件路径) | ✅ 不受影响 | + +## 解决方案 + +### 实现方案 + +修改控制器端的 `_node_data()` 方法,对远程计算节点发送相对路径(仅文件名)而非绝对路径。 + +**文件**: `gns3server/controller/node.py` + +**修改位置**: 第541-546行 + +```python +# For remote computes, convert absolute image paths to relative paths +# The remote compute will search for the image in its own configured directories +if self._compute.id != "local" and "path" in data and os.path.isabs(data["path"]): + data["path"] = os.path.basename(data["path"]) +``` + +### 工作原理 + +#### 1. 控制器发送相对路径 + +```python +# 原来发送:"/home/gns3/GNS3/images/IOU/i86bi-linux-l3-adventerprisek9-15.4.1T.bin" +# 现在发送:"i86bi-linux-l3-adventerprisek9-15.4.1T.bin" +if self._compute.id != "local" and "path" in data and os.path.isabs(data["path"]): + data["path"] = os.path.basename(data["path"]) +``` + +#### 2. 计算节点搜索镜像 + +计算节点的 `get_abs_image_path()` 方法处理相对路径(第432-446行): + +```python +if not os.path.isabs(orig_path): + for directory in valid_directory_prefices: + path = self._recursive_search_file_in_directory(directory, orig_path) + if path: + return force_unix_path(path) + # 未找到,抛出 ImageMissingError + raise ImageMissingError(orig_path) +``` + +计算节点在其配置的镜像目录中搜索: +- 默认目录:`~/GNS3/images/IOU`(从配置文件 `gns3_server.conf` 读取) +- 额外目录:`Server.additional_images_paths` 中配置的目录 + +#### 3. 自动上传机制 + +如果计算节点未找到镜像,返回 `ImageMissingError`,控制器捕获后自动上传: + +```python +except ComputeConflictError as e: + response = e.response() + if response.get("exception") == "ImageMissingError": + res = await self._upload_missing_image(self._node_type, response["image"]) +``` + +### 向后兼容性 + +- **本地计算节点**(`id == "local"`):不受影响,继续使用绝对路径 +- **远程计算节点**(`id != "local"`):使用相对路径,由计算节点在其目录中搜索 + +## 测试场景 + +### 场景 1:镜像已存在于计算节点 + +1. 控制器创建节点,发送相对路径 `i86bi-linux-l3-adventerprisek9-15.4.1T.bin` +2. 计算节点在 `~/GNS3/images/IOU/` 中找到镜像 +3. 节点创建成功 ✅ + +### 场景 2:镜像不存在于计算节点 + +1. 控制器创建节点,发送相对路径 `i86bi-linux-l3-adventerprisek9-15.4.1T.bin` +2. 计算节点未找到镜像,返回 `ImageMissingError` +3. 控制器自动上传镜像到计算节点 +4. 重试创建,节点创建成功 ✅ + +### 场景 3:同名但内容不同的镜像 + +⚠️ **潜在风险**:如果计算节点已存在同名镜像但内容不同(不同版本),不会触发上传,可能使用错误的镜像。 + +**建议改进**:未来可以添加 MD5 校验和验证,确保使用正确的镜像版本。 + +## 当前状态 + +- ✅ IOU 节点已修复 +- ⏳ 其他节点类型(QEMU, Dynamips, VMware)需要类似的修复 + +## 相关文件 + +- `gns3server/controller/node.py` - 控制器节点实现 +- `gns3server/compute/base_manager.py` - 计算节点基础管理器 +- `gns3server/compute/iou/iou_vm.py` - IOU 节点实现 +- `gns3server/utils/images.py` - 镜像工具函数 + +## 参考 + +- GitHub Issue: #2676 +- 修复分支: `fix/iou-remote-path-validation` From 38f5e8df76bee626d955a5f0a8af123988a82c4f Mon Sep 17 00:00:00 2001 From: YueGuobin Date: Mon, 4 May 2026 15:39:16 +0800 Subject: [PATCH 5/6] fix: extend remote compute image path conversion to all node types Previously only the "path" field was converted to a relative path for remote computes, causing issues with QEMU and VMware nodes that use different image path fields. Now all known image path fields are converted, ensuring consistent behavior across IOU, QEMU, Dynamips, and VMware node types. --- ...76-remote-compute-image-path-validation.md | 37 ++++++++++++++++--- gns3server/controller/node.py | 17 ++++++++- 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/docs/bugs/issue-2676-remote-compute-image-path-validation.md b/docs/bugs/issue-2676-remote-compute-image-path-validation.md index db6589aa8..39353a122 100644 --- a/docs/bugs/issue-2676-remote-compute-image-path-validation.md +++ b/docs/bugs/issue-2676-remote-compute-image-path-validation.md @@ -75,17 +75,30 @@ if os.path.isabs(orig_path): ### 实现方案 -修改控制器端的 `_node_data()` 方法,对远程计算节点发送相对路径(仅文件名)而非绝对路径。 +修改控制器端的 `_node_data()` 方法,对远程计算节点发送相对路径(仅文件名)而非绝对路径。此修复适用于所有节点类型(IOU、QEMU、Dynamips、VMware)。 **文件**: `gns3server/controller/node.py` -**修改位置**: 第541-546行 +**修改位置**: 第541-558行 ```python # For remote computes, convert absolute image paths to relative paths # The remote compute will search for the image in its own configured directories -if self._compute.id != "local" and "path" in data and os.path.isabs(data["path"]): - data["path"] = os.path.basename(data["path"]) +if self._compute.id != "local": + # Image path fields for various node types + image_path_fields = { + # Common fields (IOU, Docker, etc.) + "path", "image", + # QEMU-specific fields + "hda_disk_image", "hdb_disk_image", "hdc_disk_image", "hdd_disk_image", + "cdrom_image", "bios_image", "initrd", "kernel_image", + # VMware-specific fields + "vmx_path", + } + + for field in image_path_fields: + if field in data and data[field] and os.path.isabs(data[field]): + data[field] = os.path.basename(data[field]) ``` ### 工作原理 @@ -137,17 +150,30 @@ except ComputeConflictError as e: ### 场景 1:镜像已存在于计算节点 +**IOU 节点:** 1. 控制器创建节点,发送相对路径 `i86bi-linux-l3-adventerprisek9-15.4.1T.bin` 2. 计算节点在 `~/GNS3/images/IOU/` 中找到镜像 3. 节点创建成功 ✅ +**QEMU 节点:** +1. 控制器创建节点,发送相对路径 `vios-adventerprisek9-m.qcow2` +2. 计算节点在 `~/GNS3/images/QEMU/` 中找到镜像 +3. 节点创建成功 ✅ + ### 场景 2:镜像不存在于计算节点 +**IOU 节点:** 1. 控制器创建节点,发送相对路径 `i86bi-linux-l3-adventerprisek9-15.4.1T.bin` 2. 计算节点未找到镜像,返回 `ImageMissingError` 3. 控制器自动上传镜像到计算节点 4. 重试创建,节点创建成功 ✅ +**QEMU 节点:** +1. 控制器创建节点,发送相对路径 `vios-adventerprisek9-m.qcow2` +2. 计算节点未找到镜像,返回 `ImageMissingError` +3. 控制器自动上传镜像到计算节点 +4. 重试创建,节点创建成功 ✅ + ### 场景 3:同名但内容不同的镜像 ⚠️ **潜在风险**:如果计算节点已存在同名镜像但内容不同(不同版本),不会触发上传,可能使用错误的镜像。 @@ -156,8 +182,7 @@ except ComputeConflictError as e: ## 当前状态 -- ✅ IOU 节点已修复 -- ⏳ 其他节点类型(QEMU, Dynamips, VMware)需要类似的修复 +- ✅ 所有节点类型已修复(IOU, QEMU, Dynamips, VMware) ## 相关文件 diff --git a/gns3server/controller/node.py b/gns3server/controller/node.py index 02c7f2cf0..3d386f06a 100644 --- a/gns3server/controller/node.py +++ b/gns3server/controller/node.py @@ -542,8 +542,21 @@ class Node: # For remote computes, convert absolute image paths to relative paths # The remote compute will search for the image in its own configured directories - if self._compute.id != "local" and "path" in data and os.path.isabs(data["path"]): - data["path"] = os.path.basename(data["path"]) + if self._compute.id != "local": + # Image path fields for various node types + image_path_fields = { + # Common fields (IOU, Docker, etc.) + "path", "image", + # QEMU-specific fields + "hda_disk_image", "hdb_disk_image", "hdc_disk_image", "hdd_disk_image", + "cdrom_image", "bios_image", "initrd", "kernel_image", + # VMware-specific fields + "vmx_path", + } + + for field in image_path_fields: + if field in data and data[field] and os.path.isabs(data[field]): + data[field] = os.path.basename(data[field]) if self._console: # console is optional for builtin nodes From 1f5ba3cada925dc556a2afc3cca393da5121f7a9 Mon Sep 17 00:00:00 2001 From: YueGuobin Date: Mon, 4 May 2026 15:48:46 +0800 Subject: [PATCH 6/6] chore: remove internal debug documentation --- ...76-remote-compute-image-path-validation.md | 197 ------------------ 1 file changed, 197 deletions(-) delete mode 100644 docs/bugs/issue-2676-remote-compute-image-path-validation.md diff --git a/docs/bugs/issue-2676-remote-compute-image-path-validation.md b/docs/bugs/issue-2676-remote-compute-image-path-validation.md deleted file mode 100644 index 39353a122..000000000 --- a/docs/bugs/issue-2676-remote-compute-image-path-validation.md +++ /dev/null @@ -1,197 +0,0 @@ -# Issue #2676: 远程计算节点镜像路径验证失败 - -## 问题描述 - -当在远程计算节点上创建 IOU 节点时,控制器将其本地绝对镜像路径发送给计算节点,但计算节点拒绝该路径,因为它与计算节点配置的镜像目录不匹配。 - -## 错误信息 - -``` -'/home/gns3/GNS3/images/IOU/i86bi-linux-l3-adventerprisek9-15.4.1T.bin' is not allowed on this remote server. Please only use a file from '/home/yueguobin/GNS3/images/IOU' -``` - -## 根本原因 - -### 架构说明 - -GNS3 采用控制器-计算节点分离架构: -- **Controller(控制节点)**:管理拓扑,负责节点创建、连接等逻辑 -- **Compute(计算节点)**:实际运行虚拟机,执行节点操作 - -### 问题详细分析 - -#### 1. 控制器发送绝对路径 - -在 `gns3server/controller/node.py` 的 `_node_data()` 方法中,控制器准备节点数据时,直接将本地镜像的绝对路径发送给计算节点: - -```python -# 控制器端的镜像路径是控制器的本地路径 -path = "/home/gns3/GNS3/images/IOU/i86bi-linux-l3-adventerprisek9-15.4.1T.bin" -data["path"] = path # 发送给计算节点 -``` - -#### 2. 计算节点验证路径 - -在 `gns3server/compute/base_manager.py` 的 `get_abs_image_path()` 方法中(第448-456行): - -```python -# 如果是绝对路径,检查是否在计算节点的允许目录中 -if os.path.isabs(orig_path): - for directory in valid_directory_prefices: - if os.path.commonprefix([directory, path]) == directory: - if os.path.exists(path): - return path - raise ImageMissingError(orig_path) - # 路径前缀不匹配,抛出错误 - raise NodeError(f"'{path}' is not allowed on this remote server...") -``` - -#### 3. 路径不匹配 - -- **控制器** 的镜像目录:`/home/gns3/GNS3/images` -- **计算节点** 的镜像目录:`/home/yueguobin/GNS3/images` -- 路径前缀检查 `os.path.commonprefix([directory, path]) == directory` 失败 -- 抛出 `NodeError`,节点创建失败 - -### 为什么不触发镜像上传? - -控制器有自动上传缺失镜像的机制(`_upload_missing_image()`),但这个机制只在收到 `ImageMissingError` 时触发。 - -由于计算节点抛出的是 `NodeError` 而不是 `ImageMissingError`,控制器无法触发自动上传逻辑。 - -## 影响范围 - -此问题影响所有使用镜像文件的节点类型: - -| 节点类型 | 镜像路径字段 | 状态 | -|---------|------------|------| -| **IOU** | `path` | ❌ 受影响 | -| **QEMU** | `hda_disk_image`, `hdb_disk_image`, `hdc_disk_image`, `hdd_disk_image`
`cdrom_image`, `bios_image`, `initrd`, `kernel_image` | ❌ 受影响 | -| **Dynamips (IOS)** | `image` | ❌ 受影响 | -| **VMware** | `vmx_path` | ❌ 受影响 | -| **Docker** | `image` (Docker 镜像名称,非文件路径) | ✅ 不受影响 | - -## 解决方案 - -### 实现方案 - -修改控制器端的 `_node_data()` 方法,对远程计算节点发送相对路径(仅文件名)而非绝对路径。此修复适用于所有节点类型(IOU、QEMU、Dynamips、VMware)。 - -**文件**: `gns3server/controller/node.py` - -**修改位置**: 第541-558行 - -```python -# For remote computes, convert absolute image paths to relative paths -# The remote compute will search for the image in its own configured directories -if self._compute.id != "local": - # Image path fields for various node types - image_path_fields = { - # Common fields (IOU, Docker, etc.) - "path", "image", - # QEMU-specific fields - "hda_disk_image", "hdb_disk_image", "hdc_disk_image", "hdd_disk_image", - "cdrom_image", "bios_image", "initrd", "kernel_image", - # VMware-specific fields - "vmx_path", - } - - for field in image_path_fields: - if field in data and data[field] and os.path.isabs(data[field]): - data[field] = os.path.basename(data[field]) -``` - -### 工作原理 - -#### 1. 控制器发送相对路径 - -```python -# 原来发送:"/home/gns3/GNS3/images/IOU/i86bi-linux-l3-adventerprisek9-15.4.1T.bin" -# 现在发送:"i86bi-linux-l3-adventerprisek9-15.4.1T.bin" -if self._compute.id != "local" and "path" in data and os.path.isabs(data["path"]): - data["path"] = os.path.basename(data["path"]) -``` - -#### 2. 计算节点搜索镜像 - -计算节点的 `get_abs_image_path()` 方法处理相对路径(第432-446行): - -```python -if not os.path.isabs(orig_path): - for directory in valid_directory_prefices: - path = self._recursive_search_file_in_directory(directory, orig_path) - if path: - return force_unix_path(path) - # 未找到,抛出 ImageMissingError - raise ImageMissingError(orig_path) -``` - -计算节点在其配置的镜像目录中搜索: -- 默认目录:`~/GNS3/images/IOU`(从配置文件 `gns3_server.conf` 读取) -- 额外目录:`Server.additional_images_paths` 中配置的目录 - -#### 3. 自动上传机制 - -如果计算节点未找到镜像,返回 `ImageMissingError`,控制器捕获后自动上传: - -```python -except ComputeConflictError as e: - response = e.response() - if response.get("exception") == "ImageMissingError": - res = await self._upload_missing_image(self._node_type, response["image"]) -``` - -### 向后兼容性 - -- **本地计算节点**(`id == "local"`):不受影响,继续使用绝对路径 -- **远程计算节点**(`id != "local"`):使用相对路径,由计算节点在其目录中搜索 - -## 测试场景 - -### 场景 1:镜像已存在于计算节点 - -**IOU 节点:** -1. 控制器创建节点,发送相对路径 `i86bi-linux-l3-adventerprisek9-15.4.1T.bin` -2. 计算节点在 `~/GNS3/images/IOU/` 中找到镜像 -3. 节点创建成功 ✅ - -**QEMU 节点:** -1. 控制器创建节点,发送相对路径 `vios-adventerprisek9-m.qcow2` -2. 计算节点在 `~/GNS3/images/QEMU/` 中找到镜像 -3. 节点创建成功 ✅ - -### 场景 2:镜像不存在于计算节点 - -**IOU 节点:** -1. 控制器创建节点,发送相对路径 `i86bi-linux-l3-adventerprisek9-15.4.1T.bin` -2. 计算节点未找到镜像,返回 `ImageMissingError` -3. 控制器自动上传镜像到计算节点 -4. 重试创建,节点创建成功 ✅ - -**QEMU 节点:** -1. 控制器创建节点,发送相对路径 `vios-adventerprisek9-m.qcow2` -2. 计算节点未找到镜像,返回 `ImageMissingError` -3. 控制器自动上传镜像到计算节点 -4. 重试创建,节点创建成功 ✅ - -### 场景 3:同名但内容不同的镜像 - -⚠️ **潜在风险**:如果计算节点已存在同名镜像但内容不同(不同版本),不会触发上传,可能使用错误的镜像。 - -**建议改进**:未来可以添加 MD5 校验和验证,确保使用正确的镜像版本。 - -## 当前状态 - -- ✅ 所有节点类型已修复(IOU, QEMU, Dynamips, VMware) - -## 相关文件 - -- `gns3server/controller/node.py` - 控制器节点实现 -- `gns3server/compute/base_manager.py` - 计算节点基础管理器 -- `gns3server/compute/iou/iou_vm.py` - IOU 节点实现 -- `gns3server/utils/images.py` - 镜像工具函数 - -## 参考 - -- GitHub Issue: #2676 -- 修复分支: `fix/iou-remote-path-validation`