diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml
index 4713c6da..c0c31acb 100644
--- a/.github/workflows/testing.yml
+++ b/.github/workflows/testing.yml
@@ -13,11 +13,16 @@ on:
jobs:
build:
- runs-on: ubuntu-latest
+ runs-on: ${{ matrix.os }}
strategy:
matrix:
+ os: ["ubuntu-latest"]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
+ #include:
+ # only test with Python 3.10 on Windows
+ # - os: windows-latest
+ # python-version: "3.10"
steps:
- uses: actions/checkout@v4
@@ -31,6 +36,14 @@ jobs:
run: |
python -m pip install --upgrade pip
python -m pip install .[dev]
+
+ - name: Install Windows specific dependencies
+ if: runner.os == 'Windows'
+ run: |
+ python -m pip install -r win-requirements.txt
+ curl -O "http://www.win10pcap.org/download/Win10Pcap-v10.2-5002.msi"
+ msiexec /i "Win10Pcap-v10.2-5002.msi" /qn /norestart
+
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
diff --git a/CHANGELOG b/CHANGELOG
index c39cc7ba..9fd09c9b 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,44 @@
# Change Log
+## 3.0.0rc2 20/11/2024
+
+* Bundle web-ui v3.0.0rc2
+* Fix error 500 on PUT for cloud, nat, vmware and vpcs nodes. Fixes #2426
+* Add a duplicated project in the same resource pools as the original project if it is in any
+* Upgrade FastAPI to v0.115.5
+* Overwrite user resources when the originals have changed.
+* Relax setuptools requirement to allow for easier Debian packaging on Ubuntu Focal & Jammy
+* Increase SQLite timeout. Ref #2422
+* Fix test user with wrong creds
+* Upgrade dependencies and fix Pydantic warnings
+* Upgrade aiohttp to v3.10.10. Fixes #2411
+* Replace aiohttp.web.HTTPConflict()
+* Python 3.13 support
+
+## 2.2.51 07/11/2024
+
+* Catch error when cannot resize Docker container TTY.
+* Do not use "ide" if there is a disk image and no interface type has been explicitly configured.
+* Use @locking when sending uBridge commands. Ref https://github.com/GNS3/gns3-gui/issues/3651
+* Fix run Docker containers with user namespaces enabled. Fixes #2414
+* Python 3.13 support
+* Upgrade dependencies
+* Fix errors in init.sh. Fixes #2431
+
+## 2.2.50 21/10/2024
+
+* Bundle web-ui v2.2.50
+* Symbolic links support for project export/import
+* Add comment to indicate sentry-sdk is optional. Ref https://github.com/GNS3/gns3-server/issues/2423
+* Fix issues with recent busybox versions
+* Support to reset MAC addresses for Docker nodes and some adjustments for fast duplication.
+* Update README.md to change the minimum required Python version.
+* Faster project duplication for local projects (no remote compute)
+* Improve error message when a project cannot be parsed.
+* Fix for running Docker containers with user namespaces enabled
+* Support for configuring MAC address in Docker containers
+* Upgrade aiohttp to v3.10.3
+
## 3.0.0rc1 11/08/2024
* Bundle web-ui v3.0.0rc1
diff --git a/dev-requirements.txt b/dev-requirements.txt
index 0fa15092..3aa82cdd 100644
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -1,7 +1,7 @@
-pytest==8.3.2
-flake8==7.1.0
+pytest==8.3.3
+flake8==7.1.1
pytest-timeout==2.3.1
pytest-asyncio==0.21.2
requests==2.32.3
-httpx==0.24.1 # version 0.24.1 is required by httpx_ws
-httpx_ws==0.4.2
+httpx==0.27.2 # version 0.24.1 is required by httpx_ws
+httpx_ws==0.6.2
diff --git a/gns3server/api/routes/compute/cloud_nodes.py b/gns3server/api/routes/compute/cloud_nodes.py
index 6dc135d5..fd333ab3 100644
--- a/gns3server/api/routes/compute/cloud_nodes.py
+++ b/gns3server/api/routes/compute/cloud_nodes.py
@@ -85,7 +85,7 @@ def get_cloud(node: Cloud = Depends(dep_node)) -> schemas.Cloud:
@router.put("/{node_id}", response_model=schemas.Cloud)
-def update_cloud(node_data: schemas.CloudUpdate, node: Cloud = Depends(dep_node)) -> schemas.Cloud:
+async def update_cloud(node_data: schemas.CloudUpdate, node: Cloud = Depends(dep_node)) -> schemas.Cloud:
"""
Update a cloud node.
"""
diff --git a/gns3server/api/routes/compute/docker_nodes.py b/gns3server/api/routes/compute/docker_nodes.py
index 11a2cf65..bf1bb072 100644
--- a/gns3server/api/routes/compute/docker_nodes.py
+++ b/gns3server/api/routes/compute/docker_nodes.py
@@ -69,6 +69,7 @@ async def create_docker_node(project_id: UUID, node_data: schemas.DockerCreate)
start_command=node_data.get("start_command"),
environment=node_data.get("environment"),
adapters=node_data.get("adapters"),
+ mac_address=node_data.get("mac_address"),
console=node_data.get("console"),
console_type=node_data.get("console_type"),
console_resolution=node_data.get("console_resolution", "1024x768"),
@@ -124,6 +125,8 @@ async def update_docker_node(node_data: schemas.DockerUpdate, node: DockerVM = D
"start_command",
"environment",
"adapters",
+ "mac_address",
+ "custom_adapters",
"extra_hosts",
"extra_volumes",
"memory",
diff --git a/gns3server/api/routes/compute/nat_nodes.py b/gns3server/api/routes/compute/nat_nodes.py
index d271d279..06788f6a 100644
--- a/gns3server/api/routes/compute/nat_nodes.py
+++ b/gns3server/api/routes/compute/nat_nodes.py
@@ -80,7 +80,7 @@ def get_nat_node(node: Nat = Depends(dep_node)) -> schemas.NAT:
@router.put("/{node_id}", response_model=schemas.NAT)
-def update_nat_node(node_data: schemas.NATUpdate, node: Nat = Depends(dep_node)) -> schemas.NAT:
+async def update_nat_node(node_data: schemas.NATUpdate, node: Nat = Depends(dep_node)) -> schemas.NAT:
"""
Update a NAT node.
"""
diff --git a/gns3server/api/routes/compute/vmware_nodes.py b/gns3server/api/routes/compute/vmware_nodes.py
index f1e3752c..30f79902 100644
--- a/gns3server/api/routes/compute/vmware_nodes.py
+++ b/gns3server/api/routes/compute/vmware_nodes.py
@@ -97,7 +97,7 @@ def get_vmware_node(node: VMwareVM = Depends(dep_node)) -> schemas.VMware:
response_model=schemas.VMware,
dependencies=[Depends(compute_authentication)]
)
-def update_vmware_node(node_data: schemas.VMwareUpdate, node: VMwareVM = Depends(dep_node)) -> schemas.VMware:
+async def update_vmware_node(node_data: schemas.VMwareUpdate, node: VMwareVM = Depends(dep_node)) -> schemas.VMware:
"""
Update a VMware node.
"""
diff --git a/gns3server/api/routes/compute/vpcs_nodes.py b/gns3server/api/routes/compute/vpcs_nodes.py
index 143ceef8..bd5181b6 100644
--- a/gns3server/api/routes/compute/vpcs_nodes.py
+++ b/gns3server/api/routes/compute/vpcs_nodes.py
@@ -91,15 +91,21 @@ def get_vpcs_node(node: VPCSVM = Depends(dep_node)) -> schemas.VPCS:
response_model=schemas.VPCS,
dependencies=[Depends(compute_authentication)]
)
-def update_vpcs_node(node_data: schemas.VPCSUpdate, node: VPCSVM = Depends(dep_node)) -> schemas.VPCS:
+async def update_vpcs_node(node_data: schemas.VPCSUpdate, node: VPCSVM = Depends(dep_node)) -> schemas.VPCS:
"""
Update a VPCS node.
"""
node_data = jsonable_encoder(node_data, exclude_unset=True)
- node.name = node_data.get("name", node.name)
- node.console = node_data.get("console", node.console)
- node.console_type = node_data.get("console_type", node.console_type)
+ name = node_data.get("name", node.name)
+ if node.name != name:
+ node.name = name
+ console = node_data.get("console", node.console)
+ if node.console != console:
+ node.console = console
+ console_type = node_data.get("console_type", node.console_type)
+ if node.console_type != console_type:
+ node.console_type = console_type
node.updated()
return node.asdict()
diff --git a/gns3server/api/routes/controller/projects.py b/gns3server/api/routes/controller/projects.py
index fa088534..235511e1 100644
--- a/gns3server/api/routes/controller/projects.py
+++ b/gns3server/api/routes/controller/projects.py
@@ -430,7 +430,8 @@ async def import_project(
)
async def duplicate_project(
project_data: schemas.ProjectDuplicate,
- project: Project = Depends(dep_project)
+ project: Project = Depends(dep_project),
+ pools_repo: ResourcePoolsRepository = Depends(get_repository(ResourcePoolsRepository))
) -> schemas.Project:
"""
Duplicate a project.
@@ -442,6 +443,15 @@ async def duplicate_project(
new_project = await project.duplicate(
name=project_data.name, reset_mac_addresses=reset_mac_addresses
)
+
+ # Add the new project in the same resource pools if the duplicated project is in any
+ pool_memberships = await pools_repo.get_resource_memberships(project.id)
+ if pool_memberships:
+ resource_create = schemas.ResourceCreate(resource_id=new_project.id, resource_type="project", name=new_project.name)
+ resource = await pools_repo.create_resource(resource_create)
+ for pool in pool_memberships:
+ await pools_repo.add_resource_to_pool(pool.resource_pool_id, resource)
+
return new_project.asdict()
diff --git a/gns3server/api/routes/controller/users.py b/gns3server/api/routes/controller/users.py
index 9901dd5c..0bd6373d 100644
--- a/gns3server/api/routes/controller/users.py
+++ b/gns3server/api/routes/controller/users.py
@@ -54,7 +54,7 @@ async def login(
) -> schemas.Token:
"""
Default user login method using forms (x-www-form-urlencoded).
- Example: curl http://host:port/v3/users/login -H "Content-Type: application/x-www-form-urlencoded" -d "username=admin&password=admin"
+ Example: curl -X POST http://host:port/v3/access/users/login -H "Content-Type: application/x-www-form-urlencoded" -d "username=admin&password=admin"
"""
user = await users_repo.authenticate_user(username=form_data.username, password=form_data.password)
@@ -76,7 +76,7 @@ async def authenticate(
) -> schemas.Token:
"""
Alternative authentication method using json.
- Example: curl http://host:port/v3/users/authenticate -d '{"username": "admin", "password": "admin"}' -H "Content-Type: application/json"
+ Example: curl -X POST http://host:port/v3/access/users/authenticate -d '{"username": "admin", "password": "admin"}' -H "Content-Type: application/json"
"""
user = await users_repo.authenticate_user(username=user_credentials.username, password=user_credentials.password)
diff --git a/gns3server/appliances/almalinux.gns3a b/gns3server/appliances/almalinux.gns3a
index f0ad0526..6e39d7d2 100644
--- a/gns3server/appliances/almalinux.gns3a
+++ b/gns3server/appliances/almalinux.gns3a
@@ -30,24 +30,24 @@
"version": "9.2",
"md5sum": "c5bc76e8c95ac9f810a3482c80a54cc7",
"filesize": 563347456,
- "download_url": "https://repo.almalinux.org/almalinux/9/cloud/x86_64/images/",
- "direct_download_url": "https://repo.almalinux.org/almalinux/9/cloud/x86_64/images/AlmaLinux-9-GenericCloud-9.2-20230513.x86_64.qcow2"
+ "download_url": "https://vault.almalinux.org/9.2/cloud/x86_64/images/",
+ "direct_download_url": "https://vault.almalinux.org/9.2/cloud/x86_64/images/AlmaLinux-9-GenericCloud-9.2-20230513.x86_64.qcow2"
},
{
"filename": "AlmaLinux-8-GenericCloud-8.8-20230524.x86_64.qcow2",
"version": "8.8",
"md5sum": "3958c5fc25770ef63cf97aa5d93f0a0b",
"filesize": 565444608,
- "download_url": "https://repo.almalinux.org/almalinux/8/cloud/x86_64/images/",
- "direct_download_url": "https://repo.almalinux.org/almalinux/8/cloud/x86_64/images/AlmaLinux-8-GenericCloud-8.8-20230524.x86_64.qcow2"
+ "download_url": "https://vault.almalinux.org/8.8/cloud/x86_64/images/",
+ "direct_download_url": "https://vault.almalinux.org/8.8/cloud/x86_64/images/AlmaLinux-8-GenericCloud-8.8-20230524.x86_64.qcow2"
},
{
"filename": "AlmaLinux-8-GenericCloud-8.7-20221111.x86_64.qcow2",
"version": "8.7",
"md5sum": "b2b8c7fd3b6869362f3f8ed47549c804",
"filesize": 566231040,
- "download_url": "https://repo.almalinux.org/almalinux/8/cloud/x86_64/images/",
- "direct_download_url": "https://repo.almalinux.org/almalinux/8/cloud/x86_64/images/AlmaLinux-8-GenericCloud-8.7-20221111.x86_64.qcow2"
+ "download_url": "https://vault.almalinux.org/8.7/cloud/x86_64/images/",
+ "direct_download_url": "https://vault.almalinux.org/8.7/cloud/x86_64/images/AlmaLinux-8-GenericCloud-8.7-20221111.x86_64.qcow2"
},
{
"filename": "almalinux-cloud-init-data.iso",
diff --git a/gns3server/appliances/cisco-csr1000v.gns3a b/gns3server/appliances/cisco-csr1000v.gns3a
index d1afb91f..90be6447 100644
--- a/gns3server/appliances/cisco-csr1000v.gns3a
+++ b/gns3server/appliances/cisco-csr1000v.gns3a
@@ -24,12 +24,26 @@
"kvm": "require"
},
"images": [
+ {
+ "filename": "csr1000v-universalk9.17.03.08a-serial.qcow2",
+ "version": "17.03.08a",
+ "md5sum": "6abece87d6db99d9fd6917203e253f91",
+ "filesize": 1421410304,
+ "download_url": "https://software.cisco.com/download/home/286323714/type/282046477/release/Amsterdam-17.3.8a"
+ },
{
"filename": "csr1000v-universalk9.17.03.06-serial.qcow2",
"version": "17.03.06",
"md5sum": "086ab9bef6e66de847af0da3910c60e8",
"filesize": 1422000128,
- "download_url": "https://software.cisco.com/download/home/284364978/type/282046477/release/Gibraltar-16.12.3"
+ "download_url": "https://software.cisco.com/download/home/286323714/type/282046477/release/Amsterdam-17.3.6"
+ },
+ {
+ "filename": "csr1000v-ucmk9.16.12.5-serial.qcow2",
+ "version": "16.12.05",
+ "md5sum": "5c0cc217f0f0648407b34b11a1dd5b8e",
+ "filesize": 844103680,
+ "download_url": "https://software.cisco.com/download/home/286323714/type/286321980/release/16.12.5"
},
{
"filename": "csr1000v-universalk9.16.12.03-serial.qcow2",
@@ -166,13 +180,25 @@
}
],
"versions": [
+ {
+ "name": "17.03.08a",
+ "images": {
+ "hda_disk_image": "csr1000v-universalk9.17.03.08a-serial.qcow2"
+ }
+ },
{
"name": "17.03.06",
"images": {
"hda_disk_image": "csr1000v-universalk9.17.03.06-serial.qcow2"
}
},
- {
+ {
+ "name": "16.12.05",
+ "images": {
+ "hda_disk_image": "csr1000v-ucmk9.16.12.5-serial.qcow2"
+ }
+ },
+ {
"name": "16.12.3",
"images": {
"hda_disk_image": "csr1000v-universalk9.16.12.03-serial.qcow2"
diff --git a/gns3server/appliances/cisco-vWLC.gns3a b/gns3server/appliances/cisco-vWLC.gns3a
index a17374d7..5dfe1df3 100644
--- a/gns3server/appliances/cisco-vWLC.gns3a
+++ b/gns3server/appliances/cisco-vWLC.gns3a
@@ -28,7 +28,21 @@
"options": ""
},
"images": [
- {
+ {
+ "filename": "MFG_CTVM_8_10_196_0.iso",
+ "version": "8.10.196.0",
+ "md5sum": "6093aca44dcf45c999f83e62dc9aeea2",
+ "filesize": 650809344,
+ "download_url": "https://software.cisco.com/download/release.html?mdfid=284464214&flowid=&softwareid=280926587&release=8.10.196.0"
+ },
+ {
+ "filename": "MFG_CTVM_8_5_182_0.iso",
+ "version": "8.5.182.0",
+ "md5sum": "1cf3c57c2b123e739ab4662ea0abbc34",
+ "filesize": 388579328,
+ "download_url": "https://software.cisco.com/download/home/284464214/type/280926587/release/8.5.182.0"
+ },
+ {
"filename": "MFG_CTVM_8_3_102_0.iso",
"version": "8.3.102.0",
"md5sum": "7f6b7968b5bed04b5ecc119b6ba4e41c",
@@ -73,6 +87,20 @@
}
],
"versions": [
+ {
+ "name": "8.10.196.0",
+ "images": {
+ "hda_disk_image": "empty8G.qcow2",
+ "cdrom_image": "MFG_CTVM_8_10_196_0.iso"
+ }
+ },
+ {
+ "name": "8.5.182.0",
+ "images": {
+ "hda_disk_image": "empty8G.qcow2",
+ "cdrom_image": "MFG_CTVM_8_5_182_0.iso"
+ }
+ },
{
"name": "8.3.102.0",
"images": {
diff --git a/gns3server/appliances/fortigate.gns3a b/gns3server/appliances/fortigate.gns3a
index 4fb16e0c..102bf096 100644
--- a/gns3server/appliances/fortigate.gns3a
+++ b/gns3server/appliances/fortigate.gns3a
@@ -29,10 +29,10 @@
},
"images": [
{
- "filename": "FGT_VM64_KVM-v7.4.4.F-build2573-FORTINET.out.kvm.qcow2",
+ "filename": "FGT_VM64_KVM-v7.4.4.F-build2662-FORTINET.out.kvm.qcow2",
"version": "7.4.4",
"md5sum": "dfe0e78827ec728631539669001bb23f",
- "filesize": 100728832,
+ "filesize": 102170624,
"download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
},
{
@@ -391,7 +391,7 @@
{
"name": "7.4.4",
"images": {
- "hda_disk_image": "FGT_VM64_KVM-v7.4.4.F-build2573-FORTINET.out.kvm.qcow2",
+ "hda_disk_image": "FGT_VM64_KVM-v7.4.4.F-build2662-FORTINET.out.kvm.qcow2",
"hdb_disk_image": "empty30G.qcow2"
}
},
diff --git a/gns3server/appliances/hbcd-pe.gns3a b/gns3server/appliances/hbcd-pe.gns3a
new file mode 100644
index 00000000..22499811
--- /dev/null
+++ b/gns3server/appliances/hbcd-pe.gns3a
@@ -0,0 +1,62 @@
+{
+ "appliance_id": "ac98ab6f-7966-444b-842f-9507c965b8b7",
+ "name": "HBCD-PE",
+ "category": "guest",
+ "description": "Hiren’s BootCD PE (Preinstallation Environment) is a restored edition of Hiren’s BootCD based on Windows 11 PE x64. ",
+ "vendor_name": "hirensbootcd.org",
+ "vendor_url": "https://www.hirensbootcd.org/",
+ "documentation_url": "https://www.hirensbootcd.org/howtos/",
+ "product_name": "Hiren’s BootCD PE",
+ "product_url": "https://www.hirensbootcd.org/",
+ "registry_version": 4,
+ "status": "stable",
+ "maintainer": "GNS3 Team",
+ "maintainer_email": "developers@gns3.net",
+ "qemu": {
+ "adapter_type": "e1000",
+ "adapters": 1,
+ "ram": 4096,
+ "hda_disk_interface": "sata",
+ "arch": "x86_64",
+ "console_type": "vnc",
+ "boot_priority": "c",
+ "kvm": "require"
+ },
+ "images": [
+ {
+ "filename": "HBCD_PE_x64.iso",
+ "version": "1.0.8",
+ "md5sum": "45baab64b088431bdf3370292e9a74b0",
+ "filesize": 3291686912,
+ "download_url": "https://www.hirensbootcd.org/download/",
+ "direct_download_url": "https://www.hirensbootcd.org/files/HBCD_PE_x64.iso"
+ },
+ {
+ "filename": "empty30G.qcow2",
+ "version": "1.0",
+ "md5sum": "3411a599e822f2ac6be560a26405821a",
+ "filesize": 197120,
+ "download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/",
+ "direct_download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/empty30G.qcow2/download"
+ },
+ {
+ "filename": "OVMF-edk2-stable202305.fd",
+ "version": "stable202305",
+ "md5sum": "6c4cf1519fec4a4b95525d9ae562963a",
+ "filesize": 4194304,
+ "download_url": "https://sourceforge.net/projects/gns-3/files/Qemu%20Appliances/",
+ "direct_download_url": "https://sourceforge.net/projects/gns-3/files/Qemu%20Appliances/OVMF-edk2-stable202305.fd.zip/download",
+ "compression": "zip"
+ }
+ ],
+ "versions": [
+ {
+ "name": "1.0.8",
+ "images": {
+ "bios_image": "OVMF-edk2-stable202305.fd",
+ "hda_disk_image": "empty30G.qcow2",
+ "cdrom_image": "HBCD_PE_x64.iso"
+ }
+ }
+ ]
+}
diff --git a/gns3server/appliances/mikrotik-chr.gns3a b/gns3server/appliances/mikrotik-chr.gns3a
index f06c56ad..48c1eade 100644
--- a/gns3server/appliances/mikrotik-chr.gns3a
+++ b/gns3server/appliances/mikrotik-chr.gns3a
@@ -28,149 +28,80 @@
},
"images": [
{
- "filename": "chr-7.14.2.img",
- "version": "7.14.2",
- "md5sum": "531901dac85b67b23011e946a62abc7b",
+ "filename": "chr-7.16.img",
+ "version": "7.16",
+ "md5sum": "a4c2d00a87e73b3129cd66a4e0743c9a",
"filesize": 134217728,
"download_url": "http://www.mikrotik.com/download",
- "direct_download_url": "https://download.mikrotik.com/routeros/7.14.2/chr-7.14.2.img.zip",
+ "direct_download_url": "https://download.mikrotik.com/routeros/7.16/chr-7.16.img.zip",
"compression": "zip"
},
{
- "filename": "chr-7.11.2.img",
- "version": "7.11.2",
- "md5sum": "fbffd097d2c5df41fc3335c3977f782c",
+ "filename": "chr-7.15.3.img",
+ "version": "7.15.3",
+ "md5sum": "5af8c748a0de4e8e8b303180738721a9",
"filesize": 134217728,
"download_url": "http://www.mikrotik.com/download",
- "direct_download_url": "https://download.mikrotik.com/routeros/7.11.2/chr-7.11.2.img.zip",
+ "direct_download_url": "https://download.mikrotik.com/routeros/7.15.3/chr-7.15.3.img.zip",
"compression": "zip"
},
{
- "filename": "chr-7.10.1.img",
- "version": "7.10.1",
- "md5sum": "917729e79b9992562f4160d461b21cac",
+ "filename": "chr-7.14.3.img",
+ "version": "7.14.3",
+ "md5sum": "73f527efef81b529b267a0683cb87617",
"filesize": 134217728,
"download_url": "http://www.mikrotik.com/download",
- "direct_download_url": "https://download.mikrotik.com/routeros/7.10.1/chr-7.10.1.img.zip",
+ "direct_download_url": "https://download.mikrotik.com/routeros/7.14.3/chr-7.14.3.img.zip",
"compression": "zip"
},
{
- "filename": "chr-7.7.img",
- "version": "7.7",
- "md5sum": "efc4fdeb1cc06dc240a14f1215fd59b3",
- "filesize": 134217728,
- "download_url": "http://www.mikrotik.com/download",
- "direct_download_url": "https://download.mikrotik.com/routeros/7.7/chr-7.7.img.zip",
- "compression": "zip"
- },
- {
- "filename": "chr-7.6.img",
- "version": "7.6",
- "md5sum": "864482f9efaea9d40910c050318f65b9",
- "filesize": 134217728,
- "download_url": "http://www.mikrotik.com/download",
- "direct_download_url": "https://download.mikrotik.com/routeros/7.6/chr-7.6.img.zip",
- "compression": "zip"
- },
- {
- "filename": "chr-7.3.1.img",
- "version": "7.3.1",
- "md5sum": "99f8ea75f8b745a8bf5ca3cc1bd325e3",
- "filesize": 134217728,
- "download_url": "http://www.mikrotik.com/download",
- "direct_download_url": "https://download.mikrotik.com/routeros/7.3.1/chr-7.3.1.img.zip",
- "compression": "zip"
- },
- {
- "filename": "chr-7.1.5.img",
- "version": "7.1.5",
- "md5sum": "9c0be05f891df2b1400bdae5e719898e",
- "filesize": 134217728,
- "download_url": "http://www.mikrotik.com/download",
- "direct_download_url": "https://download.mikrotik.com/routeros/7.1.5/chr-7.1.5.img.zip",
- "compression": "zip"
- },
- {
- "filename": "chr-6.49.10.img",
- "version": "6.49.10",
- "md5sum": "49ae1ecfe310aea1df37b824aa13cf84",
+ "filename": "chr-6.49.17.img",
+ "version": "6.49.17",
+ "md5sum": "ad9f4bd8cd4965a403350deeb5d35b96",
"filesize": 67108864,
"download_url": "http://www.mikrotik.com/download",
- "direct_download_url": "https://download.mikrotik.com/routeros/6.49.10/chr-6.49.10.img.zip",
+ "direct_download_url": "https://download.mikrotik.com/routeros/6.49.17/chr-6.49.17.img.zip",
"compression": "zip"
},
{
- "filename": "chr-6.49.6.img",
- "version": "6.49.6",
- "md5sum": "ae27d38acc9c4dcd875e0f97bcae8d97",
+ "filename": "chr-6.49.13.img",
+ "version": "6.49.13",
+ "md5sum": "18349e1c3209495e571bcbee8a7e3259",
"filesize": 67108864,
"download_url": "http://www.mikrotik.com/download",
- "direct_download_url": "https://download.mikrotik.com/routeros/6.49.6/chr-6.49.6.img.zip",
- "compression": "zip"
- },
- {
- "filename": "chr-6.48.6.img",
- "version": "6.48.6",
- "md5sum": "875574a561570227ff8f395aabe478c6",
- "filesize": 67108864,
- "download_url": "http://www.mikrotik.com/download",
- "direct_download_url": "https://download.mikrotik.com/routeros/6.48.6/chr-6.48.6.img.zip",
+ "direct_download_url": "https://download.mikrotik.com/routeros/6.49.13/chr-6.49.13.img.zip",
"compression": "zip"
}
],
"versions": [
{
- "name": "7.11.2",
+ "name": "7.16",
"images": {
- "hda_disk_image": "chr-7.11.2.img"
+ "hda_disk_image": "chr-7.16.img"
}
},
{
- "name": "7.10.1",
+ "name": "7.15.3",
"images": {
- "hda_disk_image": "chr-7.10.1.img"
+ "hda_disk_image": "chr-7.15.3.img"
}
},
{
- "name": "7.7",
+ "name": "7.14.3",
"images": {
- "hda_disk_image": "chr-7.7.img"
+ "hda_disk_image": "chr-7.14.3.img"
}
},
{
- "name": "7.6",
+ "name": "6.49.17",
"images": {
- "hda_disk_image": "chr-7.6.img"
+ "hda_disk_image": "chr-6.49.17.img"
}
},
{
- "name": "7.3.1",
+ "name": "6.49.13",
"images": {
- "hda_disk_image": "chr-7.3.1.img"
- }
- },
- {
- "name": "7.1.5",
- "images": {
- "hda_disk_image": "chr-7.1.5.img"
- }
- },
- {
- "name": "6.49.10",
- "images": {
- "hda_disk_image": "chr-6.49.10.img"
- }
- },
- {
- "name": "6.49.6",
- "images": {
- "hda_disk_image": "chr-6.49.6.img"
- }
- },
- {
- "name": "6.48.6",
- "images": {
- "hda_disk_image": "chr-6.48.6.img"
+ "hda_disk_image": "chr-6.49.13.img"
}
}
]
diff --git a/gns3server/appliances/nixos.gns3a b/gns3server/appliances/nixos.gns3a
new file mode 100644
index 00000000..32d3b609
--- /dev/null
+++ b/gns3server/appliances/nixos.gns3a
@@ -0,0 +1,52 @@
+{
+ "appliance_id": "00714342-14b2-4281-aa20-9043ca8dc26e",
+ "name": "NixOS",
+ "category": "guest",
+ "description": "NixOS QEMU Appliance for images created with nixos-generator. Automatically sets hostname based on vmname.",
+ "vendor_name": "NixOS",
+ "vendor_url": "https://nixos.org/",
+ "vendor_logo_url": "https://avatars.githubusercontent.com/u/487568",
+ "documentation_url": "https://github.com/ob7/gns3-nixos-appliance",
+ "product_name": "NixOS",
+ "product_url": "https://github.com/NixOS/nixpkgs",
+ "registry_version": 4,
+ "status": "experimental",
+ "availability": "free",
+ "maintainer": "ob7dev",
+ "maintainer_email": "dev@ob7.us",
+ "usage": "For custom NixOS images, create qcow2 VM with: nixos-generate -f qcow -c ./server.nix Import it into GNS3 as image. VM name is passed into QEMU guest with Advanced Options field entry: -fw_cfg name=opt/vm_hostname,string=%vm-name%",
+ "symbol": ":/symbols/affinity/circle/gray/template.svg",
+ "first_port_name": "eth0",
+ "port_name_format": "eth{0}",
+ "qemu": {
+ "adapter_type": "e1000",
+ "adapters": 4,
+ "ram": 256,
+ "cpus": 1,
+ "hda_disk_interface": "ide",
+ "arch": "x86_64",
+ "console_type": "telnet",
+ "kvm": "allow",
+ "options": "-fw_cfg name=opt/vm_hostname,string=%vm-name%",
+ "on_close": "power_off"
+ },
+ "images": [
+ {
+ "filename": "nixos-24-11.qcow2",
+ "version": "24.11",
+ "md5sum": "2459f05136836dd430402d75cba0f205",
+ "download_url": "https://github.com/nix-community/nixos-generators",
+ "filesize": 1749483520,
+ "download_url": "https://f.ob7.us/gns3/",
+ "direct_download_url": "http://ob7.us/nixos-24-11.qcow2"
+ }
+ ],
+ "versions": [
+ {
+ "name": "24.11",
+ "images": {
+ "hda_disk_image": "nixos-24-11.qcow2"
+ }
+ }
+ ]
+}
diff --git a/gns3server/appliances/opnsense.gns3a b/gns3server/appliances/opnsense.gns3a
index b971f3a4..75970176 100644
--- a/gns3server/appliances/opnsense.gns3a
+++ b/gns3server/appliances/opnsense.gns3a
@@ -25,6 +25,13 @@
"kvm": "require"
},
"images": [
+ {
+ "filename": "OPNsense-24.7-nano-amd64.img",
+ "version": "24.7",
+ "md5sum": "4f75dc2c948b907cbf73763b1539677c",
+ "filesize": 3221225472,
+ "download_url": "https://opnsense.c0urier.net/releases/24.7/"
+ },
{
"filename": "OPNsense-24.1-nano-amd64.img",
"version": "24.1",
@@ -69,6 +76,12 @@
}
],
"versions": [
+ {
+ "name": "24.7",
+ "images": {
+ "hda_disk_image": "OPNsense-24.7-nano-amd64.img"
+ }
+ },
{
"name": "24.1",
"images": {
diff --git a/gns3server/appliances/reactos.gns3a b/gns3server/appliances/reactos.gns3a
index 1b1c2fd8..b1d7fdcc 100644
--- a/gns3server/appliances/reactos.gns3a
+++ b/gns3server/appliances/reactos.gns3a
@@ -24,17 +24,17 @@
},
"images": [
{
- "filename": "ReactOS-0.4.14-release-15-gb6088a6.iso",
- "version": "Installer-0.4.14-release-15",
- "md5sum": "af4be6b27463446905f155f14232d2b4",
+ "filename": "ReactOS-0.4.14-release-21-g1302c1b.iso",
+ "version": "Installer-0.4.14-release-21",
+ "md5sum": "a30bc9143788c76ed584ffd5d25fddfe",
"filesize": 140509184,
"download_url": "https://reactos.org/download",
"direct_download_url": "https://sourceforge.net/projects/reactos/files/ReactOS/0.4.14/ReactOS-0.4.14-release-21-g1302c1b-iso.zip/download"
},
{
- "filename": "ReactOS-0.4.14-release-15-gb6088a6-Live.iso",
- "version": "Live-0.4.14-release-15",
- "md5sum": "73c1a0169a9a3b8a4feb91f4d00f5e97",
+ "filename": "ReactOS-0.4.14-release-21-g1302c1b-Live.iso",
+ "version": "Live-0.4.14-release-21",
+ "md5sum": "fc362820069adeea088b3a48dcfa3f9e",
"filesize": 267386880,
"download_url": "https://reactos.org/download",
"direct_download_url": "https://sourceforge.net/projects/reactos/files/ReactOS/0.4.14/ReactOS-0.4.14-release-21-g1302c1b-live.zip/download"
@@ -50,17 +50,17 @@
],
"versions": [
{
- "name": "Installer-0.4.14-release-15",
+ "name": "Installer-0.4.14-release-21",
"images": {
"hda_disk_image": "empty30G.qcow2",
- "cdrom_image": "ReactOS-0.4.14-release-15-gb6088a6.iso"
+ "cdrom_image": "ReactOS-0.4.14-release-21-g1302c1b.iso"
}
},
{
- "name": "Live-0.4.14-release-15",
+ "name": "Live-0.4.14-release-21",
"images": {
"hda_disk_image": "empty30G.qcow2",
- "cdrom_image": "ReactOS-0.4.14-release-15-gb6088a6-Live.iso"
+ "cdrom_image": "ReactOS-0.4.14-release-21-g1302c1b-Live.iso"
}
}
]
diff --git a/gns3server/appliances/truenas.gns3a b/gns3server/appliances/truenas.gns3a
new file mode 100644
index 00000000..be8e2d03
--- /dev/null
+++ b/gns3server/appliances/truenas.gns3a
@@ -0,0 +1,104 @@
+{
+ "appliance_id": "8c19ccaa-a1d0-4473-94a2-a93b64924d88",
+ "name": "TrueNAS",
+ "category": "guest",
+ "description": "TrueNAS is a family of network-attached storage (NAS) products produced by iXsystems, incorporating both FOSS, as well as commercial offerings. Based on the OpenZFS file system, TrueNAS runs on FreeBSD as well as Linux and is available under the BSD License It is compatible with x86-64 hardware and is also available as turnkey appliances from iXsystems.",
+ "vendor_name": "iXsystems",
+ "vendor_url": "https://www.truenas.com/",
+ "documentation_url": "https://www.truenas.com/docs/",
+ "product_name": "TrueNAS",
+ "product_url": "https://www.truenas.com/",
+ "registry_version": 4,
+ "status": "stable",
+ "maintainer": "GNS3 Team",
+ "maintainer_email": "developers@gns3.net",
+ "usage": "To install TrueNAS SCALE you may have to select the Legacy BIOS option.",
+ "port_name_format": "eth{0}",
+ "qemu": {
+ "adapter_type": "e1000",
+ "adapters": 1,
+ "ram": 8192,
+ "hda_disk_interface": "ide",
+ "hdb_disk_interface": "ide",
+ "arch": "x86_64",
+ "console_type": "vnc",
+ "boot_priority": "cd",
+ "kvm": "require"
+ },
+ "images": [
+ {
+ "filename": "TrueNAS-13.0-U6.2.iso",
+ "version": "CORE 13.0 U6.2",
+ "md5sum": "8b2882b53af5e9f3ca905c6acdee1690",
+ "filesize": 1049112576,
+ "download_url": "https://www.truenas.com/download-truenas-core/",
+ "direct_download_url": "https://download-core.sys.truenas.net/13.0/STABLE/U6.2/x64/TrueNAS-13.0-U6.2.iso"
+ },
+ {
+ "filename": "TrueNAS-13.3-RELEASE.iso",
+ "version": "CORE 13.3 RELEASE",
+ "md5sum": "8bb16cfb06f3f1374a27cf6aebb14ed3",
+ "filesize": 995567616,
+ "download_url": "https://www.truenas.com/download-truenas-core/",
+ "direct_download_url": "https://download-core.sys.truenas.net/13.3/STABLE/RELEASE/x64/TrueNAS-13.3-RELEASE.iso"
+ },
+ {
+ "filename": "TrueNAS-SCALE-24.04.2.2.iso",
+ "version": "SCALE 24.04.2.2",
+ "md5sum": "47d9026254a0775800bb2b8ab6d874fd",
+ "filesize": 1630355456,
+ "download_url": "https://www.truenas.com/download-truenas-scale/",
+ "direct_download_url": "https://download.sys.truenas.net/TrueNAS-SCALE-Dragonfish/24.04.2.2/TrueNAS-SCALE-24.04.2.2.iso"
+ },
+ {
+ "filename": "TrueNAS-SCALE-24.10-BETA.1.iso",
+ "version": "SCALE 24.10-BETA.1",
+ "md5sum": "cc3d5758d1db3d55ae9c8716f5d43b88",
+ "filesize": 1491378176,
+ "download_url": "https://www.truenas.com/download-truenas-scale/",
+ "direct_download_url": "https://download.sys.truenas.net/TrueNAS-SCALE-ElectricEel-BETA/24.10-BETA.1/TrueNAS-SCALE-24.10-BETA.1.iso"
+ },
+ {
+ "filename": "empty30G.qcow2",
+ "version": "1.0",
+ "md5sum": "3411a599e822f2ac6be560a26405821a",
+ "filesize": 197120,
+ "download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/",
+ "direct_download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/empty30G.qcow2/download"
+ }
+ ],
+ "versions": [
+ {
+ "name": "CORE 13.0 U6.2",
+ "images": {
+ "hda_disk_image": "empty30G.qcow2",
+ "hdb_disk_image": "empty30G.qcow2",
+ "cdrom_image": "TrueNAS-13.0-U6.2.iso"
+ }
+ },
+ {
+ "name": "CORE 13.3 RELEASE",
+ "images": {
+ "hda_disk_image": "empty30G.qcow2",
+ "hdb_disk_image": "empty30G.qcow2",
+ "cdrom_image": "TrueNAS-13.3-RELEASE.iso"
+ }
+ },
+ {
+ "name": "SCALE 24.04.2.2",
+ "images": {
+ "hda_disk_image": "empty30G.qcow2",
+ "hdb_disk_image": "empty30G.qcow2",
+ "cdrom_image": "TrueNAS-SCALE-24.04.2.2.iso"
+ }
+ },
+ {
+ "name": "SCALE 24.10-BETA.1",
+ "images": {
+ "hda_disk_image": "empty30G.qcow2",
+ "hdb_disk_image": "empty30G.qcow2",
+ "cdrom_image": "TrueNAS-SCALE-24.10-BETA.1.iso"
+ }
+ }
+ ]
+}
diff --git a/gns3server/appliances/ubuntu-cloud.gns3a b/gns3server/appliances/ubuntu-cloud.gns3a
index cd2eb457..33643ea9 100644
--- a/gns3server/appliances/ubuntu-cloud.gns3a
+++ b/gns3server/appliances/ubuntu-cloud.gns3a
@@ -28,36 +28,44 @@
},
"images": [
{
- "filename": "ubuntu-23.04-server-cloudimg-amd64.img",
- "version": "Ubuntu 23.04 (Lunar Lobster)",
- "md5sum": "369e3b1f68416c39245a8014172406dd",
- "filesize": 756678656,
- "download_url": "https://cloud-images.ubuntu.com/releases/23.04/release/",
- "direct_download_url": "https://cloud-images.ubuntu.com/releases/23.04/release/ubuntu-23.04-server-cloudimg-amd64.img"
+ "filename": "ubuntu-24.10-server-cloudimg-amd64.img",
+ "version": "Ubuntu 24.10 (Oracular Oriole)",
+ "md5sum": "f2960f8743efedd0a4968bfcd9451782",
+ "filesize": 627360256,
+ "download_url": "https://cloud-images.ubuntu.com/releases/oracular/release-20241009/",
+ "direct_download_url": "https://cloud-images.ubuntu.com/releases/oracular/release-20241009/ubuntu-24.10-server-cloudimg-amd64.img"
+ },
+ {
+ "filename": "ubuntu-24.04-server-cloudimg-amd64.img",
+ "version": "Ubuntu 24.04 LTS (Noble Numbat)",
+ "md5sum": "a1c8a01953578ad432cbef03db2f3161",
+ "filesize": 587241984,
+ "download_url": "https://cloud-images.ubuntu.com/releases/noble/release-20241004/",
+ "direct_download_url": "https://cloud-images.ubuntu.com/releases/noble/release-20241004/ubuntu-24.04-server-cloudimg-amd64.img"
},
{
"filename": "ubuntu-22.04-server-cloudimg-amd64.img",
"version": "Ubuntu 22.04 LTS (Jammy Jellyfish)",
- "md5sum": "3ce0b84f9592482fb645e8253b979827",
- "filesize": 686096384,
- "download_url": "https://cloud-images.ubuntu.com/releases/jammy/release",
- "direct_download_url": "https://cloud-images.ubuntu.com/releases/jammy/release/ubuntu-22.04-server-cloudimg-amd64.img"
+ "md5sum": "8f9a70435dc5b0b86cf5d0d4716b6091",
+ "filesize": 653668352,
+ "download_url": "https://cloud-images.ubuntu.com/releases/jammy/release-20241002/",
+ "direct_download_url": "https://cloud-images.ubuntu.com/releases/jammy/release-20241002/ubuntu-22.04-server-cloudimg-amd64.img"
},
{
"filename": "ubuntu-20.04-server-cloudimg-amd64.img",
"version": "Ubuntu 20.04 LTS (Focal Fossa)",
- "md5sum": "044bc979b2238192ee3edb44e2bb6405",
- "filesize": 552337408,
- "download_url": "https://cloud-images.ubuntu.com/releases/focal/release-20210119.1/",
- "direct_download_url": "https://cloud-images.ubuntu.com/releases/focal/release-20210119.1/ubuntu-20.04-server-cloudimg-amd64.img"
+ "md5sum": "1dff90e16acb0167c27ff82e4ac1813a",
+ "filesize": 627310592,
+ "download_url": "https://cloud-images.ubuntu.com/releases/focal/release-20240821/",
+ "direct_download_url": "https://cloud-images.ubuntu.com/releases/focal/release-20240821/ubuntu-20.04-server-cloudimg-amd64.img"
},
{
"filename": "ubuntu-18.04-server-cloudimg-amd64.img",
"version": "Ubuntu 18.04 LTS (Bionic Beaver)",
- "md5sum": "f4134e7fa16d7fa766c7467cbe25c949",
- "filesize": 336134144,
- "download_url": "https://cloud-images.ubuntu.com/releases/18.04/release-20180426.2/",
- "direct_download_url": "https://cloud-images.ubuntu.com/releases/18.04/release-20180426.2/ubuntu-18.04-server-cloudimg-amd64.img"
+ "md5sum": "62fa110eeb0459c1ff166f897aeb9f78",
+ "filesize": 405667840,
+ "download_url": "https://cloud-images.ubuntu.com/releases/bionic/release-20230607/",
+ "direct_download_url": "https://cloud-images.ubuntu.com/releases/bionic/release-20230607/ubuntu-18.04-server-cloudimg-amd64.img"
},
{
"filename": "ubuntu-cloud-init-data.iso",
@@ -70,9 +78,16 @@
],
"versions": [
{
- "name": "Ubuntu 23.04 (Lunar Lobster)",
+ "name": "Ubuntu 24.10 (Oracular Oriole)",
"images": {
- "hda_disk_image": "ubuntu-23.04-server-cloudimg-amd64.img",
+ "hda_disk_image": "ubuntu-24.10-server-cloudimg-amd64.img",
+ "cdrom_image": "ubuntu-cloud-init-data.iso"
+ }
+ },
+ {
+ "name": "Ubuntu 24.04 LTS (Noble Numbat)",
+ "images": {
+ "hda_disk_image": "ubuntu-24.04-server-cloudimg-amd64.img",
"cdrom_image": "ubuntu-cloud-init-data.iso"
}
},
diff --git a/gns3server/appliances/ubuntu-gui.gns3a b/gns3server/appliances/ubuntu-gui.gns3a
index 9e31f420..fa8bb59d 100644
--- a/gns3server/appliances/ubuntu-gui.gns3a
+++ b/gns3server/appliances/ubuntu-gui.gns3a
@@ -27,6 +27,13 @@
"options": "-vga qxl"
},
"images": [
+ {
+ "filename": "Ubuntu 24.04 (64bit).vmdk",
+ "version": "24.04",
+ "md5sum": "7709a5a1cf888c7644d245c42d217918",
+ "filesize": 6432555008,
+ "download_url": "https://www.osboxes.org/ubuntu/"
+ },
{
"filename": "Ubuntu 22.04 (64bit).vmdk",
"version": "22.04",
@@ -57,6 +64,12 @@
}
],
"versions": [
+ {
+ "name": "24.04",
+ "images": {
+ "hda_disk_image": "Ubuntu 24.04 (64bit).vmdk"
+ }
+ },
{
"name": "22.04",
"images": {
diff --git a/gns3server/appliances/viptela-edge-genericx86-64.gns3a b/gns3server/appliances/viptela-edge-genericx86-64.gns3a
index f729590d..4b802954 100644
--- a/gns3server/appliances/viptela-edge-genericx86-64.gns3a
+++ b/gns3server/appliances/viptela-edge-genericx86-64.gns3a
@@ -10,8 +10,8 @@
"product_url": "http://www.cisco.com/",
"registry_version": 4,
"status": "experimental",
- "maintainer": "Laurent LEVIER",
- "maintainer_email": "laurent.levier@orange.com",
+ "maintainer": "Christopher Uhrig",
+ "maintainer_email": "christopher.uhrig@telekom.de",
"usage": "Initial username is admin, password is admin as well.",
"first_port_name": "Management0/0",
"port_name_format": "Ge0/{0}",
@@ -39,6 +39,20 @@
"md5sum": "4aa487101d4cdc390f53a6e8b6f45ca7",
"filesize": 328400896,
"download_url": "http://www.cisco.com/"
+ },
+ {
+ "filename": "viptela-edge-20.9.5.1-genericx86-64.qcow2",
+ "version": "20.9.5.1",
+ "md5sum": "41d9e981908fd83695de78d6ca5794bd",
+ "filesize": 409468928,
+ "download_url": "https://software.cisco.com/download/home/286320995/type/286321047/release/20.9.5.1"
+ },
+ {
+ "filename": "viptela-edge-20.12.4-genericx86-64.qcow2",
+ "version": "20.12.4",
+ "md5sum": "9f1aedada5e632c7bc29a51c004f4486",
+ "filesize": 411762688,
+ "download_url": "https://software.cisco.com/download/home/286320995/type/286321047/release/20.12.4"
}
],
"versions": [
@@ -53,6 +67,18 @@
"images": {
"hda_disk_image": "viptela-edge-genericx86-64.qcow2"
}
+ },
+ {
+ "name": "20.9.5.1",
+ "images": {
+ "hda_disk_image": "viptela-edge-20.9.5.1-genericx86-64.qcow2"
+ }
+ },
+ {
+ "name": "20.12.4",
+ "images": {
+ "hda_disk_image": "viptela-edge-20.12.4-genericx86-64.qcow2"
+ }
}
]
}
diff --git a/gns3server/appliances/viptela-smart-genericx86-64.gns3a b/gns3server/appliances/viptela-smart-genericx86-64.gns3a
index c877743e..bd960219 100644
--- a/gns3server/appliances/viptela-smart-genericx86-64.gns3a
+++ b/gns3server/appliances/viptela-smart-genericx86-64.gns3a
@@ -24,6 +24,20 @@
"options": "-smp 2,maxcpus=2 -cpu host"
},
"images": [
+ {
+ "filename": "viptela-smart-20.12.4-genericx86-64.qcow2",
+ "version": "20.12.4",
+ "md5sum": "0e7b6468498a89195ab815260bc4cfb6",
+ "filesize": 411762688,
+ "download_url": "https://software.cisco.com/download/home/286320995/type/286321043/release/20.12.4"
+ },
+ {
+ "filename": "viptela-smart-20.9.5.1-genericx86-64.qcow2",
+ "version": "20.9.5.1",
+ "md5sum": "08e105778bb68f8f24f323dfd263a91a",
+ "filesize": 409468928,
+ "download_url": "https://software.cisco.com/download/home/286320995/type/286321043/release/20.9.5.1"
+ },
{
"filename": "viptela-smart-19.2.0-genericx86-64.qcow2",
"version": "19.2.0",
@@ -31,7 +45,7 @@
"filesize": 328400896,
"download_url": "http://www.cisco.com/"
},
- {
+ {
"filename": "viptela-smart-genericx86-64-disk1.vmdk",
"version": "18.3.7",
"md5sum": "ab9b06c212319336810a4b336ec3dd96",
@@ -51,6 +65,18 @@
"images": {
"hda_disk_image": "viptela-smart-19.2.0-genericx86-64.qcow2"
}
+ },
+ {
+ "name": "20.9.5.1",
+ "images": {
+ "hda_disk_image": "viptela-smart-20.9.5.1-genericx86-64.qcow2"
+ }
+ },
+ {
+ "name": "20.12.4",
+ "images": {
+ "hda_disk_image": "viptela-smart-20.12.4-genericx86-64.qcow2"
+ }
}
]
}
diff --git a/gns3server/appliances/viptela-vmanage-genericx86-64.gns3a b/gns3server/appliances/viptela-vmanage-genericx86-64.gns3a
index 8c547ec4..30634abc 100644
--- a/gns3server/appliances/viptela-vmanage-genericx86-64.gns3a
+++ b/gns3server/appliances/viptela-vmanage-genericx86-64.gns3a
@@ -10,8 +10,8 @@
"product_url": "http://www.cisco.com/",
"registry_version": 4,
"status": "experimental",
- "maintainer": "Laurent LEVIER",
- "maintainer_email": "laurent.levier@orange.com",
+ "maintainer": "Christopher Uhrig",
+ "maintainer_email": "christopher.uhrig@telekom.de",
"usage": "Initial username is admin, password is admin as well.",
"qemu": {
"adapter_type": "vmxnet3",
@@ -25,14 +25,28 @@
"options": "-cpu host -smp 2,maxcpus=2"
},
"images": [
- {
+ {
+ "filename": "viptela-vmanage-20.12.4-genericx86-64.qcow2",
+ "version": "20.12.4",
+ "md5sum": "4e0d4c379623c495a0bb671a76a12f9f",
+ "filesize": 4951375872,
+ "download_url": "https://software.cisco.com/download/home/286320995/type/286321039/release/20.12.4"
+ },
+ {
+ "filename": "viptela-vmanage-20.9.5.2-genericx86-64.qcow2",
+ "version": "20.9.5.2",
+ "md5sum": "a8a4ebee0274541200f4fe94a739d454",
+ "filesize": 3133865984,
+ "download_url": "https://software.cisco.com/download/home/286320995/type/286321039/release/20.9.5.2"
+ },
+ {
"filename": "viptela-vmanage-19.2.0-genericx86-64.qcow2",
"version": "19.2.0",
"md5sum": "27ef126f178c6c929a36ad2cf6ed8db7",
"filesize": 1185349632,
"download_url": "http://www.cisco.com/"
},
- {
+ {
"filename": "viptela-vmanage-genericx86-64-disk1.vmdk",
"version": "18.3.7",
"md5sum": "2290c6467c907d9ca9c65793fe898716",
@@ -55,6 +69,20 @@
"hda_disk_image": "viptela-vmanage-19.2.0-genericx86-64.qcow2",
"hdb_disk_image": "empty30G.qcow2"
}
+ },
+ {
+ "name": "20.12.4",
+ "images": {
+ "hda_disk_image": "viptela-vmanage-20.12.4-genericx86-64.qcow2",
+ "hdb_disk_image": "empty30G.qcow2"
+ }
+ },
+ {
+ "name": "20.9.5.2",
+ "images": {
+ "hda_disk_image": "viptela-vmanage-20.9.5.2-genericx86-64.qcow2",
+ "hdb_disk_image": "empty30G.qcow2"
+ }
},
{
"name": "18.3.7",
diff --git a/gns3server/appliances/vyos.gns3a b/gns3server/appliances/vyos.gns3a
index 925e39ef..aade8d86 100644
--- a/gns3server/appliances/vyos.gns3a
+++ b/gns3server/appliances/vyos.gns3a
@@ -1,181 +1,178 @@
{
"appliance_id": "f82b74c4-0f30-456f-a582-63daca528502",
- "name": "VyOS",
+ "name": "VyOS Universal Router",
"category": "router",
- "description": "VyOS is a community fork of Vyatta, a Linux-based network operating system that provides software-based network routing, firewall, and VPN functionality.",
- "vendor_name": "Linux",
- "vendor_url": "https://vyos.net/",
+ "description": "VyOS is an open-source network operating system that provides a comprehensive suite of features for routing, firewalling, and VPN functionality. VyOS offers a robust and flexible solution for both small-scale and large-scale network environments. It is designed to support enterprise-grade networking with the added benefits of community-driven development and continuous updates.\n\nThe VyOS Universal Router, when used in GNS3, brings the power and versatility of VyOS to network simulation and emulation. GNS3 users can deploy the VyOS Universal Router to create and test complex network topologies in a virtual environment. This appliance provides a rich set of features, including dynamic routing protocols, stateful firewall capabilities, various VPNs, as well as high availability configurations.\n\nThe seamless integration with GNS3 allows network engineers and architects to validate network designs, perform testing and troubleshooting, and enhance their skill sets in a controlled, risk-free environment.",
+ "vendor_name": "VyOS Inc.",
+ "vendor_url": "https://vyos.io/",
+ "vendor_logo_url": "https://raw.githubusercontent.com/GNS3/gns3-registry/master/vendor-logos/VyOS.png",
"documentation_url": "https://docs.vyos.io/",
- "product_name": "VyOS",
- "product_url": "https://vyos.net/",
+ "product_name": "VyOS Universal Router",
+ "product_url": "https://vyos.io/vyos-universal-router",
"registry_version": 4,
"status": "stable",
- "maintainer": "GNS3 Team",
- "maintainer_email": "developers@gns3.net",
- "usage": "Default username/password is vyos/vyos.\n\nThe -KVM versions are ready to use, no installation is required.\nThe other images will start the router from the CDROM on initial boot. Login and then type \"install image\" and follow the instructions.",
+ "availability": "service-contract",
+ "maintainer": "VyOS Inc.",
+ "maintainer_email": "support@vyos.io",
+ "usage": "\nDefault credentials:\nUser: vyos\nPassword: vyos",
"symbol": "vyos.svg",
"port_name_format": "eth{0}",
"qemu": {
- "adapter_type": "e1000",
- "adapters": 3,
- "ram": 512,
- "hda_disk_interface": "scsi",
+ "adapter_type": "virtio-net-pci",
+ "adapters": 10,
+ "ram": 2048,
+ "cpus": 4,
+ "hda_disk_interface": "virtio",
"arch": "x86_64",
"console_type": "telnet",
- "boot_priority": "cd",
- "kvm": "allow"
+ "boot_priority": "c",
+ "kvm": "require",
+ "on_close": "shutdown_signal"
},
"images": [
{
- "filename": "vyos-1.3.2-amd64.iso",
+ "filename": "vyos-1.4.0-kvm-amd64.qcow2",
+ "version": "1.4.0",
+ "md5sum": "a130e446bc5bf87391981f183ee3694b",
+ "filesize": 468320256,
+ "download_url": "https://support.vyos.io/"
+ },
+ {
+ "filename": "vyos-1.3.7-qemu-amd64.qcow2",
+ "version": "1.3.7",
+ "md5sum": "f4663b1e2df115bfa5c7ec17584514d6",
+ "filesize": 359792640,
+ "download_url": "https://support.vyos.io/"
+ },
+ {
+ "filename": "vyos-1.3.2-10G-qemu.qcow2",
"version": "1.3.2",
- "md5sum": "070743faac800f9e5197058a8b6b3ba1",
- "filesize": 334495744,
- "download_url": "https://support.vyos.io/en/downloads/files/vyos-1-3-2-generic-iso-image"
+ "md5sum": "68ad3fb530213189ac9ed496d5fe7897",
+ "filesize": 326893568,
+ "download_url": "https://support.vyos.io/"
},
{
- "filename": "vyos-1.3.1-S1-amd64.iso",
+ "filename": "vyos-1.3.1-S1-10G-qemu.qcow2",
"version": "1.3.1-S1",
- "md5sum": "781f345e8a4ab9eb9e075ce5c87c8817",
- "filesize": 351272960,
- "download_url": "https://support.vyos.io/en/downloads/files/vyos-1-3-1-s1-generic-iso-image"
+ "md5sum": "d8ed9f82a983295b94b07f8e37c48ed0",
+ "filesize": 343801856,
+ "download_url": "https://support.vyos.io/"
},
{
- "filename": "vyos-1.3.1-amd64.iso",
+ "filename": "vyos-1.3.1-10G-qemu.qcow2",
"version": "1.3.1",
- "md5sum": "b6f57bd0cf9b60cdafa337b08ba4f2bc",
- "filesize": 350224384,
- "download_url": "https://support.vyos.io/en/downloads/files/vyos-1-3-1-generic-iso-image"
+ "md5sum": "482367c833990fb2b9350e3708d33dc9",
+ "filesize": 342556672,
+ "download_url": "https://support.vyos.io/"
},
{
- "filename": "vyos-1.3.0-amd64.iso",
+ "filename": "vyos-1.3.0-10G-qemu.qcow2",
"version": "1.3.0",
- "md5sum": "2019bd9c5efa6194e2761de678d0073f",
- "filesize": 338690048,
- "download_url": "https://support.vyos.io/en/downloads/files/vyos-1-3-0-generic-iso-image"
- },
- {
- "filename": "vyos-1.2.9-S1-amd64.iso",
- "version": "1.2.9-S1",
- "md5sum": "3fece6363f9766f862e26d292d0ed5a3",
- "filesize": 430964736,
- "download_url": "https://support.vyos.io/en/downloads/files/vyos-1-2-9-s1-generic-iso-image",
- "direct_download_url": "https://legacy-lts-images.vyos.io/1.2.9-S1/vyos-1.2.9-S1-amd64.iso"
+ "md5sum": "086e95e992e9b4d014c5f154cd01a6e6",
+ "filesize": 330956800,
+ "download_url": "https://support.vyos.io/"
},
{
"filename": "vyos-1.2.9-S1-10G-qemu.qcow2",
- "version": "1.2.9-S1-KVM",
+ "version": "1.2.9-S1",
"md5sum": "0a70d78b80a3716d42487c02ef44f41f",
"filesize": 426967040,
- "download_url": "https://support.vyos.io/en/downloads/files/vyos-1-2-9-s1-for-kvm",
- "direct_download_url": "https://legacy-lts-images.vyos.io/1.2.9-S1/vyos-1.2.9-S1-10G-qemu.qcow2"
+ "download_url": "https://support.vyos.io/"
},
{
- "filename": "vyos-1.2.9-amd64.iso",
- "version": "1.2.9",
- "md5sum": "586be23b6256173e174c82d8f1f699a1",
- "filesize": 430964736,
- "download_url": "https://support.vyos.io/en/downloads/files/vyos-1-2-9-generic-iso-image",
- "direct_download_url": "https://legacy-lts-images.vyos.io/1.2.9/vyos-1.2.9-amd64.iso"
- },
- {
- "filename": "vyos-1.2.9-10G-qemu.qcow2",
- "version": "1.2.9-KVM",
- "md5sum": "76871c7b248c32f75177c419128257ac",
- "filesize": 427360256,
- "download_url": "https://support.vyos.io/en/downloads/files/vyos-1-2-9-10g-qemu-qcow2",
- "direct_download_url": "https://legacy-lts-images.vyos.io/1.2.9/vyos-1.2.9-10G-qemu.qcow2"
- },
- {
- "filename": "vyos-1.2.8-amd64.iso",
+ "filename": "vyos-1.2.8-10G-qemu.qcow2",
"version": "1.2.8",
- "md5sum": "0ad879db903efdbf1c39dc945e165931",
- "filesize": 429916160,
- "download_url": "https://support.vyos.io/en/downloads/files/vyos-1-2-8-generic-iso-image"
+ "md5sum": "96c76f619d0f8ea11dc8a3a18ed67b98",
+ "filesize": 425852928,
+ "download_url": "https://support.vyos.io/"
},
{
- "filename": "vyos-1.1.8-amd64.iso",
- "version": "1.1.8",
- "md5sum": "95a141d4b592b81c803cdf7e9b11d8ea",
- "filesize": 241172480,
- "direct_download_url": "https://legacy-lts-images.vyos.io/vyos-1.1.8-amd64.iso"
+ "filename": "vyos-1.2.7-qemu.qcow2",
+ "version": "1.2.7",
+ "md5sum": "1be4674c970fcdd65067e504baea5d74",
+ "filesize": 424607744,
+ "download_url": "https://support.vyos.io/"
},
{
- "filename": "empty8G.qcow2",
- "version": "1.0",
- "md5sum": "f1d2c25b6990f99bd05b433ab603bdb4",
- "filesize": 197120,
- "download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/",
- "direct_download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/empty8G.qcow2/download"
+ "filename": "vyos-1.2.6-qemu.qcow2",
+ "version": "1.2.6",
+ "md5sum": "d8010d79889ca0ba5cb2634665e548e3",
+ "filesize": 424607744,
+ "download_url": "https://support.vyos.io/"
+ },
+ {
+ "filename": "vyos-1.2.5-amd64.qcow2",
+ "version": "1.2.5",
+ "md5sum": "110c22309ec480600446fd2fb4f27a0d",
+ "filesize": 411500544 ,
+ "download_url": "https://support.vyos.io/"
}
],
"versions": [
+ {
+ "name": "1.4.0",
+ "images": {
+ "hda_disk_image": "vyos-1.4.0-kvm-amd64.qcow2"
+ }
+ },
+ {
+ "name": "1.3.7",
+ "images": {
+ "hda_disk_image": "vyos-1.3.7-qemu-amd64.qcow2"
+ }
+ },
{
"name": "1.3.2",
"images": {
- "hda_disk_image": "empty8G.qcow2",
- "cdrom_image": "vyos-1.3.2-amd64.iso"
+ "hda_disk_image": "vyos-1.3.2-10G-qemu.qcow2"
}
},
{
"name": "1.3.1-S1",
"images": {
- "hda_disk_image": "empty8G.qcow2",
- "cdrom_image": "vyos-1.3.1-S1-amd64.iso"
+ "hda_disk_image": "vyos-1.3.1-S1-10G-qemu.qcow2"
}
},
{
"name": "1.3.1",
"images": {
- "hda_disk_image": "empty8G.qcow2",
- "cdrom_image": "vyos-1.3.1-amd64.iso"
+ "hda_disk_image": "vyos-1.3.1-10G-qemu.qcow2"
}
},
{
"name": "1.3.0",
"images": {
- "hda_disk_image": "empty8G.qcow2",
- "cdrom_image": "vyos-1.3.0-amd64.iso"
+ "hda_disk_image": "vyos-1.3.0-10G-qemu.qcow2"
}
},
{
"name": "1.2.9-S1",
- "images": {
- "hda_disk_image": "empty8G.qcow2",
- "cdrom_image": "vyos-1.2.9-S1-amd64.iso"
- }
- },
- {
- "name": "1.2.9-S1-KVM",
"images": {
"hda_disk_image": "vyos-1.2.9-S1-10G-qemu.qcow2"
}
},
- {
- "name": "1.2.9",
- "images": {
- "hda_disk_image": "empty8G.qcow2",
- "cdrom_image": "vyos-1.2.9-amd64.iso"
- }
- },
- {
- "name": "1.2.9-KVM",
- "images": {
- "hda_disk_image": "vyos-1.2.9-10G-qemu.qcow2"
- }
- },
{
"name": "1.2.8",
"images": {
- "hda_disk_image": "empty8G.qcow2",
- "cdrom_image": "vyos-1.2.8-amd64.iso"
+ "hda_disk_image": "vyos-1.2.8-10G-qemu.qcow2"
}
},
{
- "name": "1.1.8",
+ "name": "1.2.7",
"images": {
- "hda_disk_image": "empty8G.qcow2",
- "cdrom_image": "vyos-1.1.8-amd64.iso"
+ "hda_disk_image": "vyos-1.2.7-qemu.qcow2"
+ }
+ },
+ {
+ "name": "1.2.6",
+ "images": {
+ "hda_disk_image": "vyos-1.2.6-qemu.qcow2"
+ }
+ },
+ {
+ "name": "1.2.5",
+ "images": {
+ "hda_disk_image": "vyos-1.2.5-amd64.qcow2"
}
}
]
diff --git a/gns3server/compute/base_node.py b/gns3server/compute/base_node.py
index 20f437be..c7a3821c 100644
--- a/gns3server/compute/base_node.py
+++ b/gns3server/compute/base_node.py
@@ -742,6 +742,7 @@ class BaseNode:
path = shutil.which(self._manager.config.settings.Server.ubridge_path)
return path
+ @locking
async def _ubridge_send(self, command):
"""
Sends a command to uBridge hypervisor.
diff --git a/gns3server/compute/docker/docker_vm.py b/gns3server/compute/docker/docker_vm.py
index 8d0e0f50..4dbced0e 100644
--- a/gns3server/compute/docker/docker_vm.py
+++ b/gns3server/compute/docker/docker_vm.py
@@ -34,6 +34,7 @@ from gns3server.utils.asyncio import wait_for_file_creation
from gns3server.utils.asyncio import monitor_process
from gns3server.utils.get_resource import get_resource
from gns3server.utils.hostname import is_rfc1123_hostname_valid
+from gns3server.utils import macaddress_to_int, int_to_macaddress
from gns3server.compute.ubridge.ubridge_error import UbridgeError, UbridgeNamespaceError
from ..base_node import BaseNode
@@ -78,6 +79,7 @@ class DockerVM(BaseNode):
aux=None,
start_command=None,
adapters=None,
+ mac_address="",
environment=None,
console_type="telnet",
aux_type="none",
@@ -130,6 +132,8 @@ class DockerVM(BaseNode):
else:
self.adapters = adapters
+ self.mac_address = mac_address
+
log.debug(
"{module}: {name} [{image}] initialized.".format(
module=self.manager.module_name, name=self.name, image=self._image
@@ -145,6 +149,7 @@ class DockerVM(BaseNode):
"project_id": self._project.id,
"image": self._image,
"adapters": self.adapters,
+ "mac_address": self.mac_address,
"console": self.console,
"console_type": self.console_type,
"console_resolution": self.console_resolution,
@@ -190,6 +195,36 @@ class DockerVM(BaseNode):
def ethernet_adapters(self):
return self._ethernet_adapters
+ @property
+ def mac_address(self):
+ """
+ Returns the MAC address for this Docker container.
+
+ :returns: adapter type (string)
+ """
+
+ return self._mac_address
+
+ @mac_address.setter
+ def mac_address(self, mac_address):
+ """
+ Sets the MAC address for this Docker container.
+
+ :param mac_address: MAC address
+ """
+
+ if not mac_address:
+ # use the node UUID to generate a random MAC address
+ self._mac_address = "02:42:%s:%s:%s:00" % (self.id[2:4], self.id[4:6], self.id[6:8])
+ else:
+ self._mac_address = mac_address
+
+ log.info('Docker container "{name}" [{id}]: MAC address changed to {mac_addr}'.format(
+ name=self._name,
+ id=self._id,
+ mac_addr=self._mac_address)
+ )
+
@property
def start_command(self):
return self._start_command
@@ -429,6 +464,7 @@ class DockerVM(BaseNode):
"Mounts": self._mount_binds(image_infos),
"Memory": self._memory * (1024 * 1024), # convert memory to bytes
"NanoCpus": int(self._cpus * 1e9), # convert cpus to nano cpus
+ "UsernsMode": "host"
},
"Volumes": {},
"Env": ["container=docker"], # Systemd compliant: https://github.com/GNS3/gns3-server/issues/573
@@ -799,7 +835,10 @@ class DockerVM(BaseNode):
"""
# resize the container TTY.
- await self._manager.query("POST", f"containers/{self._cid}/resize?h={rows}&w={columns}")
+ try:
+ await self._manager.query("POST", f"containers/{self._cid}/resize?h={rows}&w={columns}")
+ except DockerError as e:
+ log.warning(f"Could not resize the container TTY: {e}")
async def _start_console(self):
"""
@@ -1058,7 +1097,20 @@ class DockerVM(BaseNode):
adapter_number=adapter_number, hostif=adapter.host_ifc
)
)
- log.debug("Move container %s adapter %s to namespace %s", self.name, adapter.host_ifc, self._namespace)
+
+ mac_address = int_to_macaddress(macaddress_to_int(self._mac_address) + adapter_number)
+ custom_adapter = self._get_custom_adapter_settings(adapter_number)
+ custom_mac_address = custom_adapter.get("mac_address")
+ if custom_mac_address:
+ mac_address = custom_mac_address
+
+ try:
+ await self._ubridge_send('docker set_mac_addr {ifc} {mac}'.format(ifc=adapter.host_ifc, mac=mac_address))
+ except UbridgeError:
+ log.warning(f"Could not set MAC address {mac_address} on interface {adapter.host_ifc}")
+
+
+ log.debug(f"Move container {self.name} adapter {adapter.host_ifc} to namespace {self._namespace}")
try:
await self._ubridge_send(
"docker move_to_ns {ifc} {ns} eth{adapter}".format(
@@ -1067,6 +1119,8 @@ class DockerVM(BaseNode):
)
except UbridgeError as e:
raise UbridgeNamespaceError(e)
+ else:
+ log.info(f"Created adapter {adapter_number} with MAC address {mac_address} in namespace {self._namespace}")
if nio:
await self._connect_nio(adapter_number, nio)
diff --git a/gns3server/compute/docker/resources/init.sh b/gns3server/compute/docker/resources/init.sh
index 695a7998..05c6ee2e 100755
--- a/gns3server/compute/docker/resources/init.sh
+++ b/gns3server/compute/docker/resources/init.sh
@@ -25,7 +25,10 @@ PATH=/gns3/bin:/tmp/gns3/bin:/sbin:$PATH
# bootstrap busybox commands
if [ ! -d /tmp/gns3/bin ]; then
busybox mkdir -p /tmp/gns3/bin
- /gns3/bin/busybox --install -s /tmp/gns3/bin
+ for applet in `busybox --list`
+ do
+ ln -s /gns3/bin/busybox "/tmp/gns3/bin/$applet"
+ done
fi
# Restore file permission and mount volumes
@@ -75,7 +78,7 @@ ip link set dev lo up
while true
do
grep $GNS3_MAX_ETHERNET /proc/net/dev > /dev/null && break
- sleep 0.5
+ usleep 500000 # wait 0.5 seconds
done
# activate eth interfaces
diff --git a/gns3server/compute/dynamips/__init__.py b/gns3server/compute/dynamips/__init__.py
index 7f3d1e13..eae8ab61 100644
--- a/gns3server/compute/dynamips/__init__.py
+++ b/gns3server/compute/dynamips/__init__.py
@@ -521,10 +521,6 @@ class Dynamips(BaseManager):
if usage is not None and usage != vm.usage:
vm.usage = usage
- aux_type = settings.get("aux_type")
- if aux_type is not None and aux_type != vm.aux_type:
- vm.aux_type = aux_type
-
# update the configs if needed
await self.set_vm_configs(vm, settings)
diff --git a/gns3server/compute/dynamips/nodes/router.py b/gns3server/compute/dynamips/nodes/router.py
index 97bcbbd0..1eea64e3 100644
--- a/gns3server/compute/dynamips/nodes/router.py
+++ b/gns3server/compute/dynamips/nodes/router.py
@@ -1030,6 +1030,26 @@ class Router(BaseNode):
self.aux = aux
await self._hypervisor.send(f'vm set_aux_tcp_port "{self._name}" {aux}')
+ async def set_aux_type(self, aux_type):
+ """
+ Sets the aux type.
+
+ :param aux_type: auxiliary console type
+ """
+
+ if self.aux_type != aux_type:
+ status = await self.get_status()
+ if status == "running":
+ raise DynamipsError('"{name}" must be stopped to change the auxiliary console type to {aux_type}'.format(
+ name=self._name,
+ aux_type=aux_type)
+ )
+
+ self.aux_type = aux_type
+
+ if self._aux and aux_type == "telnet":
+ await self._hypervisor.send(f'vm set_aux_tcp_port "{self._name}" {self._aux}')
+
async def reset_console(self):
"""
Reset console
diff --git a/gns3server/compute/notification_manager.py b/gns3server/compute/notification_manager.py
index 82388b0a..30f0a805 100644
--- a/gns3server/compute/notification_manager.py
+++ b/gns3server/compute/notification_manager.py
@@ -15,7 +15,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-
+import asyncio
from contextlib import contextmanager
from gns3server.utils.notification_queue import NotificationQueue
@@ -54,7 +54,7 @@ class NotificationManager:
"""
for listener in self._listeners:
- listener.put_nowait((action, event, kwargs))
+ asyncio.get_running_loop().call_soon_threadsafe(listener.put_nowait, (action, event, kwargs))
@staticmethod
def reset():
diff --git a/gns3server/compute/qemu/qemu_vm.py b/gns3server/compute/qemu/qemu_vm.py
index 35a1b89d..10023233 100644
--- a/gns3server/compute/qemu/qemu_vm.py
+++ b/gns3server/compute/qemu/qemu_vm.py
@@ -2157,11 +2157,6 @@ class QemuVM(BaseNode):
continue
interface = getattr(self, f"hd{drive}_disk_interface")
- # fail-safe: use "ide" if there is a disk image and no interface type has been explicitly configured
- if interface == "none":
- interface = "ide"
- setattr(self, f"hd{drive}_disk_interface", interface)
-
disk_name = f"hd{drive}"
if not os.path.isfile(disk_image) or not os.path.exists(disk_image):
if os.path.islink(disk_image):
diff --git a/gns3server/controller/__init__.py b/gns3server/controller/__init__.py
index 6f82a42d..202beef3 100644
--- a/gns3server/controller/__init__.py
+++ b/gns3server/controller/__init__.py
@@ -30,7 +30,7 @@ except ImportError:
from ..config import Config
-from ..utils import parse_version
+from ..utils import parse_version, md5sum
from ..utils.images import default_images_directory
from .project import Project
@@ -91,7 +91,7 @@ class Controller:
if server_config.enable_ssl:
self._ssl_context = self._create_ssl_context(server_config)
- protocol = server_config.protocol
+ protocol = server_config.protocol.value
if self._ssl_context and protocol != "https":
log.warning(f"Protocol changed to 'https' for local compute because SSL is enabled")
protocol = "https"
@@ -308,12 +308,21 @@ class Controller:
except OSError as e:
log.error(str(e))
+
@staticmethod
- def install_resource_files(dst_path, resource_name):
+ def install_resource_files(dst_path, resource_name, upgrade_resources=True):
"""
Install files from resources to user's file system
"""
+ def should_copy(src, dst, upgrade_resources):
+ if not os.path.exists(dst):
+ return True
+ if upgrade_resources is False:
+ return False
+ # copy the resource if it is different
+ return md5sum(src) != md5sum(dst)
+
if hasattr(sys, "frozen") and sys.platform.startswith("win"):
resource_path = os.path.normpath(os.path.join(os.path.dirname(sys.executable), resource_name))
for filename in os.listdir(resource_path):
@@ -322,7 +331,7 @@ class Controller:
else:
for entry in importlib_resources.files('gns3server').joinpath(resource_name).iterdir():
full_path = os.path.join(dst_path, entry.name)
- if entry.is_file() and not os.path.exists(full_path):
+ if entry.is_file() and should_copy(str(entry), full_path, upgrade_resources):
log.debug(f'Installing {resource_name} resource file "{entry.name}" to "{full_path}"')
shutil.copy(str(entry), os.path.join(dst_path, entry.name))
elif entry.is_dir():
@@ -338,7 +347,7 @@ class Controller:
dst_path = self.configs_path()
log.info(f"Installing base configs in '{dst_path}'")
try:
- Controller.install_resource_files(dst_path, "configs")
+ Controller.install_resource_files(dst_path, "configs", upgrade_resources=False)
except OSError as e:
log.error(f"Could not install base config files to {dst_path}: {e}")
@@ -351,7 +360,7 @@ class Controller:
dst_path = self.disks_path()
log.info(f"Installing built-in disks in '{dst_path}'")
try:
- Controller.install_resource_files(dst_path, "disks")
+ Controller.install_resource_files(dst_path, "disks", upgrade_resources=False)
except OSError as e:
log.error(f"Could not install disk files to {dst_path}: {e}")
diff --git a/gns3server/controller/appliance_manager.py b/gns3server/controller/appliance_manager.py
index d132fe19..9bf1eb80 100644
--- a/gns3server/controller/appliance_manager.py
+++ b/gns3server/controller/appliance_manager.py
@@ -95,7 +95,7 @@ class ApplianceManager:
os.makedirs(appliances_path, exist_ok=True)
return appliances_path
- def builtin_appliances_path(self, delete_first=False):
+ def builtin_appliances_path(self):
"""
Get the built-in appliance storage directory
"""
@@ -107,8 +107,6 @@ class ApplianceManager:
else:
resources_path = os.path.expanduser(resources_path)
appliances_dir = os.path.join(resources_path, "appliances")
- if delete_first:
- shutil.rmtree(appliances_dir, ignore_errors=True)
os.makedirs(appliances_dir, exist_ok=True)
return appliances_dir
@@ -117,7 +115,7 @@ class ApplianceManager:
At startup we copy the built-in appliances files.
"""
- dst_path = self.builtin_appliances_path(delete_first=True)
+ dst_path = self.builtin_appliances_path()
log.info(f"Installing built-in appliances in '{dst_path}'")
from . import Controller
try:
diff --git a/gns3server/controller/export_project.py b/gns3server/controller/export_project.py
index 7c0eb54e..29226fc7 100644
--- a/gns3server/controller/export_project.py
+++ b/gns3server/controller/export_project.py
@@ -16,6 +16,7 @@
# along with this program. If not, see .
import os
+import sys
import json
import asyncio
import aiofiles
@@ -89,14 +90,15 @@ async def export_project(
files = [f for f in files if _is_exportable(os.path.join(root, f), include_snapshots)]
for file in files:
path = os.path.join(root, file)
- # check if we can export the file
- try:
- open(path).close()
- except OSError as e:
- msg = f"Could not export file {path}: {e}"
- log.warning(msg)
- project.emit_notification("log.warning", {"message": msg})
- continue
+ if not os.path.islink(path):
+ try:
+ # check if we can export the file
+ open(path).close()
+ except OSError as e:
+ msg = f"Could not export file {path}: {e}"
+ log.warning(msg)
+ project.emit_notification("log.warning", {"message": msg})
+ continue
# ignore the .gns3 file
if file.endswith(".gns3"):
continue
@@ -150,7 +152,10 @@ def _patch_mtime(path):
:param path: file path
"""
- st = os.stat(path)
+ if sys.platform.startswith("win"):
+ # only UNIX type platforms
+ return
+ st = os.stat(path, follow_symlinks=False)
file_date = datetime.fromtimestamp(st.st_mtime)
if file_date.year < 1980:
new_mtime = file_date.replace(year=1980).timestamp()
@@ -166,10 +171,6 @@ def _is_exportable(path, include_snapshots=False):
if include_snapshots is False and path.endswith("snapshots"):
return False
- # do not export symlinks
- if os.path.islink(path):
- return False
-
# do not export directories of snapshots
if include_snapshots is False and "{sep}snapshots{sep}".format(sep=os.path.sep) in path:
return False
@@ -228,13 +229,16 @@ async def _patch_project_file(
if not keep_compute_ids:
node["compute_id"] = "local" # To make project portable all node by default run on local
- if "properties" in node and node["node_type"] != "docker":
+ if "properties" in node:
for prop, value in node["properties"].items():
# reset the MAC address
if reset_mac_addresses and prop in ("mac_addr", "mac_address"):
node["properties"][prop] = None
+ if node["node_type"] == "docker":
+ continue
+
if node["node_type"] == "iou":
if not prop == "path":
continue
diff --git a/gns3server/controller/import_project.py b/gns3server/controller/import_project.py
index 49b59f8b..4af3717c 100644
--- a/gns3server/controller/import_project.py
+++ b/gns3server/controller/import_project.py
@@ -17,6 +17,7 @@
import os
import sys
+import stat
import json
import uuid
import shutil
@@ -95,6 +96,7 @@ async def import_project(controller, project_id, stream, location=None, name=Non
try:
with zipfile_zstd.ZipFile(stream) as zip_file:
await wait_run_in_executor(zip_file.extractall, path)
+ _create_symbolic_links(zip_file, path)
except zipfile_zstd.BadZipFile:
raise ControllerError("Cannot extract files from GNS3 project (invalid zip)")
@@ -184,6 +186,24 @@ async def import_project(controller, project_id, stream, location=None, name=Non
project = await controller.load_project(dot_gns3_path, load=False)
return project
+def _create_symbolic_links(zip_file, path):
+ """
+ Manually create symbolic links (if any) because ZipFile does not support it.
+
+ :param zip_file: ZipFile instance
+ :param path: project location
+ """
+
+ for zip_info in zip_file.infolist():
+ if stat.S_ISLNK(zip_info.external_attr >> 16):
+ symlink_target = zip_file.read(zip_info.filename).decode()
+ symlink_path = os.path.join(path, zip_info.filename)
+ try:
+ # remove the regular file and replace it by a symbolic link
+ os.remove(symlink_path)
+ os.symlink(symlink_target, symlink_path)
+ except OSError as e:
+ raise ControllerError(f"Cannot create symbolic link: {e}")
def _move_node_file(path, old_id, new_id):
"""
@@ -269,6 +289,7 @@ async def _import_snapshots(snapshots_path, project_name, project_id):
with open(snapshot_path, "rb") as f:
with zipfile_zstd.ZipFile(f) as zip_file:
await wait_run_in_executor(zip_file.extractall, tmpdir)
+ _create_symbolic_links(zip_file, tmpdir)
except OSError as e:
raise ControllerError(f"Cannot open snapshot '{os.path.basename(snapshot)}': {e}")
except zipfile_zstd.BadZipFile:
diff --git a/gns3server/controller/node.py b/gns3server/controller/node.py
index 839f36c4..ebb7be38 100644
--- a/gns3server/controller/node.py
+++ b/gns3server/controller/node.py
@@ -29,8 +29,8 @@ from .controller_error import (
)
from .ports.port_factory import PortFactory, StandardPortFactory, DynamipsPortFactory
from ..utils.images import images_directories
+from ..utils import macaddress_to_int, int_to_macaddress
from ..config import Config
-from ..utils.qt import qt_font_to_style
import logging
@@ -758,7 +758,13 @@ class Node:
break
port_name = f"eth{adapter_number}"
port_name = custom_adapter_settings.get("port_name", port_name)
- self._ports.append(PortFactory(port_name, 0, adapter_number, 0, "ethernet", short_name=port_name))
+ mac_address = custom_adapter_settings.get("mac_address")
+ if not mac_address and "mac_address" in self._properties:
+ mac_address = int_to_macaddress(macaddress_to_int(self._properties["mac_address"]) + adapter_number)
+
+ port = PortFactory(port_name, 0, adapter_number, 0, "ethernet", short_name=port_name)
+ port.mac_address = mac_address
+ self._ports.append(port)
elif self._node_type in ("ethernet_switch", "ethernet_hub"):
# Basic node we don't want to have adapter number
port_number = 0
diff --git a/gns3server/controller/notification.py b/gns3server/controller/notification.py
index 48d5a3d2..3a63d9b8 100644
--- a/gns3server/controller/notification.py
+++ b/gns3server/controller/notification.py
@@ -15,7 +15,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-import os
+import asyncio
from contextlib import contextmanager
from gns3server.utils.notification_queue import NotificationQueue
@@ -73,7 +73,7 @@ class Notification:
"""
for controller_listener in self._controller_listeners:
- controller_listener.put_nowait((action, event, {}))
+ asyncio.get_running_loop().call_soon_threadsafe(controller_listener.put_nowait, (action, event, {}))
def project_has_listeners(self, project_id):
"""
@@ -134,7 +134,7 @@ class Notification:
except KeyError:
return
for listener in project_listeners:
- listener.put_nowait((action, event, {}))
+ asyncio.get_running_loop().call_soon_threadsafe(listener.put_nowait, (action, event, {}))
def _send_event_to_all_projects(self, action, event):
"""
@@ -146,4 +146,4 @@ class Notification:
"""
for project_listeners in self._project_listeners.values():
for listener in project_listeners:
- listener.put_nowait((action, event, {}))
+ asyncio.get_running_loop().call_soon_threadsafe(listener.put_nowait, (action, event, {}))
diff --git a/gns3server/controller/project.py b/gns3server/controller/project.py
index 80b9dd56..c1da4586 100644
--- a/gns3server/controller/project.py
+++ b/gns3server/controller/project.py
@@ -15,6 +15,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+import sys
import re
import os
import json
@@ -26,6 +27,7 @@ import asyncio
import aiofiles
import tempfile
import zipfile
+import pathlib
from uuid import UUID, uuid4
@@ -41,8 +43,9 @@ from ..utils.application_id import get_next_application_id
from ..utils.asyncio.pool import Pool
from ..utils.asyncio import locking
from ..utils.asyncio import aiozipstream
+from ..utils.asyncio import wait_run_in_executor
from .export_project import export_project
-from .import_project import import_project
+from .import_project import import_project, _move_node_file
from .controller_error import ControllerError, ControllerForbiddenError, ControllerNotFoundError
import logging
@@ -1062,13 +1065,15 @@ class Project:
"""
Duplicate a project
- It's the save as feature of the 1.X. It's implemented on top of the
- export / import features. It will generate a gns3p and reimport it.
- It's a little slower but we have only one implementation to maintain.
+ Implemented on top of the export / import features. It will generate a gns3p and reimport it.
+
+ NEW: fast duplication is used if possible (when there are no remote computes).
+ If not, the project is exported and reimported as explained above.
:param name: Name of the new project. A new one will be generated in case of conflicts
:param reset_mac_addresses: Reset MAC addresses for the new project
"""
+
# If the project was not open we open it temporary
previous_status = self._status
if self._status == "closed":
@@ -1076,6 +1081,18 @@ class Project:
self.dump()
assert self._status != "closed"
+
+ try:
+ proj = await self._fast_duplication(name, reset_mac_addresses)
+ if proj:
+ if previous_status == "closed":
+ await self.close()
+ return proj
+ else:
+ log.info("Fast duplication failed, fallback to normal duplication")
+ except Exception as e:
+ raise ControllerError(f"Cannot duplicate project: {str(e)}")
+
try:
begin = time.time()
@@ -1314,3 +1331,70 @@ class Project:
def __repr__(self):
return f""
+
+ async def _fast_duplication(self, name=None, reset_mac_addresses=True):
+ """
+ Fast duplication of a project.
+
+ Copy the project files directly rather than in an import-export fashion.
+
+ :param name: Name of the new project. A new one will be generated in case of conflicts
+ :param location: Parent directory of the new project
+ :param reset_mac_addresses: Reset MAC addresses for the new project
+ """
+
+ # remote replication is not supported with remote computes
+ for compute in self.computes:
+ if compute.id != "local":
+ log.warning("Fast duplication is not supported with remote compute: '{}'".format(compute.id))
+ return None
+ # work dir
+ p_work = pathlib.Path(self.path).parent.absolute()
+ t0 = time.time()
+ new_project_id = str(uuid.uuid4())
+ new_project_path = p_work.joinpath(new_project_id)
+ # copy dir
+ await wait_run_in_executor(shutil.copytree, self.path, new_project_path.as_posix(), symlinks=True, ignore_dangling_symlinks=True)
+ log.info("Project content copied from '{}' to '{}' in {}s".format(self.path, new_project_path, time.time() - t0))
+ topology = json.loads(new_project_path.joinpath('{}.gns3'.format(self.name)).read_bytes())
+ project_name = name or topology["name"]
+ # If the project name is already used we generate a new one
+ project_name = self.controller.get_free_project_name(project_name)
+ topology["name"] = project_name
+ # To avoid unexpected behavior (project start without manual operations just after import)
+ topology["auto_start"] = False
+ topology["auto_open"] = False
+ topology["auto_close"] = False
+ # change node ID
+ node_old_to_new = {}
+ for node in topology["topology"]["nodes"]:
+ new_node_id = str(uuid.uuid4())
+ if "node_id" in node:
+ node_old_to_new[node["node_id"]] = new_node_id
+ _move_node_file(new_project_path, node["node_id"], new_node_id)
+ node["node_id"] = new_node_id
+ if reset_mac_addresses:
+ if "properties" in node:
+ for prop, value in node["properties"].items():
+ # reset the MAC address
+ if prop in ("mac_addr", "mac_address"):
+ node["properties"][prop] = None
+ # change link ID
+ for link in topology["topology"]["links"]:
+ link["link_id"] = str(uuid.uuid4())
+ for node in link["nodes"]:
+ node["node_id"] = node_old_to_new[node["node_id"]]
+ # Generate new drawings id
+ for drawing in topology["topology"]["drawings"]:
+ drawing["drawing_id"] = str(uuid.uuid4())
+
+ # And we dump the updated.gns3
+ dot_gns3_path = new_project_path.joinpath('{}.gns3'.format(project_name))
+ topology["project_id"] = new_project_id
+ with open(dot_gns3_path, "w+") as f:
+ json.dump(topology, f, indent=4)
+
+ os.remove(new_project_path.joinpath('{}.gns3'.format(self.name)))
+ project = await self.controller.load_project(dot_gns3_path, load=False)
+ log.info("Project '{}' fast duplicated in {:.4f} seconds".format(project.name, time.time() - t0))
+ return project
diff --git a/gns3server/controller/symbols.py b/gns3server/controller/symbols.py
index 69ec399c..2a7ee788 100644
--- a/gns3server/controller/symbols.py
+++ b/gns3server/controller/symbols.py
@@ -45,7 +45,7 @@ class Symbols:
self._symbol_size_cache = {}
self._server_config = Config.instance().settings.Server
- self._current_theme = self._server_config.default_symbol_theme
+ self._current_theme = self._server_config.default_symbol_theme.value
self._themes = BUILTIN_SYMBOL_THEMES
@property
diff --git a/gns3server/controller/topology.py b/gns3server/controller/topology.py
index a336aee9..417848fd 100644
--- a/gns3server/controller/topology.py
+++ b/gns3server/controller/topology.py
@@ -194,7 +194,7 @@ def load_topology(path):
try:
_check_topology_schema(topo, path)
except ControllerError as e:
- log.error("Can't load the topology %s", path)
+ log.error("Can't load the topology %s, please check using the debug mode...", path)
raise e
if changed:
diff --git a/gns3server/crash_report.py b/gns3server/crash_report.py
index b4d0d9de..b6b202bd 100644
--- a/gns3server/crash_report.py
+++ b/gns3server/crash_report.py
@@ -58,7 +58,7 @@ class CrashReport:
Report crash to a third party service
"""
- DSN = "https://1ae6f3c9d64e75bf8ad39295723da722@o19455.ingest.us.sentry.io/38482"
+ DSN = "https://29d15f2b7fde7fbd860843b7ee24dc7f@o19455.ingest.us.sentry.io/38482"
_instance = None
def __init__(self):
diff --git a/gns3server/db/models/templates.py b/gns3server/db/models/templates.py
index a24c59df..c4364229 100644
--- a/gns3server/db/models/templates.py
+++ b/gns3server/db/models/templates.py
@@ -66,6 +66,7 @@ class DockerTemplate(Template):
template_id = Column(GUID, ForeignKey("templates.template_id", ondelete="CASCADE"), primary_key=True)
image = Column(String)
adapters = Column(Integer)
+ mac_address = Column(String)
start_command = Column(String)
environment = Column(String)
console_type = Column(String)
diff --git a/gns3server/db/tasks.py b/gns3server/db/tasks.py
index 35b31ec1..95880a76 100644
--- a/gns3server/db/tasks.py
+++ b/gns3server/db/tasks.py
@@ -77,7 +77,7 @@ async def connect_to_db(app: FastAPI) -> None:
db_path = os.path.join(Config.instance().config_dir, "gns3_controller.db")
db_url = os.environ.get("GNS3_DATABASE_URI", f"sqlite+aiosqlite:///{db_path}")
- engine = create_async_engine(db_url, connect_args={"check_same_thread": False}, future=True)
+ engine = create_async_engine(db_url, connect_args={"check_same_thread": False, "timeout": 20}, future=True)
alembic_cfg = config.Config()
alembic_cfg.set_main_option("script_location", "gns3server:db_migrations")
#alembic_cfg.set_main_option('sqlalchemy.url', db_url)
diff --git a/gns3server/db_migrations/versions/9a5292aa4389_add_mac_address_field_in_docker_.py b/gns3server/db_migrations/versions/9a5292aa4389_add_mac_address_field_in_docker_.py
new file mode 100644
index 00000000..5fb1597a
--- /dev/null
+++ b/gns3server/db_migrations/versions/9a5292aa4389_add_mac_address_field_in_docker_.py
@@ -0,0 +1,27 @@
+"""add mac_address field in Docker templates table
+
+Revision ID: 9a5292aa4389
+Revises: 7ceeddd9c9a8
+Create Date: 2024-09-18 17:52:53.429522
+
+"""
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision = '9a5292aa4389'
+down_revision = '7ceeddd9c9a8'
+branch_labels = None
+depends_on = None
+
+
+def upgrade() -> None:
+
+ op.add_column('docker_templates', sa.Column('mac_address', sa.String()))
+
+
+def downgrade() -> None:
+
+ op.drop_column('docker_templates', 'mac_address')
+
diff --git a/gns3server/schemas/compute/docker_nodes.py b/gns3server/schemas/compute/docker_nodes.py
index 63bc60af..2d6d7d47 100644
--- a/gns3server/schemas/compute/docker_nodes.py
+++ b/gns3server/schemas/compute/docker_nodes.py
@@ -39,6 +39,7 @@ class DockerBase(BaseModel):
usage: Optional[str] = Field(None, description="How to use the Docker container")
start_command: Optional[str] = Field(None, description="Docker CMD entry")
adapters: Optional[int] = Field(None, ge=0, le=99, description="Number of adapters")
+ mac_address: Optional[str] = Field(None, description="Base MAC address", pattern="^([0-9a-fA-F]{2}[:]){5}([0-9a-fA-F]{2})$")
environment: Optional[str] = Field(None, description="Docker environment variables")
extra_hosts: Optional[str] = Field(None, description="Docker extra hosts (added to /etc/hosts)")
extra_volumes: Optional[List[str]] = Field(None, description="Additional directories to make persistent")
diff --git a/gns3server/schemas/compute/ethernet_switch_nodes.py b/gns3server/schemas/compute/ethernet_switch_nodes.py
index 62c3a66c..ddb4b32c 100644
--- a/gns3server/schemas/compute/ethernet_switch_nodes.py
+++ b/gns3server/schemas/compute/ethernet_switch_nodes.py
@@ -43,7 +43,7 @@ class EthernetSwitchPort(BaseModel):
port_number: int
type: EthernetSwitchPortType = Field(..., description="Port type")
vlan: int = Field(..., ge=1, le=4094, description="VLAN number")
- ethertype: Optional[EthernetSwitchEtherType] = Field("0x8100", description="QinQ Ethertype")
+ ethertype: Optional[EthernetSwitchEtherType] = Field(EthernetSwitchEtherType.ethertype_8021q, description="QinQ Ethertype")
@model_validator(mode="after")
def check_ethertype(self) -> "EthernetSwitchPort":
diff --git a/gns3server/schemas/controller/templates/cloud_templates.py b/gns3server/schemas/controller/templates/cloud_templates.py
index 40695cdd..1754911a 100644
--- a/gns3server/schemas/controller/templates/cloud_templates.py
+++ b/gns3server/schemas/controller/templates/cloud_templates.py
@@ -29,13 +29,13 @@ from typing import Optional, Union, List
class CloudTemplate(TemplateBase):
- category: Optional[Category] = "guest"
+ category: Optional[Category] = Category.guest
default_name_format: Optional[str] = "Cloud{0}"
symbol: Optional[str] = "cloud"
ports_mapping: List[Union[EthernetPort, TAPPort, UDPPort]] = Field(default_factory=list)
remote_console_host: Optional[str] = Field("127.0.0.1", description="Remote console host or IP")
remote_console_port: Optional[int] = Field(23, gt=0, le=65535, description="Remote console TCP port")
- remote_console_type: Optional[CloudConsoleType] = Field("none", description="Remote console type")
+ remote_console_type: Optional[CloudConsoleType] = Field(CloudConsoleType.none, description="Remote console type")
remote_console_http_path: Optional[str] = Field("/", description="Path of the remote web interface")
diff --git a/gns3server/schemas/controller/templates/docker_templates.py b/gns3server/schemas/controller/templates/docker_templates.py
index 1a799236..329de37a 100644
--- a/gns3server/schemas/controller/templates/docker_templates.py
+++ b/gns3server/schemas/controller/templates/docker_templates.py
@@ -24,15 +24,16 @@ from typing import Optional, List
class DockerTemplate(TemplateBase):
- category: Optional[Category] = "guest"
+ category: Optional[Category] = Category.guest
default_name_format: Optional[str] = "{name}-{0}"
symbol: Optional[str] = "docker_guest"
image: str = Field(..., description="Docker image name")
adapters: Optional[int] = Field(1, ge=0, le=100, description="Number of adapters")
+ mac_address: Optional[str] = Field("", description="Base MAC address", pattern="^([0-9a-fA-F]{2}[:]){5}([0-9a-fA-F]{2})$|^$")
start_command: Optional[str] = Field("", description="Docker CMD entry")
environment: Optional[str] = Field("", description="Docker environment variables")
- console_type: Optional[ConsoleType] = Field("telnet", description="Console type")
- aux_type: Optional[AuxType] = Field("none", description="Auxiliary console type")
+ console_type: Optional[ConsoleType] = Field(ConsoleType.telnet, description="Console type")
+ aux_type: Optional[AuxType] = Field(AuxType.none, description="Auxiliary console type")
console_auto_start: Optional[bool] = Field(
False, description="Automatically start the console when the node has started"
)
diff --git a/gns3server/schemas/controller/templates/dynamips_templates.py b/gns3server/schemas/controller/templates/dynamips_templates.py
index 5f0d5773..4fb5235a 100644
--- a/gns3server/schemas/controller/templates/dynamips_templates.py
+++ b/gns3server/schemas/controller/templates/dynamips_templates.py
@@ -32,7 +32,7 @@ from enum import Enum
class DynamipsTemplate(TemplateBase):
- category: Optional[Category] = "router"
+ category: Optional[Category] = Category.router
default_name_format: Optional[str] = "R{0}"
symbol: Optional[str] = "router"
platform: DynamipsPlatform = Field(..., description="Cisco router platform")
@@ -51,11 +51,11 @@ class DynamipsTemplate(TemplateBase):
disk0: Optional[int] = Field(0, description="Disk0 size in MB")
disk1: Optional[int] = Field(0, description="Disk1 size in MB")
auto_delete_disks: Optional[bool] = Field(False, description="Automatically delete nvram and disk files")
- console_type: Optional[DynamipsConsoleType] = Field("telnet", description="Console type")
+ console_type: Optional[DynamipsConsoleType] = Field(DynamipsConsoleType.telnet, description="Console type")
console_auto_start: Optional[bool] = Field(
False, description="Automatically start the console when the node has started"
)
- aux_type: Optional[DynamipsConsoleType] = Field("none", description="Auxiliary console type")
+ aux_type: Optional[DynamipsConsoleType] = Field(DynamipsConsoleType.none, description="Auxiliary console type")
slot0: Optional[DynamipsAdapters] = Field(None, description="Network module slot 0")
slot1: Optional[DynamipsAdapters] = Field(None, description="Network module slot 1")
slot2: Optional[DynamipsAdapters] = Field(None, description="Network module slot 2")
@@ -72,8 +72,8 @@ class C7200DynamipsTemplate(DynamipsTemplate):
ram: Optional[int] = Field(512, description="Amount of RAM in MB")
nvram: Optional[int] = Field(512, description="Amount of NVRAM in KB")
- npe: Optional[DynamipsNPE] = Field("npe-400", description="NPE model")
- midplane: Optional[DynamipsMidplane] = Field("vxr", description="Midplane model")
+ npe: Optional[DynamipsNPE] = Field(DynamipsNPE.npe_400, description="NPE model")
+ midplane: Optional[DynamipsMidplane] = Field(DynamipsMidplane.vxr, description="Midplane model")
sparsemem: Optional[bool] = Field(True, description="Sparse memory feature")
diff --git a/gns3server/schemas/controller/templates/ethernet_hub_templates.py b/gns3server/schemas/controller/templates/ethernet_hub_templates.py
index 263a942c..23b04241 100644
--- a/gns3server/schemas/controller/templates/ethernet_hub_templates.py
+++ b/gns3server/schemas/controller/templates/ethernet_hub_templates.py
@@ -35,7 +35,7 @@ DEFAULT_PORTS = [
class EthernetHubTemplate(TemplateBase):
- category: Optional[Category] = "switch"
+ category: Optional[Category] = Category.switch
default_name_format: Optional[str] = "Hub{0}"
symbol: Optional[str] = "hub"
ports_mapping: Optional[List[EthernetHubPort]] = Field(DEFAULT_PORTS, description="Ports")
diff --git a/gns3server/schemas/controller/templates/ethernet_switch_templates.py b/gns3server/schemas/controller/templates/ethernet_switch_templates.py
index 4831d2a4..f4e83a0a 100644
--- a/gns3server/schemas/controller/templates/ethernet_switch_templates.py
+++ b/gns3server/schemas/controller/templates/ethernet_switch_templates.py
@@ -45,11 +45,11 @@ class ConsoleType(str, Enum):
class EthernetSwitchTemplate(TemplateBase):
- category: Optional[Category] = "switch"
+ category: Optional[Category] = Category.switch
default_name_format: Optional[str] = "Switch{0}"
symbol: Optional[str] = "ethernet_switch"
ports_mapping: Optional[List[EthernetSwitchPort]] = Field(DEFAULT_PORTS, description="Ports")
- console_type: Optional[ConsoleType] = Field("none", description="Console type")
+ console_type: Optional[ConsoleType] = Field(ConsoleType.none, description="Console type")
class EthernetSwitchTemplateUpdate(EthernetSwitchTemplate):
diff --git a/gns3server/schemas/controller/templates/iou_templates.py b/gns3server/schemas/controller/templates/iou_templates.py
index 4f2d3f38..cf581c8c 100644
--- a/gns3server/schemas/controller/templates/iou_templates.py
+++ b/gns3server/schemas/controller/templates/iou_templates.py
@@ -24,7 +24,7 @@ from typing import Optional
class IOUTemplate(TemplateBase):
- category: Optional[Category] = "router"
+ category: Optional[Category] = Category.router
default_name_format: Optional[str] = "IOU{0}"
symbol: Optional[str] = "multilayer_switch"
path: str = Field(..., description="Path of IOU executable")
@@ -36,7 +36,7 @@ class IOUTemplate(TemplateBase):
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)")
- console_type: Optional[ConsoleType] = Field("telnet", description="Console type")
+ console_type: Optional[ConsoleType] = Field(ConsoleType.telnet, description="Console type")
console_auto_start: Optional[bool] = Field(
False, description="Automatically start the console when the node has started"
)
diff --git a/gns3server/schemas/controller/templates/qemu_templates.py b/gns3server/schemas/controller/templates/qemu_templates.py
index 5b73f50d..f5353d7b 100644
--- a/gns3server/schemas/controller/templates/qemu_templates.py
+++ b/gns3server/schemas/controller/templates/qemu_templates.py
@@ -33,17 +33,17 @@ from typing import Optional, List
class QemuTemplate(TemplateBase):
- category: Optional[Category] = "guest"
+ category: Optional[Category] = Category.guest
default_name_format: Optional[str] = "{name}-{0}"
symbol: Optional[str] = "qemu_guest"
qemu_path: Optional[str] = Field("", description="Qemu executable path")
- platform: Optional[QemuPlatform] = Field("x86_64", description="Platform to emulate")
+ platform: Optional[QemuPlatform] = Field(QemuPlatform.x86_64, description="Platform to emulate")
linked_clone: Optional[bool] = Field(True, description="Whether the VM is a linked clone or not")
ram: Optional[int] = Field(256, description="Amount of RAM in MB")
cpus: Optional[int] = Field(1, ge=1, le=255, description="Number of vCPUs")
maxcpus: Optional[int] = Field(1, ge=1, le=255, description="Maximum number of hotpluggable vCPUs")
adapters: Optional[int] = Field(1, ge=0, le=275, description="Number of adapters")
- adapter_type: Optional[QemuAdapterType] = Field("e1000", description="QEMU adapter type")
+ adapter_type: Optional[QemuAdapterType] = Field(QemuAdapterType.e1000, description="QEMU adapter type")
mac_address: Optional[str] = Field(
"", description="QEMU MAC address", pattern="^([0-9a-fA-F]{2}[:]){5}([0-9a-fA-F]{2})$|^$"
)
@@ -55,20 +55,20 @@ class QemuTemplate(TemplateBase):
0,
description="Optional port segment size. A port segment is a block of port. For example Ethernet0/0 Ethernet0/1 is the module 0 with a port segment size of 2",
)
- console_type: Optional[QemuConsoleType] = Field("telnet", description="Console type")
+ console_type: Optional[QemuConsoleType] = Field(QemuConsoleType.telnet, description="Console type")
console_auto_start: Optional[bool] = Field(
False, description="Automatically start the console when the node has started"
)
- aux_type: Optional[QemuConsoleType] = Field("none", description="Auxiliary console type")
- boot_priority: Optional[QemuBootPriority] = Field("c", description="QEMU boot priority")
+ aux_type: Optional[QemuConsoleType] = Field(QemuConsoleType.none, description="Auxiliary console type")
+ boot_priority: Optional[QemuBootPriority] = Field(QemuBootPriority.c, description="QEMU boot priority")
hda_disk_image: Optional[str] = Field("", description="QEMU hda disk image path")
- hda_disk_interface: Optional[QemuDiskInterfaceType] = Field("none", description="QEMU hda interface")
+ hda_disk_interface: Optional[QemuDiskInterfaceType] = Field(QemuDiskInterfaceType.none, description="QEMU hda interface")
hdb_disk_image: Optional[str] = Field("", description="QEMU hdb disk image path")
- hdb_disk_interface: Optional[QemuDiskInterfaceType] = Field("none", description="QEMU hdb interface")
+ hdb_disk_interface: Optional[QemuDiskInterfaceType] = Field(QemuDiskInterfaceType.none, description="QEMU hdb interface")
hdc_disk_image: Optional[str] = Field("", description="QEMU hdc disk image path")
- hdc_disk_interface: Optional[QemuDiskInterfaceType] = Field("none", description="QEMU hdc interface")
+ hdc_disk_interface: Optional[QemuDiskInterfaceType] = Field(QemuDiskInterfaceType.none, description="QEMU hdc interface")
hdd_disk_image: Optional[str] = Field("", description="QEMU hdd disk image path")
- hdd_disk_interface: Optional[QemuDiskInterfaceType] = Field("none", description="QEMU hdd interface")
+ hdd_disk_interface: Optional[QemuDiskInterfaceType] = Field(QemuDiskInterfaceType.none, description="QEMU hdd interface")
cdrom_image: Optional[str] = Field("", description="QEMU cdrom image path")
initrd: Optional[str] = Field("", description="QEMU initrd path")
kernel_image: Optional[str] = Field("", description="QEMU kernel image path")
@@ -82,9 +82,9 @@ class QemuTemplate(TemplateBase):
)
tpm: Optional[bool] = Field(False, description="Enable Trusted Platform Module (TPM)")
uefi: Optional[bool] = Field(False, description="Enable UEFI boot mode")
- on_close: Optional[QemuOnCloseAction] = Field("power_off", description="Action to execute on the VM is closed")
+ on_close: Optional[QemuOnCloseAction] = Field(QemuOnCloseAction.power_off, description="Action to execute on the VM is closed")
cpu_throttling: Optional[int] = Field(0, ge=0, le=800, description="Percentage of CPU allowed for QEMU")
- process_priority: Optional[QemuProcessPriority] = Field("normal", description="Process priority for QEMU")
+ process_priority: Optional[QemuProcessPriority] = Field(QemuProcessPriority.normal, description="Process priority for QEMU")
options: Optional[str] = Field("", description="Additional QEMU options")
custom_adapters: Optional[List[CustomAdapter]] = Field(default_factory=list, description="Custom adapters")
diff --git a/gns3server/schemas/controller/templates/virtualbox_templates.py b/gns3server/schemas/controller/templates/virtualbox_templates.py
index 34fb6a1d..922cae6b 100644
--- a/gns3server/schemas/controller/templates/virtualbox_templates.py
+++ b/gns3server/schemas/controller/templates/virtualbox_templates.py
@@ -28,7 +28,7 @@ from typing import Optional, List
class VirtualBoxTemplate(TemplateBase):
- category: Optional[Category] = "guest"
+ category: Optional[Category] = Category.guest
default_name_format: Optional[str] = "{name}-{0}"
symbol: Optional[str] = "vbox_guest"
vmname: str = Field(..., description="VirtualBox VM name (in VirtualBox itself)")
@@ -38,8 +38,7 @@ class VirtualBoxTemplate(TemplateBase):
1, ge=0, le=36, description="Number of adapters"
) # 36 is the maximum given by the ICH9 chipset in VirtualBox
use_any_adapter: Optional[bool] = Field(False, description="Allow GNS3 to use any VirtualBox adapter")
- adapter_type: Optional[VirtualBoxAdapterType] = Field(
- "Intel PRO/1000 MT Desktop (82540EM)", description="VirtualBox adapter type"
+ adapter_type: Optional[VirtualBoxAdapterType] = Field(VirtualBoxAdapterType.intel_pro_1000_mt_desktop, description="VirtualBox adapter type"
)
first_port_name: Optional[str] = Field("", description="Optional name of the first networking port example: eth0")
port_name_format: Optional[str] = Field(
@@ -51,9 +50,9 @@ class VirtualBoxTemplate(TemplateBase):
)
headless: Optional[bool] = Field(False, description="Headless mode")
on_close: Optional[VirtualBoxOnCloseAction] = Field(
- "power_off", description="Action to execute on the VM is closed"
+ VirtualBoxOnCloseAction.power_off, description="Action to execute on the VM is closed"
)
- console_type: Optional[VirtualBoxConsoleType] = Field("none", description="Console type")
+ console_type: Optional[VirtualBoxConsoleType] = Field(VirtualBoxConsoleType.none, description="Console type")
console_auto_start: Optional[bool] = Field(
False, description="Automatically start the console when the node has started"
)
diff --git a/gns3server/schemas/controller/templates/vmware_templates.py b/gns3server/schemas/controller/templates/vmware_templates.py
index 7109e5d0..555e5189 100644
--- a/gns3server/schemas/controller/templates/vmware_templates.py
+++ b/gns3server/schemas/controller/templates/vmware_templates.py
@@ -29,7 +29,7 @@ from typing import Optional, List
class VMwareTemplate(TemplateBase):
- category: Optional[Category] = "guest"
+ category: Optional[Category] = Category.guest
default_name_format: Optional[str] = "{name}-{0}"
symbol: Optional[str] = "vmware_guest"
vmx_path: str = Field(..., description="Path to the vmx file")
@@ -45,11 +45,11 @@ class VMwareTemplate(TemplateBase):
adapters: Optional[int] = Field(
1, ge=0, le=10, description="Number of adapters"
) # 10 is the maximum adapters support by VMware VMs
- adapter_type: Optional[VMwareAdapterType] = Field("e1000", description="VMware adapter type")
+ adapter_type: Optional[VMwareAdapterType] = Field(VMwareAdapterType.e1000, description="VMware adapter type")
use_any_adapter: Optional[bool] = Field(False, description="Allow GNS3 to use any VMware adapter")
headless: Optional[bool] = Field(False, description="Headless mode")
- on_close: Optional[VMwareOnCloseAction] = Field("power_off", description="Action to execute on the VM is closed")
- console_type: Optional[VMwareConsoleType] = Field("none", description="Console type")
+ on_close: Optional[VMwareOnCloseAction] = Field(VMwareOnCloseAction.power_off, description="Action to execute on the VM is closed")
+ console_type: Optional[VMwareConsoleType] = Field(VMwareConsoleType.none, description="Console type")
console_auto_start: Optional[bool] = Field(
False, description="Automatically start the console when the node has started"
)
diff --git a/gns3server/schemas/controller/templates/vpcs_templates.py b/gns3server/schemas/controller/templates/vpcs_templates.py
index 5ed288c9..79d6f1c3 100644
--- a/gns3server/schemas/controller/templates/vpcs_templates.py
+++ b/gns3server/schemas/controller/templates/vpcs_templates.py
@@ -24,11 +24,11 @@ from typing import Optional
class VPCSTemplate(TemplateBase):
- category: Optional[Category] = "guest"
+ category: Optional[Category] = Category.guest
default_name_format: Optional[str] = "PC{0}"
symbol: Optional[str] = "vpcs_guest"
base_script_file: Optional[str] = Field("vpcs_base_config.txt", description="Script file")
- console_type: Optional[ConsoleType] = Field("telnet", description="Console type")
+ console_type: Optional[ConsoleType] = Field(ConsoleType.telnet, description="Console type")
console_auto_start: Optional[bool] = Field(
False, description="Automatically start the console when the node has started"
)
diff --git a/gns3server/static/web-ui/index.html b/gns3server/static/web-ui/index.html
index 6fb2fa0b..305aefb5 100644
--- a/gns3server/static/web-ui/index.html
+++ b/gns3server/static/web-ui/index.html
@@ -46,6 +46,6 @@
gtag('config', 'G-0BT7QQV1W1');
-
+