GNS3 2.2.61 REST API 使用详解
基于 2.2.61 实机验证的 /v2 接口指南:Basic 鉴权、computes 与 capabilities 失真、工程 open/close 与 auto_close、节点与链路创建、qemu_path 必填、系统盘文件上传 204 与无删除接口、WebSocket 心跳与 node.updated 推送,以及 400/401/403/404/405/409 原样报错与端到端脚本。
GNS3 2.2.61 REST API 使用详解
目标:把 GNS3 2.2.61 的
/v2REST 接口与 WebSocket 通知讲透——怎么认证、怎么建工程、怎么建节点与链路、怎么上传系统盘文件、怎么订阅状态变化,以及每个错误码背后真实的原因。本文所有请求与响应均来自 2.2.61 实机(Ubuntu 22.04.5,服务端监听 3080),接口对应的服务端版本为2.2.61;组件构成见 《GNS3 2.2.61 介绍与选型指南》,部署见 《Ubuntu 22.04 安装 GNS3 2.2.61 服务端指南》,参数含义见 《GNS3 2.2.61 配置参数详解》。
一、接口约定与认证
1.1 基础约定
| 项目 | 值 |
|---|---|
| 基础路径 | http://<host>:3080/v2 |
| 数据格式 | 请求与响应均为 JSON(上传系统盘文件除外,走原始字节流) |
| 方法 | GET / POST / PUT / DELETE |
| 认证 | 配置开启 auth 后使用 HTTP Basic |
| 响应标识 | 响应头带 X-Route、Server: Python/3.10 GNS3/2.2.61 |
版本接口是最轻量的连通性检查,也是唯一不依赖任何对象状态的接口:
curl -s http://127.0.0.1:3080/v2/version
{
"local": false,
"version": "2.2.61"
}
local 字段反映服务端配置里的 local 值,不是"是否本机"的意思。
1.2 认证
配置里开启鉴权后,不带凭据的请求直接 401,不会返回业务数据:
curl -s -o /dev/null -w 'noauth HTTP %{http_code}\n' http://127.0.0.1:3080/v2/version
curl -s -o /dev/null -w 'basic HTTP %{http_code}\n' -u root:q1w2e3r4 http://127.0.0.1:3080/v2/version
noauth HTTP 401
basic HTTP 200
401 响应带 WWW-Authenticate: Basic realm="GNS3 server",所有写操作同样需要凭据。脚本里推荐用环境变量传账号,避免密码进 shell 历史:
export GNS3_API=http://127.0.0.1:3080/v2
curl -s -u "$GNS3_USER:$GNS3_PASS" $GNS3_API/projects
1.3 一处易踩的认证豁免
console 的 WebSocket 路径(/projects/{project_id}/nodes/{node_id}/console/ws)不在 Basic 认证范围内。这意味着仅靠 HTTP 鉴权并不能保护 console 通道,服务端暴露在公网时必须在反向代理层再加一层访问控制。
二、计算节点信息
2.1 列出计算节点
curl -s http://127.0.0.1:3080/v2/computes
[
{
"capabilities": {
"node_types": [
"cloud", "ethernet_hub", "ethernet_switch", "nat", "vpcs",
"virtualbox", "dynamips", "frame_relay_switch", "atm_switch",
"qemu", "vmware", "traceng", "iou"
],
"platform": "linux",
"version": "2.2.61"
},
"compute_id": "local",
"connected": true,
"cpu_usage_percent": 83.5,
"host": "127.0.0.1",
"last_error": null,
"memory_usage_percent": 63.3,
"name": "gns3-server",
"port": 3080,
"protocol": "http",
"user": null
}
]
(node_types 上列为节选)node_types 是服务端实际支持的节点类型清单,脚本里可以据此判断某类节点是否可用;connected 为 false 时先查 last_error,再查 compute 之间的连通性。
2.2 capabilities 与真实可用性不一致(实测)
QEMU 能力探测接口在本机 KVM 完全不可用的情况下,仍然报告支持 KVM:
curl -s http://127.0.0.1:3080/v2/compute/qemu/capabilities
{
"kvm": [
"x86_64",
"i386"
]
}
这个结果是"二进制编译时支持的架构",不是"当前机器能不能跑 KVM"。不要用它做健康判断:真正的判据是启动节点后观察状态是否稳定,以及配置里 require_kvm 的行为(见参数篇 6.2)。
2.3 QEMU 二进制与磁盘文件清单
curl -s http://127.0.0.1:3080/v2/compute/qemu/binaries
[
{"path": "/usr/bin/qemu-system-i386", "version": "6.2.0"},
{"path": "/usr/bin/qemu-system-x86_64", "version": "6.2.0"},
{"path": "/bin/qemu-system-x86_64", "version": "6.2.0"}
]
该接口是"扫 PATH 得到的清单",与配置文件里 [Qemu] 的 qemu_path 无关(该键在 2.2.61 不被读取)。真正创建节点时必须把 qemu_path 写在节点属性里,否则报错(见第五章)。
系统盘文件清单:
curl -s http://127.0.0.1:3080/v2/compute/qemu/images
[
{
"filename": "empty8G.qcow2",
"filesize": 196616,
"md5sum": "9f2c1b0d0b0e4a1e8f0b6d4b3c2a1f00"
}
]
首启时服务端会自动装入 empty8G.qcow2、empty10G.qcow2 等内置文件,出现在这个列表里是正常现象。
三、工程(project)生命周期
3.1 创建
curl -s -X POST http://127.0.0.1:3080/v2/projects \
-H 'Content-Type: application/json' \
-d '{"name":"api-doc-demo"}'
{
"auto_close": true,
"auto_open": false,
"auto_start": false,
"drawing_grid_size": 25,
"filename": "api-doc-demo.gns3",
"grid_size": 75,
"name": "api-doc-demo",
"path": "/var/lib/gns3/projects/f1c60cb3-ac6f-4f99-bf30-4d9e61d47911",
"project_id": "f1c60cb3-ac6f-4f99-bf30-4d9e61d47911",
"scene_height": 1000,
"scene_width": 2000,
"status": "closed"
}
两个关键默认值:
status是closed,新建工程不会自动打开;auto_close为true,工程在一段时间无客户端访问后会自动关闭。实测中一个已打开的工程在操作中断后自动回到closed,此时查询节点/统计会得到空结果,容易误判为"数据丢了"。
写入位置由 projects_path 决定,path 字段可以直接拿去做备份脚本的输入。
3.2 打开与关闭
curl -s -o /dev/null -w 'open HTTP %{http_code}\n' -X POST $GNS3_API/projects/$PID/open
curl -s -o /dev/null -w 'close HTTP %{http_code}\n' -X POST $GNS3_API/projects/$PID/close
open HTTP 201
close HTTP 204
未打开就操作工程内的对象会被拒:
{"message": "The project is not opened", "status": 403}
所以自动化脚本的第一个动作应该是 open,且每次长时间空闲后重新确认 status:
curl -s $GNS3_API/projects/$PID | python3 -c 'import sys,json;print(json.load(sys.stdin)["status"])'
opened
3.3 统计与删除
curl -s $GNS3_API/projects/$PID/stats
{
"drawings": 0,
"links": 1,
"nodes": 2,
"snapshots": 0
}
统计只在工程处于 opened 时反映真实内容;工程自动关闭后返回全 0。
删除工程返回 204,磁盘目录同步移除:
curl -s -o /dev/null -w 'delete HTTP %{http_code}\n' -X DELETE $GNS3_API/projects/$PID
delete HTTP 204
四、节点与链路
4.1 创建节点
节点的统一入口是 POST /v2/projects/{project_id}/nodes,用 node_type 区分类型;不要去找 /vpcs/nodes、/qemu/nodes 这类路径,它们不存在(会 404)。
curl -s -X POST $GNS3_API/projects/$PID/nodes \
-H 'Content-Type: application/json' \
-d '{
"name": "Q1",
"node_type": "qemu",
"compute_id": "local",
"properties": {
"ram": 128,
"hda_disk_image": "empty8G.qcow2",
"qemu_path": "/usr/bin/qemu-system-x86_64"
}
}'
返回体是完整的节点对象,关键字段:
{
"name": "Q1",
"node_id": "60c4e99e-8f32-453c-8c79-a15e2c0248e2",
"node_type": "qemu",
"status": "stopped",
"console": null,
"properties": {
"ram": 128,
"hda_disk_image": "empty8G.qcow2",
"qemu_path": "/usr/bin/qemu-system-x86_64"
}
}
qemu_path 必须写在节点属性里。省略时服务端会拼出一个 qemu-system-None 的路径并直接报 409:
{
"message": "QEMU binary path qemu-system-None is not found in the path",
"status": 409
}
status 恒为 stopped,创建不等于启动。
4.2 创建链路
链路创建要用两端节点的 node_id + 适配器号 + 端口号,缺一不可;字段缺失会得到 schema 校验错误:
curl -s -X POST $GNS3_API/projects/$PID/links \
-H 'Content-Type: application/json' \
-d '{
"nodes": [
{"node_id": "'$N1'", "adapter_number": 0, "port_number": 0},
{"node_id": "'$N2'", "adapter_number": 0, "port_number": 0}
]
}'
{
"link_id": "5ba82033-a2fd-4bf3-9623-8421cd5ee0c6",
"link_type": "ethernet",
"nodes": [
{"node_id": "60c4e99e-8f32-453c-8c79-a15e2c0248e2", "adapter_number": 0, "port_number": 0},
{"node_id": "279730b5-a952-4419-8542-e2f7bb707987", "adapter_number": 0, "port_number": 0}
]
}
node_id 传空字符串时的原样报错(可用于自查拼装脚本):
{
"message": "JSON schema error with API request '/v2/projects/<uuid>/links' and JSON data '{...}': '' is too short",
"method": "POST",
"path": "/v2/projects/{project_id}/links",
"status": 400
}
4.3 启动与停止
curl -s -o /tmp/q1start.json -w 'start HTTP %{http_code}\n' \
-X POST $GNS3_API/projects/$PID/nodes/$N1/start
start HTTP 200
启动成功的响应会把分配到的 console 端口与最终命令行回填:
{
"name": "Q1",
"status": "started",
"console": 5000,
"console_host": "0.0.0.0"
}
命令行(节选,可在排障时直接拿去复现):
/usr/bin/qemu-system-x86_64 -name Q1 -m 128M -smp cpus=1,sockets=1 -boot order=c \
-drive file=/var/lib/gns3/projects/<project_id>/project-files/qemu/<node_id>/hda_disk.qcow2,if=none,index=0,media=disk,id=drive0 \
-uuid <node_id> -serial telnet:127.0.0.1:5001,server,nowait -monitor tcp:127.0.0.1:44161,server,nowait \
-net none -device e1000,mac=0c:c4:e9:9e:00:00,netdev=gns3-0 \
-netdev socket,id=gns3-0,udp=127.0.0.1:20003,localaddr=127.0.0.1:20002 -display none
几个信息点:
- QEMU 节点会占用两个连续端口:
console(5000)与 serial 端口(5001),端口段由配置里的 console 范围决定; - 链路在运行时体现为
-netdev socket的 UDP 隧道,端口取自配置里的 UDP 范围(实测 20002/20003); - 没有
-enable-kvm,说明当前走的是纯软件模拟(配置enable_kvm = false)。
停止同样简单,返回 200:
curl -s -o /dev/null -w 'stop HTTP %{http_code}\n' \
-X POST $GNS3_API/projects/$PID/nodes/$N1/stop
stop HTTP 200
启动接口返回 200 不代表节点一直活着。实测在 enable_kvm = true 而 KVM 不可用时,启动先 200、约 3 秒后节点自动变 stopped,接口不会补报错误。自动化脚本应在启动后轮询一次状态:
sleep 4
curl -s $GNS3_API/projects/$PID/nodes/$N1 \
| python3 -c 'import sys,json;print(json.load(sys.stdin)["status"])'
started
五、系统盘文件上传
5.1 上传
上传接口接收原始字节流,不是 multipart 表单,成功只返回 204 无响应体:
qemu-img create -f qcow2 /tmp/demo-disk.qcow2 64M
curl -s -o /dev/null -w 'upload HTTP %{http_code}\n' \
-X POST --data-binary @/tmp/demo-disk.qcow2 \
$GNS3_API/compute/qemu/images/demo-disk.qcow2
Formatting '/tmp/demo-disk.qcow2', fmt=qcow2 cluster_size=65536 ... size=67108864 ...
upload HTTP 204
上传后服务端会计算校验和,列表接口可以直接核对文件是否完整:
curl -s $GNS3_API/compute/qemu/images
[
{
"filename": "demo-disk.qcow2",
"filesize": 196616,
"md5sum": "41ce4d32ce22529a293044bc1eb4e734"
}
]
本地文件大小与 filesize 一致(196616 字节)即上传完整;qcow2 是稀疏格式,创建 64M 磁盘只占 196KB 属正常。
5.2 没有删除接口(实测)
对同一路径发 DELETE 会得到 405:
curl -s -o /dev/null -w 'delete image HTTP %{http_code}\n' -X DELETE $GNS3_API/compute/qemu/images/demo-disk.qcow2
delete image HTTP 405
2.2.61 的该资源只注册了 GET(下载)与 POST(上传),没有 DELETE。清理只能落到磁盘目录上(images_path/QEMU/<filename>),或者先停止全部引用它的节点再手工删除,否则节点启动会因找不到系统盘文件失败。
下载同理,直接 GET 同名路径即得到文件流。
六、WebSocket 通知
6.1 工程通知通道
ws://<host>:3080/v2/projects/{project_id}/notifications/ws
连接后服务端持续推送两类消息:心跳(带资源占用)与对象变更。实测抓取(启动/停止 Q1 期间的 6 秒窗口):
{"action": "ping", "event": {"cpu_usage_percent": 88.4, "memory_usage_percent": 43.5}}
{"action": "node.updated", "event": {"name": "Q1", "status": "started"}}
{"action": "ping", "event": {"cpu_usage_percent": 90.3, "memory_usage_percent": 43.7}}
{"action": "node.updated", "event": {"name": "Q1", "status": "stopped"}}
{"action": "node.updated", "event": {"name": "Q1", "status": "stopped"}}
要点:
ping是心跳,event里带cpu_usage_percent、memory_usage_percent,可直接做监控数据源;node.updated会在启动、停止各推一次,停止时偶发重复推送,客户端要做幂等处理;- 心跳间隔在 5 秒量级,脚本里不要假设"连上就立刻有业务事件",需要配合轮询兜底。
Python 订阅示例(依赖 aiohttp):
import asyncio, json, aiohttp
async def watch(project_id):
url = f"ws://127.0.0.1:3080/v2/projects/{project_id}/notifications/ws"
async with aiohttp.ClientSession() as s:
async with s.ws_connect(url) as ws:
async for msg in ws:
if msg.type == aiohttp.WSMsgType.TEXT:
data = json.loads(msg.data)
if data["action"] in ("node.updated", "link.created", "link.deleted"):
print(data["action"], data["event"].get("name"), data["event"].get("status"))
asyncio.run(watch("<project_id>"))
6.2 其它两类通道
| 通道 | 路径 | 用途 |
|---|---|---|
| compute 通知 | /v2/compute/notifications/ws | 订阅计算节点自身事件(如可用文件变化) |
| 节点 console | /v2/projects/{project_id}/nodes/{node_id}/console/ws | 浏览器终端,二进制/文本帧承载 console 输出 |
console 通道不在认证范围内(见 1.3),暴露在外网时必须由反代控制访问。
七、错误码与排障
| 状态码 | 典型原样报错 | 根因与处理 |
|---|---|---|
| 400 | JSON schema error with API request ...: '' is too short | 请求体字段缺失/为空,按 schema 补齐;链路必须有 node_id/adapter_number/port_number |
| 401 | 无响应体,带 WWW-Authenticate: Basic realm="GNS3 server" | 未认证;补 Basic 凭据(且确认 auth 已开启) |
| 403 | {"message": "The project is not opened", "status": 403} | 工程未打开;先 POST /projects/{id}/open |
| 404 | 404: Not Found | 路径拼错(常见:把节点创建写成 /vpcs/nodes、/qemu/nodes) |
| 405 | Method Not Allowed(Allow: GET,OPTIONS,POST) | 方法不支持;HEAD /v2/version、DELETE /compute/qemu/images/xxx 都会命中原样 405 |
| 409 | QEMU binary path qemu-system-None is not found in the path | 节点属性缺 qemu_path |
| 409 | VPCS executable version must be >= 0.6.1 but not a 0.8 | VPCS 版本过旧(Ubuntu 22.04 自带 0.5b2),升级到 0.8.4 |
| 409 | uBridge is not available, path doesn't exist... | 缺 ubridge,或路径不在 PATH |
补充两个实测细节:
HEAD /v2/version返回 405 而不是 200,健康检查请用GET;GET /v2/status不存在(404),服务状态用/v2/version或/v2/computes判断。
八、端到端脚本
把前面各章串起来,一个可以直接跑的完整流程(成功时最终输出 started):
#!/bin/bash
set -e
API=${GNS3_API:-http://127.0.0.1:3080/v2}
AUTH=${GNS3_AUTH:--u root:q1w2e3r4}
PID=$(curl -s $AUTH -X POST $API/projects -H 'Content-Type: application/json' \
-d '{"name":"api-doc-demo"}' | python3 -c 'import sys,json;print(json.load(sys.stdin)["project_id"])')
curl -s $AUTH -o /dev/null -X POST $API/projects/$PID/open
mknode () {
curl -s $AUTH -X POST $API/projects/$PID/nodes -H 'Content-Type: application/json' \
-d "{\"name\":\"$1\",\"node_type\":\"qemu\",\"compute_id\":\"local\",
\"properties\":{\"ram\":128,\"hda_disk_image\":\"empty8G.qcow2\",
\"qemu_path\":\"/usr/bin/qemu-system-x86_64\"}}" \
| python3 -c 'import sys,json;print(json.load(sys.stdin)["node_id"])'
}
N1=$(mknode Q1)
N2=$(mknode Q2)
curl -s $AUTH -X POST $API/projects/$PID/links -H 'Content-Type: application/json' \
-d "{\"nodes\":[{\"node_id\":\"$N1\",\"adapter_number\":0,\"port_number\":0},
{\"node_id\":\"$N2\",\"adapter_number\":0,\"port_number\":0}]}" >/dev/null
curl -s $AUTH -o /dev/null -X POST $API/projects/$PID/nodes/$N1/start
sleep 4
curl -s $AUTH $API/projects/$PID/nodes/$N1 \
| python3 -c 'import sys,json;print(json.load(sys.stdin)["status"])'
# 清理
curl -s $AUTH -o /dev/null -X POST $API/projects/$PID/close
curl -s $AUTH -o /dev/null -X DELETE $API/projects/$PID
注意脚本里的三处"必须":创建后 open、节点属性带 qemu_path、启动后等 4 秒再查状态。漏掉任何一处,结果要么 403,要么 409,要么误判为成功。
九、参考与延伸阅读
- GNS3 官方 API 文档:https://docs.gns3.com/docs/using-gns3/advanced/gns3-server-api
- gns3-server 源码(接口实现):https://github.com/GNS3/gns3-server
- 站内:《GNS3 2.2.61 介绍与选型指南》
- 站内:《Ubuntu 22.04 安装 GNS3 2.2.61 服务端指南》
- 站内:《GNS3 2.2.61 配置参数详解》