mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-08-27 12:30:13 +03:00
Merge remote-tracking branch 'origin/3.0' into gh-pages
This commit is contained in:
commit
0c6cbe8fbc
40
.github/workflows/docker-build.yml
vendored
Normal file
40
.github/workflows/docker-build.yml
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
name: Build and Push Docker Image
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags:
|
||||
- v2.*
|
||||
- v3.*
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: docker.io
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build and push to GitHub Container Registry
|
||||
run: |
|
||||
docker build -t ghcr.io/$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]'):latest .
|
||||
docker push ghcr.io/$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]'):latest
|
||||
|
||||
- name: Build and push to Docker Hub
|
||||
run: |
|
||||
docker build -t gns3/${{ github.event.repository.name }}:latest .
|
||||
docker push gns3/${{ github.event.repository.name }}:latest
|
||||
6
.github/workflows/testing.yml
vendored
6
.github/workflows/testing.yml
vendored
@ -18,11 +18,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
os: ["ubuntu-latest"]
|
||||
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
||||
#include:
|
||||
# only test with Python 3.10 on Windows
|
||||
# - os: windows-latest
|
||||
# python-version: "3.10"
|
||||
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
29
CHANGELOG
29
CHANGELOG
@ -1,5 +1,34 @@
|
||||
# Change Log
|
||||
|
||||
## 3.0.6 28/01/2026
|
||||
|
||||
* Sync appliances
|
||||
* Fix creating Qemu disk image. Fixes #2542
|
||||
* Disable checking for available disk space. Ref #2548
|
||||
* Set default location of udhcpc in "/etc/network/udhcpc". Fixes #2582
|
||||
* Support for Python 3.14
|
||||
* Fix non-ASCII characters in project names
|
||||
* Increase DB engine pool size and max overflow
|
||||
* Enable ip cef in IOU L2 and IOU L3 base configs
|
||||
|
||||
## 2.2.56 21/01/2026
|
||||
|
||||
* Set default location of udhcpc in "/etc/network/udhcpc". Fixes #2582
|
||||
* Upgrade pywin32 to v311
|
||||
|
||||
## 2.2.55 19/11/2025
|
||||
|
||||
* Docker API version requirements and process to handle older daemons
|
||||
* Use docker_name property when creating a container
|
||||
* Set name of container in Docker
|
||||
* Support for Python 3.14
|
||||
* Enable ip cef in IOU L2 and IOU L3 base configs
|
||||
* Only build and push Docker image when releasing a new version
|
||||
* Fix pushing Docker image to Docker hub
|
||||
* Update security issues in README.md. Fixes #2535
|
||||
* Add missing 'nat' template type in schema. Fixes #2529
|
||||
* Resolve deprecation warnings of regex library
|
||||
|
||||
## 3.0.5 14/05/2025
|
||||
|
||||
* Bundle web-ui v3.0.5
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
pytest==8.3.4
|
||||
flake8==7.1.1
|
||||
pytest-timeout==2.3.1
|
||||
pytest-asyncio==0.25.2
|
||||
requests==2.32.3
|
||||
pytest==8.4.2 # version 8.4.2 is the last one supporting Python 3.9
|
||||
flake8==7.3.0
|
||||
pytest-timeout==2.4.0
|
||||
pytest-asyncio==0.26.0 # upgrading leads to stuck / failed tests
|
||||
requests==2.32.5
|
||||
httpx==0.28.1
|
||||
httpx_ws==0.7.1
|
||||
httpx_ws==0.7.1 # upgrading leads to failures in tests
|
||||
@ -96,7 +96,13 @@ async def create_qemu_image(
|
||||
await Qemu.instance().create_disk_image(disk_image_path, options)
|
||||
|
||||
image_info = await read_image_info(disk_image_path, "qemu")
|
||||
return await images_repo.add_image(**image_info)
|
||||
|
||||
image = await images_repo.get_image(disk_image_path)
|
||||
if image:
|
||||
# the image has already been added to the database
|
||||
return image
|
||||
else:
|
||||
return await images_repo.add_image(**image_info)
|
||||
|
||||
@router.get(
|
||||
"",
|
||||
|
||||
@ -330,6 +330,9 @@ async def export_project(
|
||||
Required privilege: Project.Audit
|
||||
"""
|
||||
|
||||
if project.is_running():
|
||||
raise ControllerError("Project must be stopped in order to export it")
|
||||
|
||||
compression_query = compression.lower()
|
||||
if compression_query == "zip":
|
||||
compression = zipfile.ZIP_DEFLATED
|
||||
@ -380,7 +383,13 @@ async def export_project(
|
||||
except (ValueError, OSError, RuntimeError) as e:
|
||||
raise ConnectionError(f"Cannot export project: {e}")
|
||||
|
||||
headers = {"CONTENT-DISPOSITION": f'attachment; filename="{project.name}.gns3project"'}
|
||||
fallback = project.name.encode("ascii", "ignore").decode() or "project"
|
||||
encoded = urllib.parse.quote(project.name, safe="")
|
||||
headers = {
|
||||
"Content-Disposition": (
|
||||
f'attachment; filename="{fallback}.gns3project"; filename*=UTF-8\'\'{encoded}.gns3project'
|
||||
)
|
||||
}
|
||||
return StreamingResponse(streamer(), media_type="application/gns3project", headers=headers)
|
||||
|
||||
|
||||
|
||||
33
gns3server/appliances/asterfusion-vAsterNOS-VPP.gns3a
Normal file
33
gns3server/appliances/asterfusion-vAsterNOS-VPP.gns3a
Normal file
@ -0,0 +1,33 @@
|
||||
{
|
||||
"appliance_id": "4aa78f74-c769-43e0-9ef1-5dd2cf1909c6",
|
||||
"name": "Asterfusion AsterNOS-VPP",
|
||||
"category": "router",
|
||||
"description": "AsterNOS-VPP leverages SONiC’s robust routing features and management capability with VPP’s high-performance data forwarding. It supports L3 routing, NAT, PPPoE, and VPN services. Minimum requirements: 4 vCPUs and 4GB RAM.",
|
||||
"vendor_name": "Asterfusion",
|
||||
"vendor_url": "https://cloudswit.ch/product/sonic-enterprise-distribution/#vAsterNOS",
|
||||
"documentation_url": "https://docs.asternos.com/routing",
|
||||
"product_name": "AsterNOS-VPP",
|
||||
"registry_version": 6,
|
||||
"status": "stable",
|
||||
"maintainer": "Asterfusion Product Team",
|
||||
"maintainer_email": "bd@cloudswit.ch",
|
||||
"images": [
|
||||
{
|
||||
"filename": "AsterNOS-VPP_V6.1-R0101P02_x86.img.gz",
|
||||
"version": "V6.1",
|
||||
"md5sum": "55834c3e8849ab226144d79ef76acf81",
|
||||
"filesize": 1318201015,
|
||||
"download_url": "https://asternos.dev/api/file/b2c9fd83-a9d3-46f3-9676-6b49dd481d6c",
|
||||
"comments": "Registration is required to download the image."
|
||||
}
|
||||
],
|
||||
"qemu": {
|
||||
"adapter_type": "virtio-net-pci",
|
||||
"adapters": 4,
|
||||
"ram": 4096,
|
||||
"cpus": 4,
|
||||
"arch": "x86_64",
|
||||
"console_type": "telnet",
|
||||
"kvm": "require"
|
||||
}
|
||||
}
|
||||
@ -6,7 +6,7 @@
|
||||
"vendor_name": "Asterfusion",
|
||||
"vendor_url": "https://cloudswit.ch/",
|
||||
"vendor_logo_url": "https://raw.githubusercontent.com/GNS3/gns3-registry/master/vendor-logos/asterfusion.png",
|
||||
"documentation_url": "https://help.cloudswit.ch/portal/en/kb/articles/vasternos",
|
||||
"documentation_url": "https://asternos.com/community/space/vasternos/post/vasternos-for-data-center-network",
|
||||
"product_name": "vAsterNOS",
|
||||
"product_url": "https://cloudswit.ch/product/sonic-enterprise-distribution",
|
||||
"registry_version": 4,
|
||||
@ -30,18 +30,18 @@
|
||||
},
|
||||
"images": [
|
||||
{
|
||||
"filename": "vAsterNOS-V3.1.img",
|
||||
"filename": "AsterNOS_V3.1_R0408P01-VS.img.gz",
|
||||
"version": "V3.1",
|
||||
"md5sum": "c323c9c3f60e1a93eca2acdc5034b85c",
|
||||
"filesize": 2724659200,
|
||||
"download_url": "https://drive.cloudswitch.io/external/8ae2e3932ad8bb2ec30dd25be415d288ff3e4a949c557c6bd48ac6e6265bcfc1"
|
||||
"md5sum": "5c182306af67be2c79a5642551412e48",
|
||||
"filesize": 1425945924,
|
||||
"download_url": "http://pub.asternos.com/AsterNOS_V3.1_R0408P01-VS.img.gz"
|
||||
}
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
"name": "V3.1",
|
||||
"images": {
|
||||
"hda_disk_image": "vAsterNOS-V3.1.img"
|
||||
"hda_disk_image": "AsterNOS_V3.1_R0408P01-VS.img.gz"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@ -50,6 +50,12 @@
|
||||
"version": "124-25G",
|
||||
"md5sum": "9c7cc9b3f3b3571411a7f62faaa2c036",
|
||||
"filesize": 71528984
|
||||
},
|
||||
{
|
||||
"filename": "c7200-itpk9-mz.124-15.SW.image",
|
||||
"version": "124-15.SW",
|
||||
"md5sum": "3334a0facdbd26164e83d3c201fff5b5",
|
||||
"filesize": 46423952
|
||||
}
|
||||
],
|
||||
"versions": [
|
||||
@ -87,6 +93,13 @@
|
||||
"images": {
|
||||
"image": "c7200-a3jk9s-mz.124-25g.image"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "124-15.SW",
|
||||
"idlepc": "0x60b2bc68",
|
||||
"images": {
|
||||
"image": "c7200-itpk9-mz.124-15.SW.image"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -28,6 +28,13 @@
|
||||
"options": "-nographic"
|
||||
},
|
||||
"images": [
|
||||
{
|
||||
"filename": "FEGNS3.9.3.0.0.qcow2",
|
||||
"version": "v9.3.0.0",
|
||||
"md5sum": "2abfa8d64219aa75522238f87b782726",
|
||||
"filesize": 490209280,
|
||||
"direct_download_url": "https://akamai-ep.extremenetworks.com/Extreme_P/github-en/Virtual_VOSS/FEGNS3.9.3.0.0.qcow2"
|
||||
},
|
||||
{
|
||||
"filename": "VOSSGNS3.8.10.1.0.qcow2",
|
||||
"version": "v8.10.1.0",
|
||||
@ -93,6 +100,13 @@
|
||||
}
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
"name": "v9.3.0.0",
|
||||
"images":
|
||||
{
|
||||
"hda_disk_image": "FEGNS3.9.3.0.0.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "v8.10.1.0",
|
||||
"images":
|
||||
|
||||
@ -25,19 +25,19 @@
|
||||
},
|
||||
"images": [
|
||||
{
|
||||
"filename": "rtr.qcow2",
|
||||
"version": "20.7.1",
|
||||
"md5sum": "4707415d15d20ced92423d90e52eedcd",
|
||||
"filesize": 254767616,
|
||||
"download_url": "http://freerouter.nop.hu/",
|
||||
"direct_download_url": "http://dl.nop.hu/rtr.qcow2"
|
||||
"filename": "rtr-x86_64.qcow2",
|
||||
"version": "25.8.29",
|
||||
"md5sum": "13d72fdfd772258808a9d4b75fa67083",
|
||||
"filesize": 73269248,
|
||||
"download_url": "http://www.freertr.org/",
|
||||
"direct_download_url": "http://dl.nop.hu/rtr-x86_64.qcow2"
|
||||
}
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
"name": "20.7.1",
|
||||
"name": "25.8.29",
|
||||
"images": {
|
||||
"hda_disk_image": "rtr.qcow2"
|
||||
"hda_disk_image": "rtr-x86_64.qcow2"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@ -2,13 +2,13 @@
|
||||
"appliance_id": "4d351078-c6f5-444c-ab30-0ef20e3d8c53",
|
||||
"name": "Infix",
|
||||
"category": "router",
|
||||
"description": "Infix is a Network Operating System based on Linux. It can be set up both as a switch, with offloading using switchdev, and a router with firewalling.",
|
||||
"description": "Infix OS is a free Linux-based operating system with made-easy management using NETCONF, RESTCONF, or the built-in command line interface (CLI). It is suitable for both end-devices with a single Ethernet port and switches/routers with multiple ports. Immutable by design, extensions are possible using Docker containers.",
|
||||
"vendor_name": "KernelKit",
|
||||
"vendor_url": "https://github.com/kernelkit",
|
||||
"vendor_logo_url": "https://kernelkit.org/assets/img/jack.png",
|
||||
"product_name": "Infix",
|
||||
"registry_version": 4,
|
||||
"documentation_url": "https://github.com/kernelkit/infix/tree/main/doc",
|
||||
"documentation_url": "https://kernelkit.org/infix/latest/",
|
||||
"status": "stable",
|
||||
"availability": "free",
|
||||
"maintainer": "KernelKit",
|
||||
@ -76,9 +76,79 @@
|
||||
"md5sum": "84bd999513325d0007d0e6587abc6140",
|
||||
"version": "25.04.0",
|
||||
"direct_download_url": "https://github.com/kernelkit/infix/releases/download/v25.04.0/infix-x86_64-disk-25.04.0.qcow2"
|
||||
},
|
||||
{
|
||||
"filename": "infix-x86_64-disk-25.05.0.qcow2",
|
||||
"filesize": 259958784,
|
||||
"md5sum": "a6b1b3a6bccc60bb5214f2b864602fde",
|
||||
"version": "25.05.0",
|
||||
"direct_download_url": "https://github.com/kernelkit/infix/releases/download/v25.05.0/infix-x86_64-disk-25.05.0.qcow2"
|
||||
},
|
||||
{
|
||||
"filename": "infix-x86_64-disk-25.08.0.qcow2",
|
||||
"filesize": 284453888,
|
||||
"md5sum": "5fbf146d2ac73d1bd8fa1c509d6169b1",
|
||||
"version": "25.08.0",
|
||||
"direct_download_url": "https://github.com/kernelkit/infix/releases/download/v25.08.0/infix-x86_64-disk-25.08.0.qcow2"
|
||||
},
|
||||
{
|
||||
"filename": "infix-x86_64-disk-25.09.0.qcow2",
|
||||
"filesize": 285120000,
|
||||
"md5sum": "9761968406780a5b28311b8527f8629f",
|
||||
"version": "25.09.0",
|
||||
"direct_download_url": "https://github.com/kernelkit/infix/releases/download/v25.09.0/infix-x86_64-disk-25.09.0.qcow2"
|
||||
},
|
||||
{
|
||||
"filename": "infix-x86_64-disk-25.10.0.qcow2",
|
||||
"filesize": 288818688,
|
||||
"md5sum": "781d0fce9fbf789f6ea6ff10dc104afb",
|
||||
"version": "25.10.0",
|
||||
"direct_download_url": "https://github.com/kernelkit/infix/releases/download/v25.10.0/infix-x86_64-disk-25.10.0.qcow2"
|
||||
},
|
||||
{
|
||||
"filename": "infix-x86_64-disk-25.11.0.qcow2",
|
||||
"filesize": 288930816,
|
||||
"md5sum": "e2eb1a4fff9d56815f37c2e82a6279fb",
|
||||
"version": "25.11.0",
|
||||
"direct_download_url": "https://github.com/kernelkit/infix/releases/download/v25.11.0/infix-x86_64-disk-25.11.0.qcow2"
|
||||
}
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
"name": "25.11.0",
|
||||
"images": {
|
||||
"bios_image": "OVMF-edk2-stable202305.fd",
|
||||
"hda_disk_image": "infix-x86_64-disk-25.11.0.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "25.10.0",
|
||||
"images": {
|
||||
"bios_image": "OVMF-edk2-stable202305.fd",
|
||||
"hda_disk_image": "infix-x86_64-disk-25.10.0.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "25.09.0",
|
||||
"images": {
|
||||
"bios_image": "OVMF-edk2-stable202305.fd",
|
||||
"hda_disk_image": "infix-x86_64-disk-25.09.0.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "25.08.0",
|
||||
"images": {
|
||||
"bios_image": "OVMF-edk2-stable202305.fd",
|
||||
"hda_disk_image": "infix-x86_64-disk-25.08.0.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "25.05.0",
|
||||
"images": {
|
||||
"bios_image": "OVMF-edk2-stable202305.fd",
|
||||
"hda_disk_image": "infix-x86_64-disk-25.05.0.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "25.04.0",
|
||||
"images": {
|
||||
@ -115,4 +185,4 @@
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -11,237 +11,34 @@
|
||||
"status": "stable",
|
||||
"maintainer": "GNS3 Team",
|
||||
"maintainer_email": "developers@gns3.net",
|
||||
"usage": "Default password is toor\nEnable persistence by selecting boot option 'Live USB Persistence'",
|
||||
"usage": "Default username is kali and the default password is kali",
|
||||
"port_name_format": "eth{0}",
|
||||
"qemu": {
|
||||
"adapter_type": "e1000",
|
||||
"adapters": 8,
|
||||
"ram": 1024,
|
||||
"ram": 2048,
|
||||
"cpus": 2,
|
||||
"hda_disk_interface": "ide",
|
||||
"arch": "x86_64",
|
||||
"console_type": "vnc",
|
||||
"boot_priority": "d",
|
||||
"kvm": "require"
|
||||
},
|
||||
"images": [
|
||||
{
|
||||
"filename": "kali-linux-2021.1-live-amd64.iso",
|
||||
"version": "2021.1",
|
||||
"md5sum": "3a3716fef866e5c29a1c1ccfc94264b5",
|
||||
"filesize": 3591385088,
|
||||
"download_url": "http://cdimage.kali.org/kali-2021.1/",
|
||||
"direct_download_url": "http://cdimage.kali.org/kali-2021.1/kali-linux-2021.1-live-amd64.iso"
|
||||
},
|
||||
{
|
||||
"filename": "kali-linux-2019.3-amd64.iso",
|
||||
"version": "2019.3",
|
||||
"md5sum": "9c6fb00558f78ed06992d89f745ef975",
|
||||
"filesize": 3037736960,
|
||||
"download_url": "http://old.kali.org/kali-images/kali-2019.3",
|
||||
"direct_download_url": "http://old.kali.org/kali-images/kali-2019.3/kali-linux-2019.3-amd64.iso"
|
||||
},
|
||||
{
|
||||
"filename": "kali-linux-2019.2-amd64.iso",
|
||||
"version": "2019.2",
|
||||
"md5sum": "0f89b6225d7ea9c18682f7cc541c1179",
|
||||
"filesize": 3353227264,
|
||||
"download_url": "http://old.kali.org/kali-images/kali-2019.2",
|
||||
"direct_download_url": "http://old.kali.org/kali-images/kali-2019.2/kali-linux-2019.2-amd64.iso"
|
||||
},
|
||||
{
|
||||
"filename": "kali-linux-mate-2019.2-amd64.iso",
|
||||
"version": "2019.2 (MATE)",
|
||||
"md5sum": "fec8dd7009f932c51a74323df965a709",
|
||||
"filesize": 3313217536,
|
||||
"download_url": "http://old.kali.org/kali-images/kali-2019.2",
|
||||
"direct_download_url": "http://old.kali.org/kali-images/kali-2019.2/kali-linux-mate-2019.2-amd64.iso"
|
||||
},
|
||||
{
|
||||
"filename": "kali-linux-2019.1a-amd64.iso",
|
||||
"version": "2019.1a",
|
||||
"md5sum": "58c6111ed0be1919ea87267e7e65ab0f",
|
||||
"filesize": 3483873280,
|
||||
"download_url": "http://old.kali.org/kali-images/kali-2019.1a",
|
||||
"direct_download_url": "http://old.kali.org/kali-images/kali-2019.1a/kali-linux-2019.1a-amd64.iso"
|
||||
},
|
||||
{
|
||||
"filename": "kali-linux-2018.4-amd64.iso",
|
||||
"version": "2018.4",
|
||||
"md5sum": "1b2d598bb8d2003e6207c119c0ba42fe",
|
||||
"filesize": 3139436544,
|
||||
"download_url": "http://old.kali.org/kali-images/kali-2018.4",
|
||||
"direct_download_url": "http://old.kali.org/kali-images/kali-2018.4/kali-linux-2018.4-amd64.iso"
|
||||
},
|
||||
{
|
||||
"filename": "kali-linux-2018.3a-amd64.iso",
|
||||
"version": "2018.3a",
|
||||
"md5sum": "2da675d016bd690c05e180e33aa98b94",
|
||||
"filesize": 3192651776,
|
||||
"download_url": "http://old.kali.org/kali-images/kali-2018.3a",
|
||||
"direct_download_url": "http://old.kali.org/kali-images/kali-2018.3a/kali-linux-2018.3a-amd64.iso"
|
||||
},
|
||||
{
|
||||
"filename": "kali-linux-2018.1-amd64.iso",
|
||||
"version": "2018.1",
|
||||
"md5sum": "a3feb90df5b71b3c7f4a02bdddf221d7",
|
||||
"filesize": 3028500480,
|
||||
"download_url": "http://old.kali.org/kali-images/kali-2018.1",
|
||||
"direct_download_url": "http://old.kali.org/kali-images/kali-2018.1/kali-linux-2018.1-amd64.iso"
|
||||
},
|
||||
{
|
||||
"filename": "kali-linux-2017.3-amd64.iso",
|
||||
"version": "2017.3",
|
||||
"md5sum": "b465580c897e94675ac1daf031fa66b9",
|
||||
"filesize": 2886402048,
|
||||
"download_url": "http://old.kali.org/kali-images/kali-2017.3/",
|
||||
"direct_download_url": "http://old.kali.org/kali-images/kali-2017.3/kali-linux-2017.3-amd64.iso"
|
||||
},
|
||||
{
|
||||
"filename": "kali-linux-2017.2-amd64.iso",
|
||||
"version": "2017.2",
|
||||
"md5sum": "541654f8f818450dc0db866a0a0f6eec",
|
||||
"filesize": 3020619776,
|
||||
"download_url": "http://old.kali.org/kali-images/kali-2017.2/",
|
||||
"direct_download_url": "http://old.kali.org/kali-images/kali-2017.2/kali-linux-2017.2-amd64.iso"
|
||||
},
|
||||
{
|
||||
"filename": "kali-linux-2017.1-amd64.iso",
|
||||
"version": "2017.1",
|
||||
"md5sum": "c8e742283929d7a12dbe7c58e398ff08",
|
||||
"filesize": 2794307584,
|
||||
"download_url": "http://old.kali.org/kali-images/kali-2017.1/",
|
||||
"direct_download_url": "http://old.kali.org/kali-images/kali-2017.1/kali-linux-2017.1-amd64.iso"
|
||||
},
|
||||
{
|
||||
"filename": "kali-linux-2016.2-amd64.iso",
|
||||
"version": "2016.2",
|
||||
"md5sum": "3d163746bc5148e61ad689d94bc263f9",
|
||||
"filesize": 3076767744,
|
||||
"download_url": "http://old.kali.org/kali-images/kali-2016.2/",
|
||||
"direct_download_url": "http://old.kali.org/kali-images/kali-2016.2/kali-linux-2016.2-amd64.iso"
|
||||
},
|
||||
{
|
||||
"filename": "kali-linux-2016.1-amd64.iso",
|
||||
"version": "2016.1",
|
||||
"md5sum": "2e1230dc14036935b3279dfe3e49ad39",
|
||||
"filesize": 2945482752,
|
||||
"download_url": "http://old.kali.org/kali-images/kali-2016.1/",
|
||||
"direct_download_url": "http://old.kali.org/kali-images/kali-2016.1/kali-linux-2016.1-amd64.iso"
|
||||
},
|
||||
{
|
||||
"filename": "kali-linux-2.0-amd64.iso",
|
||||
"version": "2.0",
|
||||
"md5sum": "ef192433017c5d99a156eaef51fd389d",
|
||||
"filesize": 3320512512,
|
||||
"download_url": "https://www.offensive-security.com/kali-linux-vmware-arm-image-download/",
|
||||
"direct_download_url": "http://images.kali.org/Kali-Linux-2.0.0-vm-amd64.7z"
|
||||
},
|
||||
{
|
||||
"filename": "kali-linux-persistence-1gb.qcow2",
|
||||
"version": "1.0",
|
||||
"md5sum": "14e9c92f3ba5a0bd1128c1ea26a129ea",
|
||||
"filesize": 34734080,
|
||||
"download_url": "https://sourceforge.net/projects/gns-3/files/Qemu%20Appliances/",
|
||||
"direct_download_url": "https://sourceforge.net/projects/gns-3/files/Qemu%20Appliances/kali-linux-persistence-1gb.qcow2/download"
|
||||
{
|
||||
"filename": "kali-linux-2025.4-qemu-amd64.qcow2",
|
||||
"version": "2025.4",
|
||||
"md5sum": "a4be43351b5deb5b2823cb38a73b2095",
|
||||
"filesize": 15719268352,
|
||||
"download_url": "https://cdimage.kali.org/kali-2025.4/",
|
||||
"direct_download_url": "https://cdimage.kali.org/kali-2025.4/kali-linux-2025.4-qemu-amd64.7z",
|
||||
"compression": "7z"
|
||||
}
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
"name": "2021.1",
|
||||
"name": "2025.4",
|
||||
"images": {
|
||||
"hda_disk_image": "kali-linux-persistence-1gb.qcow2",
|
||||
"cdrom_image": "kali-linux-2021.1-live-amd64.iso"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "2019.3",
|
||||
"images": {
|
||||
"hda_disk_image": "kali-linux-persistence-1gb.qcow2",
|
||||
"cdrom_image": "kali-linux-2019.3-amd64.iso"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "2019.2",
|
||||
"images": {
|
||||
"hda_disk_image": "kali-linux-persistence-1gb.qcow2",
|
||||
"cdrom_image": "kali-linux-2019.2-amd64.iso"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "2019.2 (MATE)",
|
||||
"images": {
|
||||
"hda_disk_image": "kali-linux-persistence-1gb.qcow2",
|
||||
"cdrom_image": "kali-linux-mate-2019.2-amd64.iso"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "2019.1a",
|
||||
"images": {
|
||||
"hda_disk_image": "kali-linux-persistence-1gb.qcow2",
|
||||
"cdrom_image": "kali-linux-2019.1a-amd64.iso"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "2018.4",
|
||||
"images": {
|
||||
"hda_disk_image": "kali-linux-persistence-1gb.qcow2",
|
||||
"cdrom_image": "kali-linux-2018.4-amd64.iso"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "2018.3a",
|
||||
"images": {
|
||||
"hda_disk_image": "kali-linux-persistence-1gb.qcow2",
|
||||
"cdrom_image": "kali-linux-2018.3a-amd64.iso"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "2018.1",
|
||||
"images": {
|
||||
"hda_disk_image": "kali-linux-persistence-1gb.qcow2",
|
||||
"cdrom_image": "kali-linux-2018.1-amd64.iso"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "2017.3",
|
||||
"images": {
|
||||
"hda_disk_image": "kali-linux-persistence-1gb.qcow2",
|
||||
"cdrom_image": "kali-linux-2017.3-amd64.iso"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "2017.2",
|
||||
"images": {
|
||||
"hda_disk_image": "kali-linux-persistence-1gb.qcow2",
|
||||
"cdrom_image": "kali-linux-2017.2-amd64.iso"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "2017.1",
|
||||
"images": {
|
||||
"hda_disk_image": "kali-linux-persistence-1gb.qcow2",
|
||||
"cdrom_image": "kali-linux-2017.1-amd64.iso"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "2016.2",
|
||||
"images": {
|
||||
"hda_disk_image": "kali-linux-persistence-1gb.qcow2",
|
||||
"cdrom_image": "kali-linux-2016.2-amd64.iso"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "2016.1",
|
||||
"images": {
|
||||
"hda_disk_image": "kali-linux-persistence-1gb.qcow2",
|
||||
"cdrom_image": "kali-linux-2016.1-amd64.iso"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "2.0",
|
||||
"images": {
|
||||
"hda_disk_image": "kali-linux-persistence-1gb.qcow2",
|
||||
"cdrom_image": "kali-linux-2.0-amd64.iso"
|
||||
"hda_disk_image": "kali-linux-2025.4-qemu-amd64.qcow2"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
22
gns3server/appliances/mikrotik-winbox-2026.gns3a
Normal file
22
gns3server/appliances/mikrotik-winbox-2026.gns3a
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"appliance_id": "1174e46d-4a29-4554-a1e8-71ed90682295",
|
||||
"name": "MikroTik WinBox 2026",
|
||||
"category": "guest",
|
||||
"description": "Mikrotik's WinBox router management software for GNS3 2026",
|
||||
"vendor_name": "MikroTik",
|
||||
"vendor_url": "https://mikrotik.com",
|
||||
"documentation_url": "https://mikrotik.com/winbox",
|
||||
"product_name": "MikroTik WinBox",
|
||||
"product_url": "https://mikrotik.com/winbox",
|
||||
"registry_version": 4,
|
||||
"status": "stable",
|
||||
"availability": "free",
|
||||
"maintainer": "h3ck13r",
|
||||
"maintainer_email": "pisdushko1886@gmail.com",
|
||||
"usage": "Connect WinBox to a MikroTik CHR port and start them both. Open WinBox console and wait until MikroTik appear in neighbors tab, provide credentials and connect.",
|
||||
"docker": {
|
||||
"adapters": 1,
|
||||
"image": "snatao808/winbox-mikrotik-2026",
|
||||
"console_type": "vnc"
|
||||
}
|
||||
}
|
||||
@ -23,6 +23,15 @@
|
||||
"kvm": "allow"
|
||||
},
|
||||
"images": [
|
||||
{
|
||||
"filename": "openwrt-24.10.2-x86-64-generic-ext4-combined.img",
|
||||
"version": "24.10.2",
|
||||
"md5sum": "0769916238524161b0a9a639ae261381",
|
||||
"filesize": 126353408,
|
||||
"download_url": "https://downloads.openwrt.org/releases/24.10.2/targets/x86/64/",
|
||||
"direct_download_url": "https://downloads.openwrt.org/releases/24.10.2/targets/x86/64/openwrt-24.10.2-x86-64-generic-ext4-combined.img.gz",
|
||||
"compression": "gzip"
|
||||
},
|
||||
{
|
||||
"filename": "openwrt-23.05.0-x86-64-generic-ext4-combined.img",
|
||||
"version": "23.05.0",
|
||||
@ -223,7 +232,13 @@
|
||||
}
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
{
|
||||
"name": "24.10.2",
|
||||
"images": {
|
||||
"hda_disk_image": "openwrt-24.10.2-x86-64-generic-ext4-combined.img"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "23.05.0",
|
||||
"images": {
|
||||
"hda_disk_image": "openwrt-23.05.0-x86-64-generic-ext4-combined.img"
|
||||
|
||||
@ -25,6 +25,20 @@
|
||||
"kvm": "require"
|
||||
},
|
||||
"images": [
|
||||
{
|
||||
"filename": "OPNsense-25.7-nano-amd64.img",
|
||||
"version": "25.7",
|
||||
"md5sum": "07aa8b44ebc57fa9ca0145f777901a59",
|
||||
"filesize": 3221225472,
|
||||
"download_url": "https://opnsense.c0urier.net/releases/25.7/"
|
||||
},
|
||||
{
|
||||
"filename": "OPNsense-25.1-nano-amd64.img",
|
||||
"version": "25.1",
|
||||
"md5sum": "9c119e4fed7dd7c1c162d8be4088d983",
|
||||
"filesize": 3221225472,
|
||||
"download_url": "https://opnsense.c0urier.net/releases/25.1/"
|
||||
},
|
||||
{
|
||||
"filename": "OPNsense-24.7-nano-amd64.img",
|
||||
"version": "24.7",
|
||||
|
||||
@ -30,6 +30,27 @@
|
||||
"on_close": "shutdown_signal"
|
||||
},
|
||||
"images": [
|
||||
{
|
||||
"filename": "vyos-1.4.4-kvm-amd64.qcow2",
|
||||
"version": "1.4.4",
|
||||
"md5sum": "942dae45d63924b58b65d069890765d4",
|
||||
"filesize": 518782976,
|
||||
"download_url": "https://support.vyos.io/"
|
||||
},
|
||||
{
|
||||
"filename": "vyos-1.4.3-kvm-amd64.qcow2",
|
||||
"version": "1.4.3",
|
||||
"md5sum": "4eec0ba2ec367bbbdf2d2f212f947746",
|
||||
"filesize": 515047424,
|
||||
"download_url": "https://support.vyos.io/"
|
||||
},
|
||||
{
|
||||
"filename": "vyos-1.4.2-kvm-amd64.qcow2",
|
||||
"version": "1.4.2",
|
||||
"md5sum": "78c5935e05a7bbd94a639fcee18f5799",
|
||||
"filesize": 514064384,
|
||||
"download_url": "https://support.vyos.io/"
|
||||
},
|
||||
{
|
||||
"filename": "vyos-1.4.1-kvm-amd64.qcow2",
|
||||
"version": "1.4.1",
|
||||
@ -116,6 +137,24 @@
|
||||
}
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
"name": "1.4.4",
|
||||
"images": {
|
||||
"hda_disk_image": "vyos-1.4.4-kvm-amd64.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "1.4.3",
|
||||
"images": {
|
||||
"hda_disk_image": "vyos-1.4.3-kvm-amd64.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "1.4.2",
|
||||
"images": {
|
||||
"hda_disk_image": "vyos-1.4.2-kvm-amd64.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "1.4.1",
|
||||
"images": {
|
||||
|
||||
@ -24,6 +24,7 @@ import socket
|
||||
import shutil
|
||||
import re
|
||||
import logging
|
||||
import inspect
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@ -196,7 +197,7 @@ class BaseManager:
|
||||
node_id = str(uuid4())
|
||||
|
||||
node = self._NODE_CLASS(name, node_id, project, self, *args, **kwargs)
|
||||
if asyncio.iscoroutinefunction(node.create):
|
||||
if inspect.iscoroutinefunction(node.create):
|
||||
await node.create()
|
||||
else:
|
||||
node.create()
|
||||
@ -245,7 +246,7 @@ class BaseManager:
|
||||
"""
|
||||
|
||||
node = self.get_node(node_id)
|
||||
if asyncio.iscoroutinefunction(node.close):
|
||||
if inspect.iscoroutinefunction(node.close):
|
||||
await node.close()
|
||||
else:
|
||||
node.close()
|
||||
|
||||
@ -38,9 +38,9 @@ log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# Be careful to keep it consistent
|
||||
DOCKER_MINIMUM_API_VERSION = "1.25"
|
||||
DOCKER_MINIMUM_VERSION = "1.13"
|
||||
DOCKER_PREFERRED_API_VERSION = "1.30"
|
||||
DOCKER_MINIMUM_API_VERSION = "1.40"
|
||||
DOCKER_MINIMUM_VERSION = "19.03.8"
|
||||
DOCKER_PREFERRED_API_VERSION = "1.44"
|
||||
CHUNK_SIZE = 1024 * 8 # 8KB
|
||||
|
||||
|
||||
@ -125,22 +125,28 @@ class Docker(BaseManager):
|
||||
if not self._connected:
|
||||
try:
|
||||
self._connected = True
|
||||
version = await self.query("GET", "version")
|
||||
docker_info = await self.query("GET", "version")
|
||||
except (aiohttp.ClientError, FileNotFoundError):
|
||||
self._connected = False
|
||||
raise DockerError("Can't connect to docker daemon")
|
||||
raise DockerError("Can't connect to Docker daemon")
|
||||
|
||||
docker_version = parse_version(version["ApiVersion"])
|
||||
api_version = parse_version(docker_info['ApiVersion'])
|
||||
version = docker_info["Version"]
|
||||
|
||||
if docker_version < parse_version(DOCKER_MINIMUM_API_VERSION):
|
||||
raise DockerError(
|
||||
f"Docker version is {version['Version']}. "
|
||||
f"GNS3 requires a minimum version of {DOCKER_MINIMUM_VERSION}"
|
||||
if api_version < parse_version(DOCKER_MINIMUM_API_VERSION):
|
||||
raise DockerError(f"Docker version is {version}. "
|
||||
f"GNS3 requires a minimum version of {DOCKER_MINIMUM_VERSION}"
|
||||
)
|
||||
|
||||
preferred_api_version = parse_version(DOCKER_PREFERRED_API_VERSION)
|
||||
if docker_version >= preferred_api_version:
|
||||
if api_version >= preferred_api_version:
|
||||
self._api_version = DOCKER_PREFERRED_API_VERSION
|
||||
else:
|
||||
# use the Min API version supported by the daemon
|
||||
self._api_version = docker_info['MinAPIVersion']
|
||||
log.warning("Using Docker client with the minimum API version {}".format(self._api_version))
|
||||
|
||||
log.info("Connected to Docker daemon version {} using API version {}".format(version, self._api_version))
|
||||
|
||||
def connector(self):
|
||||
|
||||
@ -188,7 +194,7 @@ class Docker(BaseManager):
|
||||
|
||||
:param method: HTTP method
|
||||
:param path: Endpoint in API
|
||||
:param data: Dictionnary with the body. Will be transformed to a JSON
|
||||
:param data: Dictionary with the body. Will be transformed to a JSON
|
||||
:param params: Parameters added as a query arg
|
||||
:param timeout: Timeout
|
||||
:returns: HTTP response
|
||||
@ -199,12 +205,11 @@ class Docker(BaseManager):
|
||||
timeout = 60 * 60 * 24 * 31 # One month timeout
|
||||
|
||||
if path == 'version':
|
||||
url = "http://docker/v1.24/" + path
|
||||
url = "http://docker/" + path
|
||||
else:
|
||||
url = "http://docker/v" + DOCKER_MINIMUM_API_VERSION + "/" + path
|
||||
await self._check_connection() # version is use by check connection
|
||||
url = "http://docker/v" + self._api_version + "/" + path
|
||||
try:
|
||||
if path != "version": # version is use by check connection
|
||||
await self._check_connection()
|
||||
if self._session is None or self._session.closed:
|
||||
connector = self.connector()
|
||||
self._session = aiohttp.ClientSession(connector=connector)
|
||||
|
||||
@ -195,6 +195,14 @@ class DockerVM(BaseNode):
|
||||
def ethernet_adapters(self):
|
||||
return self._ethernet_adapters
|
||||
|
||||
@property
|
||||
def docker_name(self):
|
||||
"""
|
||||
Container name in Docker
|
||||
"""
|
||||
|
||||
return "GNS3.{}.{}".format(self.name, self._project.id)
|
||||
|
||||
@property
|
||||
def mac_address(self):
|
||||
"""
|
||||
@ -539,7 +547,8 @@ class DockerVM(BaseNode):
|
||||
if extra_hosts:
|
||||
params["Env"].append(f"GNS3_EXTRA_HOSTS={extra_hosts}")
|
||||
|
||||
result = await self.manager.query("POST", "containers/create", data=params)
|
||||
# Support name in Doker: [a-zA-Z0-9][a-zA-Z0-9_.-]
|
||||
result = await self.manager.query("POST", f"containers/create?name={self.docker_name}", data=params)
|
||||
self._cid = result["Id"]
|
||||
log.info(f"Docker container '{self._name}' [{self._id}] created")
|
||||
if self._cpus > 0:
|
||||
@ -745,6 +754,7 @@ class DockerVM(BaseNode):
|
||||
"-rfbport", str(self.console),
|
||||
"-AlwaysShared",
|
||||
"-SecurityTypes", "None",
|
||||
"-desktop", self.name,
|
||||
":{}".format(self._display),
|
||||
stdout=fd, stderr=subprocess.STDOUT)
|
||||
|
||||
|
||||
@ -3,7 +3,12 @@
|
||||
# script for udhcpc
|
||||
# Copyright (c) 2008 Natanael Copa <natanael.copa@gmail.com>
|
||||
|
||||
UDHCPC="/gns3/etc/udhcpc"
|
||||
if [ -d /etc/network ]; then
|
||||
UDHCPC="/etc/network/udhcpc"
|
||||
else
|
||||
UDHCPC="/gns3/etc/udhcpc"
|
||||
fi
|
||||
|
||||
UDHCPC_CONF="$UDHCPC/udhcpc.conf"
|
||||
|
||||
RESOLV_CONF="/etc/resolv.conf"
|
||||
|
||||
@ -106,7 +106,7 @@ class ProjectManager:
|
||||
if project_id is not None and project_id in self._projects:
|
||||
return self._projects[project_id]
|
||||
project = Project(name=name, project_id=project_id, path=path, variables=variables)
|
||||
self._check_available_disk_space(project)
|
||||
#self._check_available_disk_space(project) # FIXME: disabled for now
|
||||
self._projects[project.id] = project
|
||||
return project
|
||||
|
||||
|
||||
@ -13,8 +13,7 @@ logging console discriminator EXCESS
|
||||
!
|
||||
no ip icmp rate-limit unreachable
|
||||
!
|
||||
! due to some bugs with IOU, try to change the following line to 'ip cef' if your routing does not work
|
||||
no ip cef
|
||||
ip cef
|
||||
no ip domain lookup
|
||||
!
|
||||
!
|
||||
|
||||
@ -12,8 +12,7 @@ no ip icmp rate-limit unreachable
|
||||
!
|
||||
!
|
||||
!
|
||||
! due to some bugs with IOU, try to change the following line to 'ip cef' if your routing does not work
|
||||
no ip cef
|
||||
ip cef
|
||||
no ip domain lookup
|
||||
!
|
||||
!
|
||||
|
||||
@ -58,7 +58,7 @@ class CrashReport:
|
||||
Report crash to a third party service
|
||||
"""
|
||||
|
||||
DSN = "https://61bb46252cabeebd49ee1e09fb8ba72e@o19455.ingest.us.sentry.io/38482"
|
||||
DSN = "https://1c0ae79fd51b77c674718c6942337300@o19455.ingest.us.sentry.io/38482"
|
||||
_instance = None
|
||||
|
||||
def __init__(self):
|
||||
|
||||
@ -78,7 +78,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, "timeout": 20}, future=True)
|
||||
engine = create_async_engine(db_url, connect_args={"check_same_thread": False, "timeout": 20}, future=True, pool_size=512, max_overflow=1024)
|
||||
alembic_cfg = config.Config()
|
||||
alembic_cfg.set_main_option("script_location", "gns3server:db_migrations")
|
||||
#alembic_cfg.set_main_option('sqlalchemy.url', db_url)
|
||||
|
||||
@ -20,6 +20,7 @@ import asyncio
|
||||
import sys
|
||||
import os
|
||||
import threading
|
||||
import inspect
|
||||
|
||||
|
||||
async def wait_run_in_executor(func, *args, **kwargs):
|
||||
@ -90,9 +91,6 @@ async def wait_for_process_termination(process, timeout=10):
|
||||
In theory this can be implemented by just:
|
||||
await asyncio.wait_for(self._iou_process.wait(), timeout=100)
|
||||
|
||||
But it's broken before Python 3.4:
|
||||
http://bugs.python.org/issue23140
|
||||
|
||||
:param process: An asyncio subprocess
|
||||
:param timeout: Timeout in seconds
|
||||
"""
|
||||
@ -106,7 +104,7 @@ async def wait_for_process_termination(process, timeout=10):
|
||||
async def _check_process(process, termination_callback):
|
||||
if not hasattr(sys, "_called_from_test") or not sys._called_from_test:
|
||||
returncode = await process.wait()
|
||||
if asyncio.iscoroutinefunction(termination_callback):
|
||||
if inspect.iscoroutinefunction(termination_callback):
|
||||
await termination_callback(returncode)
|
||||
else:
|
||||
termination_callback(returncode)
|
||||
|
||||
@ -60,7 +60,14 @@ class FileWatcher:
|
||||
self._hashed[path] = zlib.adler32(open(path, "rb").read())
|
||||
except OSError:
|
||||
self._hashed[path] = None
|
||||
asyncio.get_event_loop().call_later(self._delay, self._check_config_file_change)
|
||||
|
||||
try:
|
||||
loop = asyncio.get_running_loop()
|
||||
except RuntimeError:
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
|
||||
loop.call_later(self._delay, self._check_config_file_change)
|
||||
|
||||
def __del__(self):
|
||||
self._closed = True
|
||||
|
||||
@ -22,8 +22,8 @@
|
||||
# or negative for a release candidate or beta (after the base version
|
||||
# number has been incremented)
|
||||
|
||||
__version__ = "3.0.5"
|
||||
__version_info__ = (3, 0, 5, 0)
|
||||
__version__ = "3.0.6"
|
||||
__version_info__ = (3, 0, 6, 0)
|
||||
|
||||
if "dev" in __version__:
|
||||
try:
|
||||
|
||||
@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
|
||||
[project]
|
||||
name = "gns3-server"
|
||||
description = "GNS3 graphical interface for the GNS3 server."
|
||||
license = {file = "LICENSE"}
|
||||
license-files = ["LICENSE"]
|
||||
authors = [
|
||||
{ name = "Jeremy Grossmann", email = "developers@gns3.com" }
|
||||
]
|
||||
|
||||
@ -1,26 +1,24 @@
|
||||
uvicorn==0.34.2 # uvicorn 0.33 is the last version supporting Python 3.8
|
||||
pydantic==2.11.4
|
||||
fastapi==0.115.12
|
||||
python-multipart==0.0.20
|
||||
websockets==15.0.1
|
||||
aiohttp>=3.11.16,<3.12
|
||||
async-timeout==5.0.1; python_version < '3.11'
|
||||
aiofiles>=24.1.0,<25.0
|
||||
uvicorn==0.39.0 # version 0.39.0 is the last version supporting Python 3.9
|
||||
pydantic==2.12.5
|
||||
fastapi==0.128.0
|
||||
python-multipart==0.0.20 # version 0.0.20 is the last to support Python 3.9
|
||||
websockets==15.0.1 # version 15.0.1 is the last to support Python 3.9
|
||||
aiohttp>=3.13.3,<3.14
|
||||
aiofiles>=25.1.0,<26.0
|
||||
Jinja2>=3.1.6,<3.2
|
||||
sentry-sdk>=2.26.1,<2.27 # optional dependency
|
||||
psutil>=7.0.0
|
||||
async-timeout>=5.0.1,<5.1
|
||||
sentry-sdk>=2.50.0,<3 # optional dependency
|
||||
psutil>=7.2.1
|
||||
async-timeout>=5.0.1,<5.1; python_version < '3.11' # this library has effectively been upstreamed into Python 3.11+
|
||||
distro>=1.9.0
|
||||
py-cpuinfo>=9.0.0,<10.0
|
||||
greenlet==3.2.0 # necessary to run sqlalchemy on Python 3.13
|
||||
sqlalchemy==2.0.40
|
||||
greenlet==3.3.1; python_version >= '3.13' # necessary to run sqlalchemy on Python >= 3.13
|
||||
sqlalchemy==2.0.43
|
||||
aiosqlite==0.21.0
|
||||
alembic==1.15.2
|
||||
bcrypt==4.3.0
|
||||
joserfc==1.0.4
|
||||
email-validator==2.2.0
|
||||
bcrypt==5.0.0
|
||||
joserfc==1.6.1
|
||||
email-validator==2.3.0
|
||||
watchdog==6.0.0
|
||||
zstandard==0.23.0
|
||||
zstandard==0.25.0
|
||||
platformdirs>=2.4.0,<3 # platformdirs >=3 conflicts when building Debian packages
|
||||
importlib-resources>=1.3; python_version <= '3.9'
|
||||
truststore>=0.10.1; python_version >= '3.10'
|
||||
truststore>=0.10.4; python_version >= '3.10'
|
||||
|
||||
@ -1,238 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (C) 2016 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
This script connect to the local GNS3 server and will create a random topology
|
||||
"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
import math
|
||||
import aiohttp
|
||||
import aiohttp.web
|
||||
import asyncio
|
||||
import random
|
||||
|
||||
import coloredlogs
|
||||
import logging
|
||||
|
||||
coloredlogs.install(fmt=" %(asctime)s %(levelname)s %(message)s")
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
PROJECT_ID = "9e26e37d-4962-4921-8c0e-136d3b04ba9c"
|
||||
HOST = "192.168.84.151:3080"
|
||||
|
||||
# Use for node names uniqueness
|
||||
node_i = 1
|
||||
|
||||
|
||||
def die(*args):
|
||||
log.error(*args)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
class HTTPError(Exception):
|
||||
|
||||
def __init__(self, method, path, response):
|
||||
self._method = method
|
||||
self._path = path
|
||||
self._response = response
|
||||
|
||||
@property
|
||||
def response(self):
|
||||
return self._response
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
return self._path
|
||||
|
||||
@property
|
||||
def method(self):
|
||||
return self._method
|
||||
|
||||
|
||||
class HTTPConflict(HTTPError):
|
||||
pass
|
||||
|
||||
|
||||
class HTTPNotFound(HTTPError):
|
||||
pass
|
||||
|
||||
|
||||
async def query(method, path, body=None, **kwargs):
|
||||
global session
|
||||
|
||||
if body:
|
||||
kwargs["data"] = json.dumps(body)
|
||||
|
||||
async with session.request(method, "http://" + HOST + "/v2" + path, **kwargs) as response:
|
||||
if response.status == 409:
|
||||
raise HTTPConflict(method, path, response)
|
||||
elif response.status == 404:
|
||||
raise HTTPNotFound(method, path, response)
|
||||
elif response.status >= 300:
|
||||
raise HTTPError(method, path, response)
|
||||
log.info("%s %s %d", method, path, response.status)
|
||||
if response.headers["content-type"] == "application/json":
|
||||
return await response.json()
|
||||
else:
|
||||
return "{}"
|
||||
|
||||
|
||||
async def post(path, **kwargs):
|
||||
return await query("POST", path, **kwargs)
|
||||
|
||||
|
||||
async def get(path, **kwargs):
|
||||
return await query("GET", path, **kwargs)
|
||||
|
||||
|
||||
async def delete(path, **kwargs):
|
||||
return await query("DELETE", path, **kwargs)
|
||||
|
||||
|
||||
async def create_project():
|
||||
# Delete project if already exists
|
||||
response = await get("/projects")
|
||||
project_exists = False
|
||||
for project in response:
|
||||
if project["name"] == "random" and project["project_id"] != PROJECT_ID:
|
||||
await delete("/projects/" + project["project_id"])
|
||||
elif project["project_id"] == PROJECT_ID:
|
||||
project_exists = True
|
||||
tasks = []
|
||||
for node in await get("/projects/" + PROJECT_ID + "/nodes"):
|
||||
tasks.append(delete_node(project, node))
|
||||
await asyncio.gather(*tasks)
|
||||
if project_exists:
|
||||
response = await post("/projects/" + PROJECT_ID + "/open")
|
||||
else:
|
||||
response = await post("/projects", body={"name": "random", "project_id": PROJECT_ID, "auto_close": False})
|
||||
return response
|
||||
|
||||
|
||||
async def create_node(project):
|
||||
global node_i
|
||||
|
||||
r = random.randint(0, 1)
|
||||
|
||||
if r == 0:
|
||||
node_type = "ethernet_switch"
|
||||
symbol = ":/symbols/ethernet_switch.svg"
|
||||
elif r == 1:
|
||||
node_type = "vpcs"
|
||||
symbol = ":/symbols/vpcs_guest.svg"
|
||||
response = await post("/projects/{}/nodes".format(project["project_id"]), body={
|
||||
"node_type": node_type,
|
||||
"compute_id": "local",
|
||||
"symbol": symbol,
|
||||
"name": "Node{}".format(node_i),
|
||||
"x": (math.floor((node_i - 1) % 12.0) * 100) - 500,
|
||||
"y": (math.ceil((node_i) / 12.0) * 100) - 300
|
||||
})
|
||||
node_i += 1
|
||||
return response
|
||||
|
||||
|
||||
async def delete_node(project, node):
|
||||
await delete("/projects/{}/nodes/{}".format(project["project_id"], node["node_id"]))
|
||||
|
||||
|
||||
async def create_link(project, nodes):
|
||||
"""
|
||||
Create all possible link of a node
|
||||
"""
|
||||
node1 = random.choice(list(nodes.values()))
|
||||
|
||||
for port in range(0, 8):
|
||||
node2 = random.choice(list(nodes.values()))
|
||||
|
||||
if node1 == node2:
|
||||
continue
|
||||
|
||||
data = {"nodes":
|
||||
[
|
||||
{
|
||||
"adapter_number": 0,
|
||||
"node_id": node1["node_id"],
|
||||
"port_number": port
|
||||
},
|
||||
{
|
||||
"adapter_number": 0,
|
||||
"node_id": node2["node_id"],
|
||||
"port_number": port
|
||||
}
|
||||
]
|
||||
}
|
||||
try:
|
||||
await post("/projects/{}/links".format(project["project_id"]), body=data)
|
||||
except (HTTPConflict, HTTPNotFound):
|
||||
pass
|
||||
|
||||
|
||||
async def build_topology():
|
||||
global node_i
|
||||
|
||||
nodes = {}
|
||||
project = await create_project()
|
||||
while True:
|
||||
rand = random.randint(0, 1000)
|
||||
if rand < 500: # chance to create a new node
|
||||
if len(nodes.keys()) < 255: # Limit of VPCS:
|
||||
node = await create_node(project)
|
||||
nodes[node["node_id"]] = node
|
||||
elif rand < 600: # start all nodes
|
||||
await post("/projects/{}/nodes/start".format(project["project_id"]))
|
||||
elif rand < 700: # stop all nodes
|
||||
await post("/projects/{}/nodes/stop".format(project["project_id"]))
|
||||
elif rand < 950: # create a link
|
||||
if len(nodes.keys()) >= 2:
|
||||
await create_link(project, nodes)
|
||||
elif rand < 999: # chance to delete a node
|
||||
continue
|
||||
if len(nodes.keys()) > 0:
|
||||
node = random.choice(list(nodes.values()))
|
||||
await delete_node(project, node)
|
||||
del nodes[node["node_id"]]
|
||||
elif len(nodes.keys()) > 0: # % chance to delete all nodes
|
||||
continue
|
||||
node_i = 1
|
||||
tasks = []
|
||||
for node in nodes.values():
|
||||
tasks.append(delete_node(project, node))
|
||||
await asyncio.gather(*tasks)
|
||||
nodes = {}
|
||||
await asyncio.sleep(0.2)
|
||||
|
||||
async def main(loop):
|
||||
global session
|
||||
async with aiohttp.ClientSession() as session:
|
||||
try:
|
||||
await build_topology()
|
||||
except HTTPError as error:
|
||||
try:
|
||||
j = await error.response.json()
|
||||
die("%s %s invalid status %d:\n%s", error.method, error.path, error.response.status, json.dumps(j, indent=4))
|
||||
except (ValueError, aiohttp.ServerDisconnectedError):
|
||||
die("%s %s invalid status %d", error.method, error.path, error.response.status)
|
||||
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.run_until_complete(main(loop))
|
||||
|
||||
if session:
|
||||
session.close()
|
||||
@ -303,7 +303,7 @@ class TestControllerProjectRoutes:
|
||||
params={"include_images": "yes"})
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
assert response.headers['CONTENT-TYPE'] == 'application/gns3project'
|
||||
assert response.headers['CONTENT-DISPOSITION'] == 'attachment; filename="{}.gns3project"'.format(project.name)
|
||||
assert response.headers['CONTENT-DISPOSITION'] == 'attachment; filename="{name}.gns3project"; filename*=UTF-8\'\'{name}.gns3project'.format(name=project.name)
|
||||
|
||||
with open(str(tmpdir / 'project.zip'), 'wb+') as f:
|
||||
f.write(response.content)
|
||||
@ -346,8 +346,7 @@ class TestControllerProjectRoutes:
|
||||
params={"include_images": "0"})
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
assert response.headers['CONTENT-TYPE'] == 'application/gns3project'
|
||||
assert response.headers['CONTENT-DISPOSITION'] == 'attachment; filename="{}.gns3project"'.format(project.name)
|
||||
|
||||
assert response.headers['CONTENT-DISPOSITION'] == 'attachment; filename="{name}.gns3project"; filename*=UTF-8\'\'{name}.gns3project'.format(name=project.name)
|
||||
with open(str(tmpdir / 'project.zip'), 'wb+') as f:
|
||||
f.write(response.content)
|
||||
|
||||
@ -412,8 +411,7 @@ class TestControllerProjectRoutes:
|
||||
|
||||
if response.status_code == status.HTTP_200_OK:
|
||||
assert response.headers['CONTENT-TYPE'] == 'application/gns3project'
|
||||
assert response.headers['CONTENT-DISPOSITION'] == 'attachment; filename="{}.gns3project"'.format(project.name)
|
||||
|
||||
assert response.headers['CONTENT-DISPOSITION'] == 'attachment; filename="{name}.gns3project"; filename*=UTF-8\'\'{name}.gns3project'.format(name=project.name)
|
||||
with open(str(tmpdir / 'project.zip'), 'wb+') as f:
|
||||
f.write(response.content)
|
||||
|
||||
|
||||
@ -183,7 +183,7 @@ class TestAuthTokens:
|
||||
jwt_secret = config.settings.Controller.jwt_secret_key
|
||||
token = auth_service.create_access_token(test_user.username)
|
||||
with pytest.raises(ValueError):
|
||||
jwt.decode(token, jwt_secret, algorithms=["ES256"])
|
||||
jwt.decode(token, jwt_secret, algorithms=["HS256"])
|
||||
|
||||
async def test_can_retrieve_username_from_token(
|
||||
self,
|
||||
|
||||
@ -49,7 +49,7 @@ async def test_query_success(vm):
|
||||
vm._session.request = AsyncioMagicMock(return_value=response)
|
||||
data = await vm.query("POST", "test", data={"a": True}, params={"b": 1})
|
||||
vm._session.request.assert_called_with('POST',
|
||||
'http://docker/v1.25/test',
|
||||
'http://docker/v{}/test'.format(DOCKER_MINIMUM_API_VERSION),
|
||||
data='{"a": true}',
|
||||
headers={'content-type': 'application/json'},
|
||||
params={'b': 1},
|
||||
@ -72,7 +72,7 @@ async def test_query_error(vm):
|
||||
with pytest.raises(DockerError):
|
||||
await vm.query("POST", "test", data={"a": True}, params={"b": 1})
|
||||
vm._session.request.assert_called_with('POST',
|
||||
'http://docker/v1.25/test',
|
||||
'http://docker/v{}/test'.format(DOCKER_MINIMUM_API_VERSION),
|
||||
data='{"a": true}',
|
||||
headers={'content-type': 'application/json'},
|
||||
params={'b': 1},
|
||||
@ -93,7 +93,7 @@ async def test_query_error_json(vm):
|
||||
with pytest.raises(DockerError):
|
||||
await vm.query("POST", "test", data={"a": True}, params={"b": 1})
|
||||
vm._session.request.assert_called_with('POST',
|
||||
'http://docker/v1.25/test',
|
||||
'http://docker/v{}/test'.format(DOCKER_MINIMUM_API_VERSION),
|
||||
data='{"a": true}',
|
||||
headers={'content-type': 'application/json'},
|
||||
params={'b': 1},
|
||||
@ -188,7 +188,9 @@ async def test_docker_check_connection_docker_minimum_version(vm):
|
||||
async def test_docker_check_connection_docker_preferred_version_against_newer(vm):
|
||||
|
||||
response = {
|
||||
'ApiVersion': '1.31'
|
||||
'ApiVersion': '1.52',
|
||||
'Version': '29.0.1',
|
||||
|
||||
}
|
||||
|
||||
with patch("gns3server.compute.docker.Docker.connector"), \
|
||||
@ -202,7 +204,9 @@ async def test_docker_check_connection_docker_preferred_version_against_newer(vm
|
||||
async def test_docker_check_connection_docker_preferred_version_against_older(vm):
|
||||
|
||||
response = {
|
||||
'ApiVersion': '1.27',
|
||||
'ApiVersion': '1.43',
|
||||
'Version': '24.0.2',
|
||||
'MinAPIVersion': '1.40'
|
||||
}
|
||||
|
||||
with patch("gns3server.compute.docker.Docker.connector"), \
|
||||
@ -212,6 +216,21 @@ async def test_docker_check_connection_docker_preferred_version_against_older(vm
|
||||
assert vm._api_version == DOCKER_MINIMUM_API_VERSION
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_docker_check_connection_docker_unsupported_version(vm):
|
||||
|
||||
response = {
|
||||
'ApiVersion': '1.25',
|
||||
'Version': '1.13.1',
|
||||
}
|
||||
|
||||
with patch("gns3server.compute.docker.Docker.connector"), \
|
||||
asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response):
|
||||
vm._connected = False
|
||||
with pytest.raises(DockerError) as e:
|
||||
await vm._check_connection()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_install_busybox():
|
||||
|
||||
|
||||
@ -99,7 +99,7 @@ async def test_create(compute_project, manager):
|
||||
with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock:
|
||||
vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest")
|
||||
await vm.create()
|
||||
mock.assert_called_with("POST", "containers/create", data={
|
||||
mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={
|
||||
"Tty": True,
|
||||
"OpenStdin": True,
|
||||
"StdinOnce": False,
|
||||
@ -150,7 +150,7 @@ async def test_create_with_tag(compute_project, manager):
|
||||
with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock:
|
||||
vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:16.04")
|
||||
await vm.create()
|
||||
mock.assert_called_with("POST", "containers/create", data={
|
||||
mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={
|
||||
"Tty": True,
|
||||
"OpenStdin": True,
|
||||
"StdinOnce": False,
|
||||
@ -204,7 +204,7 @@ async def test_create_vnc(compute_project, manager):
|
||||
vm._start_vnc = MagicMock()
|
||||
vm._display = 42
|
||||
await vm.create()
|
||||
mock.assert_called_with("POST", "containers/create", data={
|
||||
mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={
|
||||
"Tty": True,
|
||||
"OpenStdin": True,
|
||||
"StdinOnce": False,
|
||||
@ -356,7 +356,7 @@ async def test_create_start_cmd(compute_project, manager):
|
||||
vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest")
|
||||
vm._start_command = "/bin/ls"
|
||||
await vm.create()
|
||||
mock.assert_called_with("POST", "containers/create", data={
|
||||
mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={
|
||||
"Tty": True,
|
||||
"OpenStdin": True,
|
||||
"StdinOnce": False,
|
||||
@ -469,7 +469,7 @@ async def test_create_image_not_available(compute_project, manager):
|
||||
with asyncio_patch("gns3server.compute.docker.DockerVM.pull_image", return_value=True) as mock_pull:
|
||||
with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock:
|
||||
await vm.create()
|
||||
mock.assert_called_with("POST", "containers/create", data={
|
||||
mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={
|
||||
"Tty": True,
|
||||
"OpenStdin": True,
|
||||
"StdinOnce": False,
|
||||
@ -524,7 +524,7 @@ async def test_create_with_user(compute_project, manager):
|
||||
with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock:
|
||||
vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest")
|
||||
await vm.create()
|
||||
mock.assert_called_with("POST", "containers/create", data={
|
||||
mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={
|
||||
"Tty": True,
|
||||
"OpenStdin": True,
|
||||
"StdinOnce": False,
|
||||
@ -624,7 +624,7 @@ async def test_create_with_extra_volumes_duplicate_1_image(compute_project, mana
|
||||
with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock:
|
||||
vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest", extra_volumes=["/vol/1"])
|
||||
await vm.create()
|
||||
mock.assert_called_with("POST", "containers/create", data={
|
||||
mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={
|
||||
"Tty": True,
|
||||
"OpenStdin": True,
|
||||
"StdinOnce": False,
|
||||
@ -680,7 +680,7 @@ async def test_create_with_extra_volumes_duplicate_2_user(compute_project, manag
|
||||
with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock:
|
||||
vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest", extra_volumes=["/vol/1", "/vol/1"])
|
||||
await vm.create()
|
||||
mock.assert_called_with("POST", "containers/create", data={
|
||||
mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={
|
||||
"Tty": True,
|
||||
"OpenStdin": True,
|
||||
"StdinOnce": False,
|
||||
@ -736,7 +736,7 @@ async def test_create_with_extra_volumes_duplicate_3_subdir(compute_project, man
|
||||
with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock:
|
||||
vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest", extra_volumes=["/vol/1/", "/vol"])
|
||||
await vm.create()
|
||||
mock.assert_called_with("POST", "containers/create", data={
|
||||
mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={
|
||||
"Tty": True,
|
||||
"OpenStdin": True,
|
||||
"StdinOnce": False,
|
||||
@ -792,7 +792,7 @@ async def test_create_with_extra_volumes_duplicate_4_backslash(compute_project,
|
||||
with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock:
|
||||
vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest", extra_volumes=["/vol//", "/vol"])
|
||||
await vm.create()
|
||||
mock.assert_called_with("POST", "containers/create", data={
|
||||
mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={
|
||||
"Tty": True,
|
||||
"OpenStdin": True,
|
||||
"StdinOnce": False,
|
||||
@ -848,7 +848,7 @@ async def test_create_with_extra_volumes_duplicate_5_subdir_issue_1595(compute_p
|
||||
with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock:
|
||||
vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest", extra_volumes=["/etc"])
|
||||
await vm.create()
|
||||
mock.assert_called_with("POST", "containers/create", data={
|
||||
mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={
|
||||
"Tty": True,
|
||||
"OpenStdin": True,
|
||||
"StdinOnce": False,
|
||||
@ -899,7 +899,7 @@ async def test_create_with_extra_volumes_duplicate_6_subdir_issue_1595(compute_p
|
||||
with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock:
|
||||
vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest", extra_volumes=["/etc/test", "/etc"])
|
||||
await vm.create()
|
||||
mock.assert_called_with("POST", "containers/create", data={
|
||||
mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={
|
||||
"Tty": True,
|
||||
"OpenStdin": True,
|
||||
"StdinOnce": False,
|
||||
@ -956,7 +956,7 @@ async def test_create_with_extra_volumes(compute_project, manager):
|
||||
with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock:
|
||||
vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest", extra_volumes=["/vol/2"])
|
||||
await vm.create()
|
||||
mock.assert_called_with("POST", "containers/create", data={
|
||||
mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={
|
||||
"Tty": True,
|
||||
"OpenStdin": True,
|
||||
"StdinOnce": False,
|
||||
@ -1243,7 +1243,7 @@ async def test_update(vm):
|
||||
await vm.update()
|
||||
|
||||
mock_query.assert_any_call("DELETE", "containers/e90e34656842", params={"force": 1, "v": 1})
|
||||
mock_query.assert_any_call("POST", "containers/create", data={
|
||||
mock_query.assert_any_call("POST", "containers/create?name={}".format(vm.docker_name), data={
|
||||
"Tty": True,
|
||||
"OpenStdin": True,
|
||||
"StdinOnce": False,
|
||||
@ -1325,7 +1325,7 @@ async def test_update_running(vm):
|
||||
await vm.update()
|
||||
|
||||
mock_query.assert_any_call("DELETE", "containers/e90e34656842", params={"force": 1, "v": 1})
|
||||
mock_query.assert_any_call("POST", "containers/create", data={
|
||||
mock_query.assert_any_call("POST", "containers/create?name={}".format(vm.docker_name), data={
|
||||
"Tty": True,
|
||||
"OpenStdin": True,
|
||||
"StdinOnce": False,
|
||||
@ -1685,7 +1685,7 @@ async def test_start_vnc(vm):
|
||||
with asyncio_patch("asyncio.create_subprocess_exec") as mock_exec:
|
||||
await vm._start_vnc()
|
||||
assert vm._display is not None
|
||||
assert mock_exec.call_args[0] == ("/bin/Xtigervnc", "-extension", "MIT-SHM", "-geometry", vm.console_resolution, "-depth", "16", "-interface", "127.0.0.1", "-rfbport", str(vm.console), "-AlwaysShared", "-SecurityTypes", "None", ":{}".format(vm._display))
|
||||
assert mock_exec.call_args[0] == ("/bin/Xtigervnc", "-extension", "MIT-SHM", "-geometry", vm.console_resolution, "-depth", "16", "-interface", "127.0.0.1", "-rfbport", str(vm.console), "-AlwaysShared", "-SecurityTypes", "None", "-desktop", "test", ":{}".format(vm._display))
|
||||
mock_wait.assert_called_with("/tmp/.X11-unix/X{}".format(vm._display))
|
||||
|
||||
|
||||
@ -1791,7 +1791,7 @@ async def test_cpus(compute_project, manager):
|
||||
with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock:
|
||||
vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest", cpus=0.5)
|
||||
await vm.create()
|
||||
mock.assert_called_with("POST", "containers/create", data={
|
||||
mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={
|
||||
"Tty": True,
|
||||
"OpenStdin": True,
|
||||
"StdinOnce": False,
|
||||
@ -1842,7 +1842,7 @@ async def test_memory(compute_project, manager):
|
||||
with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock:
|
||||
vm = DockerVM("test", str(uuid.uuid4()), compute_project, manager, "ubuntu:latest", memory=32)
|
||||
await vm.create()
|
||||
mock.assert_called_with("POST", "containers/create", data={
|
||||
mock.assert_called_with("POST", "containers/create?name={}".format(vm.docker_name), data={
|
||||
"Tty": True,
|
||||
"OpenStdin": True,
|
||||
"StdinOnce": False,
|
||||
|
||||
@ -18,11 +18,9 @@
|
||||
|
||||
import asyncio
|
||||
import pytest
|
||||
import sys
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from gns3server.utils.asyncio import wait_run_in_executor, subprocess_check_output, wait_for_process_termination, locking
|
||||
from tests.utils import AsyncioMagicMock
|
||||
from gns3server.utils.asyncio import wait_run_in_executor, subprocess_check_output, locking
|
||||
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
-r requirements.txt
|
||||
|
||||
pywin32==306
|
||||
pywin32==311
|
||||
wmi==1.5.1
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user