mirror of
https://github.com/GNS3/gns3-server
synced 2024-11-25 09:48:09 +00:00
Merge remote-tracking branch 'origin/3.0' into gh-pages
This commit is contained in:
commit
7bcdcef928
6
.github/workflows/testing.yml
vendored
6
.github/workflows/testing.yml
vendored
@ -17,12 +17,12 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.7", "3.8", "3.9", "3.10"]
|
||||
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v2
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Display Python version
|
||||
|
41
CHANGELOG
41
CHANGELOG
@ -1,5 +1,46 @@
|
||||
# Change Log
|
||||
|
||||
## 3.0.0a3 27/12/2022
|
||||
|
||||
* Add web-ui v3.0.0a3
|
||||
* Add config option to change the server name. Ref #2149
|
||||
* Option to disable image discovery and do not scan parent directory
|
||||
* Allow raw images by default. Fixes https://github.com/GNS3/gns3-server/issues/2097
|
||||
* Fix bug when creating Dynamips router with chassis setting
|
||||
* Stricter checks to create/update an Ethernet switch and add tests
|
||||
* Fix schema for removing WICs from Cisco routers. Fixes #3392
|
||||
* Fix issues with VMnet interface on macOS >= 11.0. Ref #3381
|
||||
* Use importlib_resources instead of pkg_resources and install built-in appliances in config dir.
|
||||
* Fix console vnc don't use configured ports in some case. Fixes #2111
|
||||
* Add missing VMware settings in gns3_server.conf
|
||||
* Make version PEP 440 compliant
|
||||
* Support for Python 3.11
|
||||
* Allow for more dependency versions at patch level
|
||||
* Replace deprecated distro.linux_distribution() call
|
||||
* Update gns3.service.systemd
|
||||
* Fix some issues with HTTP notification streams
|
||||
* gns3.service.openrc: make openrc script posix compliant
|
||||
* fix: use exact match to find interface in windows to avoid get wrong interface
|
||||
|
||||
## 2.2.35.1 10/11/2022
|
||||
|
||||
* Re-release Web-Ui v2.2.35
|
||||
|
||||
## 2.2.35 08/11/2022
|
||||
|
||||
* Release web-ui v2.2.35
|
||||
* Fix issues with VMnet interface on macOS >= 11.0. Ref #3381
|
||||
* Use importlib_resources instead of pkg_resources and install built-in appliances in config dir.
|
||||
* Fix console vnc don't use configured ports in some case. Fixes #2111
|
||||
* Add missing VMware settings in gns3_server.conf
|
||||
* Make version PEP 440 compliant
|
||||
* Support for Python 3.11
|
||||
* Allow for more dependency versions at patch level
|
||||
* Replace deprecated distro.linux_distribution() call
|
||||
* Update gns3.service.systemd
|
||||
* gns3.service.openrc: make openrc script posix compliant
|
||||
* fix: use exact match to find interface in windows to avoid get wrong interface
|
||||
|
||||
## 3.0.0a2 06/09/2022
|
||||
|
||||
* Add web-ui v3.0.0a2
|
||||
|
@ -15,6 +15,9 @@ default_admin_password = admin
|
||||
|
||||
[Server]
|
||||
|
||||
; Server name, default is what is returned by socket.gethostname()
|
||||
name = GNS3_Server
|
||||
|
||||
; What protocol the server uses (http or https)
|
||||
protocol = http
|
||||
|
||||
@ -54,6 +57,12 @@ configs_path = /home/gns3/GNS3/configs
|
||||
; "Affinity-square-gray", "Affinity-circle-blue", "Affinity-circle-red" and "Affinity-circle-gray"
|
||||
default_symbol_theme = Affinity-square-blue
|
||||
|
||||
; Option to enable or disable raw images to be uploaded to the server
|
||||
allow_raw_images = True
|
||||
|
||||
; Option to automatically discover images in the images directory
|
||||
auto_discover_images = True
|
||||
|
||||
; Option to automatically send crash reports to the GNS3 team
|
||||
report_errors = True
|
||||
|
||||
@ -133,3 +142,9 @@ monitor_host = 127.0.0.1
|
||||
enable_hardware_acceleration = True
|
||||
; Require hardware acceleration in order to start VMs
|
||||
require_hardware_acceleration = False
|
||||
|
||||
[VMware]
|
||||
; First vmnet interface of the range that can be managed by the GNS3 server
|
||||
vmnet_start_range = 2
|
||||
; Last vmnet interface of the range that can be managed by the GNS3 server. It must be maximum 19 on Windows.
|
||||
vmnet_end_range = 255
|
||||
|
@ -1,8 +1,8 @@
|
||||
-r requirements.txt
|
||||
|
||||
pytest==7.1.2
|
||||
flake8==5.0.4
|
||||
pytest==7.2.0
|
||||
flake8==5.0.4 # v5.0.4 is the last to support Python 3.7
|
||||
pytest-timeout==2.1.0
|
||||
pytest-asyncio==0.19.0
|
||||
pytest-asyncio==0.20.3
|
||||
requests==2.28.1
|
||||
httpx==0.23.0
|
||||
httpx==0.23.1
|
||||
|
@ -61,9 +61,11 @@ async def create_router(project_id: UUID, node_data: schemas.DynamipsCreate) ->
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
platform = node_data.platform
|
||||
chassis = None
|
||||
print(node_data.chassis, platform in DEFAULT_CHASSIS)
|
||||
if not node_data.chassis and platform in DEFAULT_CHASSIS:
|
||||
chassis = DEFAULT_CHASSIS[platform]
|
||||
else:
|
||||
chassis = node_data.chassis
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
vm = await dynamips_manager.create_node(
|
||||
node_data.pop("name"),
|
||||
|
@ -150,12 +150,22 @@ def suspend_ethernet_switch(node: EthernetSwitch = Depends(dep_node)) -> None:
|
||||
pass
|
||||
|
||||
|
||||
@router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def reload_ethernet_switch(node: EthernetSwitch = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Reload an Ethernet switch.
|
||||
This endpoint results in no action since Ethernet switch nodes are always on.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
)
|
||||
async def create_nio(
|
||||
async def create_ethernet_switch_nio(
|
||||
*,
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
port_number: int,
|
||||
@ -169,7 +179,7 @@ async def create_nio(
|
||||
|
||||
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_nio(
|
||||
async def delete_ethernet_switch_nio(
|
||||
*,
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
port_number: int,
|
||||
@ -185,7 +195,7 @@ async def delete_nio(
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
||||
async def start_capture(
|
||||
async def start_ethernet_switch_capture(
|
||||
*,
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
port_number: int,
|
||||
@ -205,7 +215,7 @@ async def start_capture(
|
||||
@router.post(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT
|
||||
)
|
||||
async def stop_capture(
|
||||
async def stop_ethernet_switch_capture(
|
||||
*,
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
port_number: int,
|
||||
|
@ -70,7 +70,6 @@ async def upload_image(
|
||||
current_user: schemas.User = Depends(get_current_active_user),
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
|
||||
install_appliances: Optional[bool] = False,
|
||||
allow_raw_image: Optional[bool] = False
|
||||
) -> schemas.Image:
|
||||
"""
|
||||
Upload an image.
|
||||
@ -91,6 +90,7 @@ async def upload_image(
|
||||
raise ControllerBadRequestError(f"Image '{image_path}' already exists")
|
||||
|
||||
try:
|
||||
allow_raw_image = Config.instance().settings.Server.allow_raw_images
|
||||
image = await write_image(image_path, full_path, request.stream(), images_repo, allow_raw_image=allow_raw_image)
|
||||
except (OSError, InvalidImageError, ClientDisconnect) as e:
|
||||
raise ControllerError(f"Could not save image '{image_path}': {e}")
|
||||
|
@ -18,7 +18,7 @@
|
||||
API routes for controller notifications.
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends, WebSocket, WebSocketDisconnect
|
||||
from fastapi import APIRouter, Request, Depends, WebSocket, WebSocketDisconnect
|
||||
from fastapi.responses import StreamingResponse
|
||||
from websockets.exceptions import ConnectionClosed, WebSocketException
|
||||
|
||||
@ -35,17 +35,24 @@ router = APIRouter()
|
||||
|
||||
|
||||
@router.get("", dependencies=[Depends(get_current_active_user)])
|
||||
async def controller_http_notifications() -> StreamingResponse:
|
||||
async def controller_http_notifications(request: Request) -> StreamingResponse:
|
||||
"""
|
||||
Receive controller notifications about the controller from HTTP stream.
|
||||
"""
|
||||
|
||||
from gns3server.api.server import app
|
||||
log.info(f"New client {request.client.host}:{request.client.port} has connected to controller HTTP "
|
||||
f"notification stream")
|
||||
|
||||
async def event_stream():
|
||||
try:
|
||||
with Controller.instance().notification.controller_queue() as queue:
|
||||
while True:
|
||||
while not app.state.exiting:
|
||||
msg = await queue.get_json(5)
|
||||
yield f"{msg}\n".encode("utf-8")
|
||||
|
||||
finally:
|
||||
log.info(f"Client {request.client.host}:{request.client.port} has disconnected from controller HTTP "
|
||||
f"notification stream")
|
||||
return StreamingResponse(event_stream(), media_type="application/json")
|
||||
|
||||
|
||||
|
@ -210,18 +210,19 @@ async def project_http_notifications(project_id: UUID) -> StreamingResponse:
|
||||
Receive project notifications about the controller from HTTP stream.
|
||||
"""
|
||||
|
||||
from gns3server.api.server import app
|
||||
controller = Controller.instance()
|
||||
project = controller.get_project(str(project_id))
|
||||
|
||||
log.info(f"New client has connected to the notification stream for project ID '{project.id}' (HTTP steam method)")
|
||||
log.info(f"New client has connected to the notification stream for project ID '{project.id}' (HTTP stream method)")
|
||||
|
||||
async def event_stream():
|
||||
|
||||
try:
|
||||
with controller.notification.project_queue(project.id) as queue:
|
||||
while True:
|
||||
while not app.state.exiting:
|
||||
msg = await queue.get_json(5)
|
||||
yield (f"{msg}\n").encode("utf-8")
|
||||
yield f"{msg}\n".encode("utf-8")
|
||||
finally:
|
||||
log.info(f"Client has disconnected from notification for project ID '{project.id}' (HTTP stream method)")
|
||||
if project.auto_close:
|
||||
|
@ -55,6 +55,9 @@ async def web_ui(file_path: str):
|
||||
if static is None or not os.path.exists(static):
|
||||
static = get_resource(os.path.join("static", "web-ui", "index.html"))
|
||||
|
||||
if static is None:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
# guesstype prefers to have text/html type than application/javascript
|
||||
# which results with warnings in Firefox 66 on Windows
|
||||
# Ref. gns3-server#1559
|
||||
|
45
gns3server/appliances/alpine-linux-virt.gns3a
Normal file
45
gns3server/appliances/alpine-linux-virt.gns3a
Normal file
@ -0,0 +1,45 @@
|
||||
{
|
||||
"appliance_id": "3da5c614-772c-4963-af86-f24e058c9216",
|
||||
"name": "Alpine Linux Virt",
|
||||
"category": "guest",
|
||||
"description": "Alpine Linux is a security-oriented, lightweight Linux distribution based on musl libc and busybox.\n\nThis is the qemu version of Alpine Linux, stripped down to the maximum, only the default packages are installed without an SSH server.",
|
||||
"vendor_name": "Alpine Linux Development Team",
|
||||
"vendor_url": "http://alpinelinux.org",
|
||||
"documentation_url": "http://wiki.alpinelinux.org",
|
||||
"product_name": "Alpine Linux Virt",
|
||||
"registry_version": 4,
|
||||
"status": "stable",
|
||||
"availability": "free",
|
||||
"maintainer": "Adnan RIHAN",
|
||||
"maintainer_email": "adnan@rihan.fr",
|
||||
"usage": "Autologin is enabled as \"root\" with no password.\n\nThe network interfaces aren't configured, you can do either of the following:\n- Use alpine's DHCP client: `udhcpc`\n- Configure them manually (ip address add …, ip route add …)\n- Modify interfaces file in /etc/network/interfaces\n- Use alpine's wizard: `setup-interfaces`",
|
||||
"symbol": "alpine-virt-qemu.svg",
|
||||
"port_name_format": "eth{0}",
|
||||
"qemu": {
|
||||
"adapter_type": "virtio-net-pci",
|
||||
"adapters": 1,
|
||||
"ram": 128,
|
||||
"hda_disk_interface": "virtio",
|
||||
"arch": "x86_64",
|
||||
"console_type": "telnet",
|
||||
"kvm": "allow"
|
||||
},
|
||||
"images": [
|
||||
{
|
||||
"filename": "alpine-virt-3.16.img",
|
||||
"version": "3.16",
|
||||
"md5sum": "ce90ff64b8f8e5860c49ea4a038e54cc",
|
||||
"filesize": 96468992,
|
||||
"download_url": "https://sourceforge.net/projects/gns-3/files/Qemu%20Appliances/",
|
||||
"direct_download_url": "https://sourceforge.net/projects/gns-3/files/Qemu%20Appliances/alpine-virt-3.16.img/download"
|
||||
}
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
"name": "3.16",
|
||||
"images": {
|
||||
"hda_disk_image": "alpine-virt-3.16.img"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@ -30,6 +30,13 @@
|
||||
"process_priority": "normal"
|
||||
},
|
||||
"images": [
|
||||
{
|
||||
"filename": "arubaoscx-disk-image-genericx86-p4-20220815162137.vmdk",
|
||||
"version": "10.10.1000",
|
||||
"md5sum": "40f9ddf1e12640376af443b5d982f2f6",
|
||||
"filesize": 356162560,
|
||||
"download_url": "https://asp.arubanetworks.com/"
|
||||
},
|
||||
{
|
||||
"filename": "arubaoscx-disk-image-genericx86-p4-20220616193419.vmdk",
|
||||
"version": "10.10.0002",
|
||||
@ -95,6 +102,12 @@
|
||||
}
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
"name": "10.10.1000",
|
||||
"images": {
|
||||
"hda_disk_image": "arubaoscx-disk-image-genericx86-p4-20220815162137.vmdk"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "10.10.0002",
|
||||
"images": {
|
||||
|
@ -21,14 +21,14 @@
|
||||
"images": [
|
||||
{
|
||||
"filename": "c3725-adventerprisek9-mz.124-15.T14.image",
|
||||
"version": "124-25.T14",
|
||||
"version": "124-15.T14",
|
||||
"md5sum": "64f8c427ed48fd21bd02cf1ff254c4eb",
|
||||
"filesize": 97859480
|
||||
}
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
"name": "124-25.T14",
|
||||
"name": "124-15.T14",
|
||||
"idlepc": "0x60c09aa0",
|
||||
"images": {
|
||||
"image": "c3725-adventerprisek9-mz.124-15.T14.image"
|
||||
|
@ -77,6 +77,13 @@
|
||||
"md5sum": "4cf5b7fd68075b6f7ee0dd41a4029ca0",
|
||||
"filesize": 2150017536,
|
||||
"download_url": "https://software.cisco.com/download/"
|
||||
},
|
||||
{
|
||||
"filename": "Cisco_Firepower_Management_Center_Virtual-6.2.2-81.qcow2",
|
||||
"version": "6.2.2 (81)",
|
||||
"md5sum": "2f75c9c6c18a6fbb5516f6f451aef3a4",
|
||||
"filesize": 2112356352,
|
||||
"download_url": "https://software.cisco.com/download/"
|
||||
}
|
||||
],
|
||||
"versions": [
|
||||
@ -121,6 +128,12 @@
|
||||
"images": {
|
||||
"hda_disk_image": "Cisco_Firepower_Management_Center_Virtual_VMware-6.2.1-342-disk1.vmdk"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "6.2.2 (81)",
|
||||
"images": {
|
||||
"hda_disk_image": "Cisco_Firepower_Management_Center_Virtual-6.2.2-81.qcow2"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -25,6 +25,14 @@
|
||||
"kvm": "require"
|
||||
},
|
||||
"images": [
|
||||
{
|
||||
"filename": "cumulus-linux-5.3.1-vx-amd64-qemu.qcow2",
|
||||
"version": "5.3.1",
|
||||
"md5sum": "366b4e5afbfb638244fac4dd6cd092fd",
|
||||
"filesize": 2147479552,
|
||||
"download_url": "https://www.nvidia.com/en-us/networking/ethernet-switching/cumulus-vx/download/",
|
||||
"direct_download_url": "https://d2cd9e7ca6hntp.cloudfront.net/public/CumulusLinux-5.3.1/cumulus-linux-5.3.1-vx-amd64-qemu.qcow2"
|
||||
},
|
||||
{
|
||||
"filename": "cumulus-linux-5.1.0-vx-amd64-qemu.qcow2",
|
||||
"version": "5.1.0",
|
||||
|
@ -24,20 +24,20 @@
|
||||
},
|
||||
"images": [
|
||||
{
|
||||
"filename": "debian-11-genericcloud-amd64-20220711-1073.qcow2",
|
||||
"version": "11.4",
|
||||
"md5sum": "e8fadf4bbf7324a2e2875a5ba00588e7",
|
||||
"filesize": 253231104,
|
||||
"filename": "debian-11-genericcloud-amd64-20221219-1234.qcow2",
|
||||
"version": "11.6",
|
||||
"md5sum": "bd6ddbccc89e40deb7716b812958238d",
|
||||
"filesize": 258801664,
|
||||
"download_url": "https://cloud.debian.org/images/cloud/bullseye/",
|
||||
"direct_download_url": "https://cloud.debian.org/images/cloud/bullseye/20220711-1073/debian-11-genericcloud-amd64-20220711-1073.qcow2"
|
||||
"direct_download_url": "https://cloud.debian.org/images/cloud/bullseye/20221219-1234/debian-11-genericcloud-amd64-20221219-1234.qcow2"
|
||||
},
|
||||
{
|
||||
"filename": "debian-10-genericcloud-amd64-20220328-962.qcow2",
|
||||
"version": "10.12",
|
||||
"md5sum": "e92dfa1fc779fff807856f6ea6876e42",
|
||||
"filesize": 232980480,
|
||||
"filename": "debian-10-genericcloud-amd64-20220911-1135.qcow2",
|
||||
"version": "10.13",
|
||||
"md5sum": "9d4d1175bef974caba79dd6ca33d500c",
|
||||
"filesize": 234749952,
|
||||
"download_url": "https://cloud.debian.org/images/cloud/buster/",
|
||||
"direct_download_url": "https://cloud.debian.org/images/cloud/buster/20220328-962/debian-10-genericcloud-amd64-20220328-962.qcow2"
|
||||
"direct_download_url": "https://cloud.debian.org/images/cloud/buster/20220911-1135/debian-10-genericcloud-amd64-20220911-1135.qcow2"
|
||||
},
|
||||
{
|
||||
"filename": "debian-cloud-init-data.iso",
|
||||
@ -49,16 +49,16 @@
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
"name": "11.4",
|
||||
"name": "11.6",
|
||||
"images": {
|
||||
"hda_disk_image": "debian-11-genericcloud-amd64-20220711-1073.qcow2",
|
||||
"hda_disk_image": "debian-11-genericcloud-amd64-20221219-1234.qcow2",
|
||||
"cdrom_image": "debian-cloud-init-data.iso"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "10.12",
|
||||
"name": "10.13",
|
||||
"images": {
|
||||
"hda_disk_image": "debian-10-genericcloud-amd64-20220328-962.qcow2",
|
||||
"hda_disk_image": "debian-10-genericcloud-amd64-20220911-1135.qcow2",
|
||||
"cdrom_image": "debian-cloud-init-data.iso"
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@
|
||||
"status": "stable",
|
||||
"maintainer": "Andras Dosztal",
|
||||
"maintainer_email": "developers@gns3.net",
|
||||
"usage": "You can add records by adding entries to the /etc/hosts file in the following format:\n%IP_ADDRESS% %HOSTNAME%.lab %HOSTNAME%\n\nExample:\n192.168.123.10 router1.lab router1",
|
||||
"usage": "You can add records by adding entries to the /etc/hosts file in the following format:\n%IP_ADDRESS% %HOSTNAME%.lab %HOSTNAME%\n\nExample:\n192.168.123.10 router1.lab router1\n\nIf you require DNS requests to be serviced from a different subnet than the one that the DNS server resides on then do the following:\n\n1. Edit (nano or vim) /ect/init.d/dnsmasq\n2. Find the line DNSMASQ_OPTS=\"$DNSMASQ_OPTS --local-service\"\n3. Remove the --local-service or comment that line out and add DNSMASQ_OPTS=\"\"\n4. Restart dnsmasq - service dnsmaq restart",
|
||||
"symbol": "linux_guest.svg",
|
||||
"docker": {
|
||||
"adapters": 1,
|
||||
|
@ -27,6 +27,13 @@
|
||||
"options": "-nographic"
|
||||
},
|
||||
"images": [
|
||||
{
|
||||
"filename": "VOSSGNS3.8.8.0.0.qcow2",
|
||||
"version": "v8.8.0.0",
|
||||
"md5sum": "caa01094bad8ea5750261924b82ca746",
|
||||
"filesize": 348389376,
|
||||
"direct_download_url": "https://akamai-ep.extremenetworks.com/Extreme_P/github-en/Virtual_VOSS/VOSSGNS3.8.8.0.0.qcow2"
|
||||
},
|
||||
{
|
||||
"filename": "VOSSGNS3.8.4.0.0.qcow2",
|
||||
"version": "v8.4.0.0",
|
||||
@ -78,6 +85,13 @@
|
||||
}
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
"name": "v8.8.0.0",
|
||||
"images":
|
||||
{
|
||||
"hda_disk_image": "VOSSGNS3.8.8.0.0.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "v8.4.0.0",
|
||||
"images": {
|
||||
|
@ -34,6 +34,13 @@
|
||||
"filesize": 340631552,
|
||||
"download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
|
||||
},
|
||||
{
|
||||
"filename": "FAZ_VM64_KVM-v7.0.5-build0365-FORTINET.out.kvm.qcow2",
|
||||
"version": "7.0.5",
|
||||
"md5sum": "6cbc1f865ed285bb3a73323e222f03b8",
|
||||
"filesize": 334184448,
|
||||
"download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
|
||||
},
|
||||
{
|
||||
"filename": "FAZ_VM64_KVM-v6-build2288-FORTINET.out.kvm.qcow2",
|
||||
"version": "6.4.5",
|
||||
@ -191,6 +198,13 @@
|
||||
"hdb_disk_image": "empty30G.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "7.0.5",
|
||||
"images": {
|
||||
"hda_disk_image": "FAZ_VM64_KVM-v7.0.5-build0365-FORTINET.out.kvm.qcow2",
|
||||
"hdb_disk_image": "empty30G.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "6.4.5",
|
||||
"images": {
|
||||
|
@ -27,6 +27,13 @@
|
||||
"kvm": "allow"
|
||||
},
|
||||
"images": [
|
||||
{
|
||||
"filename": "FGT_VM64_KVM-v7.2.3.F-build1262-FORTINET.out.kvm.qcow2",
|
||||
"version": "7.2.3",
|
||||
"md5sum": "e8f3c5879f0d6fe238dc2665a3508694",
|
||||
"filesize": 87490560,
|
||||
"download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
|
||||
},
|
||||
{
|
||||
"filename": "FGT_VM64_KVM-v7.2.1.F-build1254-FORTINET.out.kvm.qcow2",
|
||||
"version": "7.2.1",
|
||||
@ -34,6 +41,20 @@
|
||||
"filesize": 86704128,
|
||||
"download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
|
||||
},
|
||||
{
|
||||
"filename": "FGT_VM64_KVM-v7.0.9.M-build0444-FORTINET.out.kvm.qcow2",
|
||||
"version": "7.0.9",
|
||||
"md5sum": "0aee912ab11bf9a4b0e3fc1a62dd0e40",
|
||||
"filesize": 77135872,
|
||||
"download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
|
||||
},
|
||||
{
|
||||
"filename": "FGT_VM64_KVM-v6.M-build2030-FORTINET.out.kvm.qcow2",
|
||||
"version": "6.4.11",
|
||||
"md5sum": "bcd7491ddfa31fec4f618b73792456e4",
|
||||
"filesize": 69861376,
|
||||
"download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
|
||||
},
|
||||
{
|
||||
"filename": "FGT_VM64_KVM-v6-build1828-FORTINET.out.kvm.qcow2",
|
||||
"version": "6.4.5",
|
||||
@ -261,6 +282,13 @@
|
||||
}
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
"name": "7.2.3",
|
||||
"images": {
|
||||
"hda_disk_image": "FGT_VM64_KVM-v7.2.3.F-build1262-FORTINET.out.kvm.qcow2",
|
||||
"hdb_disk_image": "empty30G.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "7.2.1",
|
||||
"images": {
|
||||
@ -268,6 +296,20 @@
|
||||
"hdb_disk_image": "empty30G.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "7.0.9",
|
||||
"images": {
|
||||
"hda_disk_image": "FGT_VM64_KVM-v7.0.9.M-build0444-FORTINET.out.kvm.qcow2",
|
||||
"hdb_disk_image": "empty30G.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "6.4.11",
|
||||
"images": {
|
||||
"hda_disk_image": "FGT_VM64_KVM-v6.M-build2030-FORTINET.out.kvm.qcow2",
|
||||
"hdb_disk_image": "empty30G.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "6.4.5",
|
||||
"images": {
|
||||
|
@ -34,6 +34,13 @@
|
||||
"filesize": 242814976,
|
||||
"download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
|
||||
},
|
||||
{
|
||||
"filename": "FMG_VM64_KVM-v7.0.5-build0365-FORTINET.out.kvm.qcow2",
|
||||
"version": "7.0.5",
|
||||
"md5sum": "e8b9c992784cea766b52a427a5fe0279",
|
||||
"filesize": 237535232,
|
||||
"download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
|
||||
},
|
||||
{
|
||||
"filename": "FMG_VM64_KVM-v6-build2288-FORTINET.out.kvm.qcow2",
|
||||
"version": "6.4.5",
|
||||
@ -191,6 +198,13 @@
|
||||
"hdb_disk_image": "empty30G.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "7.0.5",
|
||||
"images": {
|
||||
"hda_disk_image": "FMG_VM64_KVM-v7.0.5-build0365-FORTINET.out.kvm.qcow2",
|
||||
"hdb_disk_image": "empty30G.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "6.4.5",
|
||||
"images": {
|
||||
|
@ -22,6 +22,14 @@
|
||||
"kvm": "allow"
|
||||
},
|
||||
"images": [
|
||||
{
|
||||
"filename": "frr-8.2.2.qcow2",
|
||||
"version": "8.2.2",
|
||||
"md5sum": "45cda6b991a1b9e8205a3a0ecc953640",
|
||||
"filesize": 56609280,
|
||||
"download_url": "https://sourceforge.net/projects/gns-3/files/Qemu%20Appliances/",
|
||||
"direct_download_url": "http://downloads.sourceforge.net/project/gns-3/Qemu%20Appliances/frr-8.2.2.qcow2"
|
||||
},
|
||||
{
|
||||
"filename": "frr-8.1.0.qcow2",
|
||||
"version": "8.1.0",
|
||||
@ -48,6 +56,12 @@
|
||||
}
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
"name": "8.2.2",
|
||||
"images": {
|
||||
"hda_disk_image": "frr-8.2.2.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "8.1.0",
|
||||
"images": {
|
||||
|
@ -14,7 +14,7 @@
|
||||
"usage": "In the web interface login as admin/admin\n\nPersistent configuration:\n- Add \"/var/lib/redis\" as an additional persistent directory.\n- Use \"redis-cli save\" in an auxiliary console to save the configuration.",
|
||||
"docker": {
|
||||
"adapters": 1,
|
||||
"image": "ntop/ntopng:stable",
|
||||
"image": "ntop/ntopng:latest",
|
||||
"start_command": "--dns-mode 2 --interface eth0",
|
||||
"console_type": "http",
|
||||
"console_http_port": 3000,
|
||||
|
@ -23,6 +23,24 @@
|
||||
"kvm": "allow"
|
||||
},
|
||||
"images": [
|
||||
{
|
||||
"filename": "openwrt-22.03.0-x86-64-generic-ext4-combined.img",
|
||||
"version": "22.03.0",
|
||||
"md5sum": "0f9a266bd8a6cdfcaf0b59f7ba103a0e",
|
||||
"filesize": 126353408,
|
||||
"download_url": "https://downloads.openwrt.org/releases/22.03.0/targets/x86/64/",
|
||||
"direct_download_url": "https://downloads.openwrt.org/releases/22.03.0/targets/x86/64/openwrt-22.03.0-x86-64-generic-ext4-combined.img.gz",
|
||||
"compression": "gzip"
|
||||
},
|
||||
{
|
||||
"filename": "openwrt-21.02.3-x86-64-generic-ext4-combined.img",
|
||||
"version": "21.02.3",
|
||||
"md5sum": "652c432e758420cb8d749139e8bef14b",
|
||||
"filesize": 126353408,
|
||||
"download_url": "https://downloads.openwrt.org/releases/21.02.3/targets/x86/64/",
|
||||
"direct_download_url": "https://downloads.openwrt.org/releases/21.02.3/targets/x86/64/openwrt-21.02.3-x86-64-generic-ext4-combined.img.gz",
|
||||
"compression": "gzip"
|
||||
},
|
||||
{
|
||||
"filename": "openwrt-21.02.1-x86-64-generic-ext4-combined.img",
|
||||
"version": "21.02.1",
|
||||
@ -196,6 +214,18 @@
|
||||
}
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
"name": "22.03.0",
|
||||
"images": {
|
||||
"hda_disk_image": "openwrt-22.03.0-x86-64-generic-ext4-combined.img"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "21.02.3",
|
||||
"images": {
|
||||
"hda_disk_image": "openwrt-21.02.3-x86-64-generic-ext4-combined.img"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "21.02.1",
|
||||
"images": {
|
||||
|
67
gns3server/appliances/reactos.gns3a
Normal file
67
gns3server/appliances/reactos.gns3a
Normal file
@ -0,0 +1,67 @@
|
||||
{
|
||||
"appliance_id": "c811e588-39ef-41e9-9f60-6e8e08618c3d",
|
||||
"name": "ReactOS",
|
||||
"category": "guest",
|
||||
"description": "Imagine running your favorite Windows applications and drivers in an open-source environment you can trust.\nThat's the mission of ReactOS! ",
|
||||
"vendor_name": "ReactOS Project",
|
||||
"vendor_url": "https://reactos.org/",
|
||||
"documentation_url": "https://reactos.org/what-is-reactos/",
|
||||
"product_name": "ReactOS",
|
||||
"product_url": "https://reactos.org/",
|
||||
"registry_version": 3,
|
||||
"status": "stable",
|
||||
"maintainer": "Savio D'souza",
|
||||
"maintainer_email": "savio2002@yahoo.co.in",
|
||||
"usage": "Passwords are set during installation.",
|
||||
"qemu": {
|
||||
"adapter_type": "e1000",
|
||||
"adapters": 1,
|
||||
"ram": 1024,
|
||||
"hda_disk_interface": "ide",
|
||||
"arch": "x86_64",
|
||||
"console_type": "vnc",
|
||||
"kvm": "require"
|
||||
},
|
||||
"images": [
|
||||
{
|
||||
"filename": "ReactOS-0.4.14-release-15-gb6088a6.iso",
|
||||
"version": "Installer-0.4.14-release-15",
|
||||
"md5sum": "af4be6b27463446905f155f14232d2b4",
|
||||
"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",
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"filename": "empty30G.qcow2",
|
||||
"version": "1.0",
|
||||
"md5sum": "3411a599e822f2ac6be560a26405821a",
|
||||
"filesize": 197120,
|
||||
"download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%30disk/",
|
||||
"direct_download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/empty30G.qcow2/download"
|
||||
}
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
"name": "Installer-0.4.14-release-15",
|
||||
"images": {
|
||||
"hda_disk_image": "empty30G.qcow2",
|
||||
"cdrom_image": "ReactOS-0.4.14-release-15-gb6088a6.iso"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Live-0.4.14-release-15",
|
||||
"images": {
|
||||
"hda_disk_image": "empty30G.qcow2",
|
||||
"cdrom_image": "ReactOS-0.4.14-release-15-gb6088a6-Live.iso"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
"appliance_id": "f82b74c4-0f30-456f-a582-63daca528502",
|
||||
"name": "VyOS",
|
||||
"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. VyOS has a subscription LTS version and a community rolling release. The latest version in this appliance is the monthly snapshot of the rolling release track.",
|
||||
"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/",
|
||||
"documentation_url": "https://docs.vyos.io/",
|
||||
@ -26,6 +26,20 @@
|
||||
"kvm": "allow"
|
||||
},
|
||||
"images": [
|
||||
{
|
||||
"filename": "vyos-1.3.2-amd64.iso",
|
||||
"version": "1.3.2",
|
||||
"md5sum": "070743faac800f9e5197058a8b6b3ba1",
|
||||
"filesize": 334495744,
|
||||
"download_url": "https://support.vyos.io/en/downloads/files/vyos-1-3-2-generic-iso-image"
|
||||
},
|
||||
{
|
||||
"filename": "vyos-1.3.1-S1-amd64.iso",
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"filename": "vyos-1.3.1-amd64.iso",
|
||||
"version": "1.3.1",
|
||||
@ -41,12 +55,11 @@
|
||||
"download_url": "https://support.vyos.io/en/downloads/files/vyos-1-3-0-generic-iso-image"
|
||||
},
|
||||
{
|
||||
"filename": "vyos-1.3.0-epa3-amd64.iso",
|
||||
"version": "1.3.0-epa3",
|
||||
"md5sum": "1b5de684d8995844e35fa5cec3171811",
|
||||
"filesize": 331350016,
|
||||
"download_url": "https://vyos.net/get/snapshots/",
|
||||
"direct_download_url": "https://s3.amazonaws.com/s3-us.vyos.io/snapshot/vyos-1.3.0-epa3/vyos-1.3.0-epa3-amd64.iso"
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"filename": "vyos-1.2.8-amd64.iso",
|
||||
@ -80,6 +93,20 @@
|
||||
}
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
"name": "1.3.2",
|
||||
"images": {
|
||||
"hda_disk_image": "empty8G.qcow2",
|
||||
"cdrom_image": "vyos-1.3.2-amd64.iso"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "1.3.1-S1",
|
||||
"images": {
|
||||
"hda_disk_image": "empty8G.qcow2",
|
||||
"cdrom_image": "vyos-1.3.1-S1-amd64.iso"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "1.3.1",
|
||||
"images": {
|
||||
@ -95,10 +122,10 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "1.3.0-epa3",
|
||||
"name": "1.2.9",
|
||||
"images": {
|
||||
"hda_disk_image": "empty8G.qcow2",
|
||||
"cdrom_image": "vyos-1.3.0-epa3-amd64.iso"
|
||||
"cdrom_image": "vyos-1.2.9-amd64.iso"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -638,8 +638,12 @@ class BaseNode:
|
||||
# no need to allocate a port when the console type is none
|
||||
self._console = None
|
||||
elif console_type == "vnc":
|
||||
# VNC is a special case and the range must be 5900-6000
|
||||
self._console = self._manager.port_manager.get_free_tcp_port(self._project, 5900, 6000)
|
||||
vnc_console_start_port_range, vnc_console_end_port_range = self._get_vnc_console_port_range()
|
||||
self._console = self._manager.port_manager.get_free_tcp_port(
|
||||
self._project,
|
||||
vnc_console_start_port_range,
|
||||
vnc_console_end_port_range
|
||||
)
|
||||
else:
|
||||
self._console = self._manager.port_manager.get_free_tcp_port(self._project)
|
||||
|
||||
|
@ -166,7 +166,7 @@ class EthernetSwitch(Device):
|
||||
"""
|
||||
if ports != self._ports:
|
||||
if len(self._nios) > 0 and len(ports) != len(self._ports):
|
||||
raise NodeError("Can't modify a switch already connected.")
|
||||
raise NodeError("Cannot change ports on a switch that is already connected.")
|
||||
|
||||
port_number = 0
|
||||
for port in ports:
|
||||
@ -356,7 +356,7 @@ class EthernetSwitch(Device):
|
||||
elif settings["type"] == "dot1q":
|
||||
await self.set_dot1q_port(port_number, settings["vlan"])
|
||||
elif settings["type"] == "qinq":
|
||||
await self.set_qinq_port(port_number, settings["vlan"], settings.get("ethertype"))
|
||||
await self.set_qinq_port(port_number, settings["vlan"], settings.get("ethertype", "0x8100"))
|
||||
|
||||
async def set_access_port(self, port_number, vlan_id):
|
||||
"""
|
||||
@ -427,7 +427,7 @@ class EthernetSwitch(Device):
|
||||
await self._hypervisor.send(
|
||||
'ethsw set_qinq_port "{name}" {nio} {outer_vlan} {ethertype}'.format(
|
||||
name=self._name, nio=nio, outer_vlan=outer_vlan, ethertype=ethertype if ethertype != "0x8100" else ""
|
||||
)
|
||||
).strip()
|
||||
)
|
||||
|
||||
log.info(
|
||||
|
@ -306,6 +306,8 @@ class VMware(BaseManager):
|
||||
|
||||
def refresh_vmnet_list(self, ubridge=True):
|
||||
|
||||
log.debug("Refreshing VMnet list with uBridge={}".format(ubridge))
|
||||
|
||||
if ubridge:
|
||||
# VMnet host adapters must be present when uBridge is used
|
||||
vmnet_interfaces = self._get_vmnet_interfaces_ubridge()
|
||||
@ -314,6 +316,7 @@ class VMware(BaseManager):
|
||||
self._vmnets_info = vmnet_interfaces.copy()
|
||||
vmnet_interfaces = list(vmnet_interfaces.keys())
|
||||
|
||||
log.debug("Found {} VMnet interfaces".format(len(vmnet_interfaces)))
|
||||
# remove vmnets already in use
|
||||
for vmware_vm in self._nodes.values():
|
||||
for used_vmnet in vmware_vm.vmnets:
|
||||
@ -324,6 +327,7 @@ class VMware(BaseManager):
|
||||
# remove vmnets that are not managed
|
||||
for vmnet in vmnet_interfaces.copy():
|
||||
if vmnet in vmnet_interfaces and self.is_managed_vmnet(vmnet) is False:
|
||||
log.debug("{} is not managed by GNS3".format(vmnet))
|
||||
vmnet_interfaces.remove(vmnet)
|
||||
|
||||
self._vmnets = vmnet_interfaces
|
||||
|
@ -481,6 +481,10 @@ class VMwareVM(BaseNode):
|
||||
|
||||
try:
|
||||
if self._ubridge_hypervisor:
|
||||
if parse_version(platform.mac_ver()[0]) >= parse_version("11.0.0"):
|
||||
# give VMware some time to create the bridge interfaces, so they can be found
|
||||
# by psutil and used by uBridge
|
||||
await asyncio.sleep(1)
|
||||
for adapter_number in range(0, self._adapters):
|
||||
nio = self._ethernet_adapters[adapter_number].get_nio(0)
|
||||
if nio:
|
||||
|
@ -22,6 +22,7 @@ import socket
|
||||
import shutil
|
||||
import asyncio
|
||||
import random
|
||||
import importlib_resources
|
||||
|
||||
from ..config import Config
|
||||
from .project import Project
|
||||
@ -32,7 +33,6 @@ from .notification import Notification
|
||||
from .symbols import Symbols
|
||||
from .topology import load_topology
|
||||
from .gns3vm import GNS3VM
|
||||
from ..utils.get_resource import get_resource
|
||||
from .gns3vm.gns3_vm_error import GNS3VMError
|
||||
from .controller_error import ControllerError, ControllerNotFoundError
|
||||
|
||||
@ -62,9 +62,10 @@ class Controller:
|
||||
async def start(self, computes=None):
|
||||
|
||||
log.info("Controller is starting")
|
||||
self.load_base_files()
|
||||
self._load_base_files()
|
||||
server_config = Config.instance().settings.Server
|
||||
Config.instance().listen_for_config_changes(self._update_config)
|
||||
name = server_config.name
|
||||
host = server_config.host
|
||||
port = server_config.port
|
||||
|
||||
@ -86,7 +87,7 @@ class Controller:
|
||||
try:
|
||||
self._local_server = await self.add_compute(
|
||||
compute_id="local",
|
||||
name=f"{socket.gethostname()} (controller)",
|
||||
name=name,
|
||||
protocol=protocol,
|
||||
host=host,
|
||||
console_host=console_host,
|
||||
@ -281,6 +282,7 @@ class Controller:
|
||||
except OSError as e:
|
||||
log.error(f"Cannot read Etag appliance file '{etag_appliances_path}': {e}")
|
||||
|
||||
self._appliance_manager.install_builtin_appliances()
|
||||
self._appliance_manager.load_appliances()
|
||||
self._config_loaded = True
|
||||
|
||||
@ -305,20 +307,27 @@ class Controller:
|
||||
except OSError as e:
|
||||
log.error(str(e))
|
||||
|
||||
def load_base_files(self):
|
||||
def _load_base_files(self):
|
||||
"""
|
||||
At startup we copy base file to the user location to allow
|
||||
them to customize it
|
||||
"""
|
||||
|
||||
dst_path = self.configs_path()
|
||||
src_path = get_resource("configs")
|
||||
try:
|
||||
for file in os.listdir(src_path):
|
||||
if not os.path.exists(os.path.join(dst_path, file)):
|
||||
shutil.copy(os.path.join(src_path, file), os.path.join(dst_path, file))
|
||||
except OSError:
|
||||
pass
|
||||
if hasattr(sys, "frozen") and sys.platform.startswith("win"):
|
||||
resource_path = os.path.normpath(os.path.join(os.path.dirname(sys.executable), "configs"))
|
||||
for filename in os.listdir(resource_path):
|
||||
if not os.path.exists(os.path.join(dst_path, filename)):
|
||||
shutil.copy(os.path.join(resource_path, filename), os.path.join(dst_path, filename))
|
||||
else:
|
||||
for entry in importlib_resources.files('gns3server.configs').iterdir():
|
||||
full_path = os.path.join(dst_path, entry.name)
|
||||
if entry.is_file() and not os.path.exists(full_path):
|
||||
log.debug(f"Installing base config file {entry.name} to {full_path}")
|
||||
shutil.copy(str(entry), os.path.join(dst_path, entry.name))
|
||||
except OSError as e:
|
||||
log.error(f"Could not install base config files to {dst_path}: {e}")
|
||||
|
||||
def images_path(self):
|
||||
"""
|
||||
|
@ -15,10 +15,13 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
import asyncio
|
||||
import aiofiles
|
||||
import importlib_resources
|
||||
import shutil
|
||||
|
||||
from typing import Tuple, List
|
||||
from aiohttp.client_exceptions import ClientError
|
||||
@ -29,7 +32,6 @@ from pydantic import ValidationError
|
||||
from .appliance import Appliance
|
||||
from ..config import Config
|
||||
from ..utils.asyncio import locking
|
||||
from ..utils.get_resource import get_resource
|
||||
from ..utils.http_client import HTTPClient
|
||||
from .controller_error import ControllerBadRequestError, ControllerNotFoundError, ControllerError
|
||||
from .appliance_to_template import ApplianceToTemplate
|
||||
@ -82,9 +84,9 @@ class ApplianceManager:
|
||||
|
||||
return self._appliances
|
||||
|
||||
def appliances_path(self) -> str:
|
||||
def _custom_appliances_path(self) -> str:
|
||||
"""
|
||||
Get the image storage directory
|
||||
Get the custom appliance storage directory
|
||||
"""
|
||||
|
||||
server_config = Config.instance().settings.Server
|
||||
@ -92,6 +94,37 @@ class ApplianceManager:
|
||||
os.makedirs(appliances_path, exist_ok=True)
|
||||
return appliances_path
|
||||
|
||||
def _builtin_appliances_path(self):
|
||||
"""
|
||||
Get the built-in appliance storage directory
|
||||
"""
|
||||
|
||||
config = Config.instance()
|
||||
appliances_dir = os.path.join(config.config_dir, "appliances")
|
||||
os.makedirs(appliances_dir, exist_ok=True)
|
||||
return appliances_dir
|
||||
|
||||
def install_builtin_appliances(self):
|
||||
"""
|
||||
At startup we copy the built-in appliances files.
|
||||
"""
|
||||
|
||||
dst_path = self._builtin_appliances_path()
|
||||
try:
|
||||
if hasattr(sys, "frozen") and sys.platform.startswith("win"):
|
||||
resource_path = os.path.normpath(os.path.join(os.path.dirname(sys.executable), "appliances"))
|
||||
for filename in os.listdir(resource_path):
|
||||
if not os.path.exists(os.path.join(dst_path, filename)):
|
||||
shutil.copy(os.path.join(resource_path, filename), os.path.join(dst_path, filename))
|
||||
else:
|
||||
for entry in importlib_resources.files('gns3server.appliances').iterdir():
|
||||
full_path = os.path.join(dst_path, entry.name)
|
||||
if entry.is_file() and not os.path.exists(full_path):
|
||||
log.debug(f"Installing built-in appliance file {entry.name} to {full_path}")
|
||||
shutil.copy(str(entry), os.path.join(dst_path, entry.name))
|
||||
except OSError as e:
|
||||
log.error(f"Could not install built-in appliance files to {dst_path}: {e}")
|
||||
|
||||
def _find_appliances_from_image_checksum(self, image_checksum: str) -> List[Tuple[Appliance, str]]:
|
||||
"""
|
||||
Find appliances that matches an image checksum.
|
||||
@ -289,11 +322,11 @@ class ApplianceManager:
|
||||
self._appliances = {}
|
||||
for directory, builtin in (
|
||||
(
|
||||
get_resource("appliances"),
|
||||
self._builtin_appliances_path(),
|
||||
True,
|
||||
),
|
||||
(
|
||||
self.appliances_path(),
|
||||
self._custom_appliances_path(),
|
||||
False,
|
||||
),
|
||||
):
|
||||
@ -407,7 +440,7 @@ class ApplianceManager:
|
||||
|
||||
Controller.instance().save()
|
||||
json_data = await response.json()
|
||||
appliances_dir = get_resource("appliances")
|
||||
appliances_dir = self._builtin_appliances_path()
|
||||
downloaded_appliance_files = []
|
||||
for appliance in json_data:
|
||||
if appliance["type"] == "file":
|
||||
|
@ -21,6 +21,7 @@ from typing import Callable
|
||||
from fastapi import FastAPI
|
||||
|
||||
from gns3server.controller import Controller
|
||||
from gns3server.config import Config
|
||||
from gns3server.compute import MODULES
|
||||
from gns3server.compute.port_manager import PortManager
|
||||
from gns3server.utils.http_client import HTTPClient
|
||||
@ -60,6 +61,7 @@ def create_startup_handler(app: FastAPI) -> Callable:
|
||||
# computing with server start
|
||||
from gns3server.compute.qemu import Qemu
|
||||
|
||||
if Config.instance().settings.Server.auto_discover_images is True:
|
||||
# Start the discovering new images on file system 5 seconds after the server has started
|
||||
# to give it a chance to process API requests
|
||||
loop.call_later(5, asyncio.create_task, discover_images_on_filesystem(app))
|
||||
|
@ -59,7 +59,7 @@ class CrashReport:
|
||||
Report crash to a third party service
|
||||
"""
|
||||
|
||||
DSN = "https://bad2bd9ef9f14c8d9239d6f815ed453f@o19455.ingest.sentry.io/38482"
|
||||
DSN = "https://45f39fa6ea64493b8966a263049e844c@o19455.ingest.sentry.io/38482"
|
||||
_instance = None
|
||||
|
||||
def __init__(self):
|
||||
@ -95,8 +95,9 @@ class CrashReport:
|
||||
"os:name": platform.system(),
|
||||
"os:release": platform.release(),
|
||||
"os:win_32": " ".join(platform.win32_ver()),
|
||||
"os:mac": f"{platform.mac_ver()[0]} {platform.mac_ver()[2]}",
|
||||
"os:linux": " ".join(distro.linux_distribution()),
|
||||
"os:mac": "{} {}".format(platform.mac_ver()[0], platform.mac_ver()[2]),
|
||||
"os:linux": distro.name(pretty=True),
|
||||
|
||||
}
|
||||
|
||||
with sentry_sdk.configure_scope() as scope:
|
||||
|
@ -30,7 +30,7 @@ from sqlalchemy.exc import SQLAlchemyError
|
||||
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
|
||||
from gns3server.db.repositories.computes import ComputesRepository
|
||||
from gns3server.db.repositories.images import ImagesRepository
|
||||
from gns3server.utils.images import discover_images, check_valid_image_header, read_image_info, InvalidImageError
|
||||
from gns3server.utils.images import discover_images, check_valid_image_header, read_image_info, default_images_directory, InvalidImageError
|
||||
from gns3server import schemas
|
||||
|
||||
from .models import Base
|
||||
@ -117,12 +117,16 @@ def image_filter(change: Change, path: str) -> bool:
|
||||
|
||||
async def monitor_images_on_filesystem(app: FastAPI):
|
||||
|
||||
server_config = Config.instance().settings.Server
|
||||
images_dir = os.path.expanduser(server_config.images_path)
|
||||
directories_to_monitor = []
|
||||
for image_type in ("qemu", "ios", "iou"):
|
||||
image_dir = default_images_directory(image_type)
|
||||
if os.path.isdir(image_dir):
|
||||
log.debug(f"Monitoring for new images in '{image_dir}'")
|
||||
directories_to_monitor.append(image_dir)
|
||||
|
||||
try:
|
||||
async for changes in awatch(
|
||||
images_dir,
|
||||
*directories_to_monitor,
|
||||
watch_filter=image_filter,
|
||||
raise_interrupt=True
|
||||
):
|
||||
|
@ -78,6 +78,7 @@ class DynamipsWics(str, Enum):
|
||||
wic_1enet = "WIC-1ENET"
|
||||
wic_1t = "WIC-1T"
|
||||
wic_2t = "WIC-2T"
|
||||
_ = ""
|
||||
|
||||
|
||||
class DynamipsConsoleType(str, Enum):
|
||||
@ -113,7 +114,7 @@ class DynamipsMidplane(str, Enum):
|
||||
vxr = "vxr"
|
||||
|
||||
|
||||
# TODO: improve schema for Dynamips (match platform specific options, e.g. NPE allowd only for c7200)
|
||||
# TODO: improve schema for Dynamips (match platform specific options, e.g. NPE allowed only for c7200)
|
||||
class DynamipsBase(BaseModel):
|
||||
"""
|
||||
Common Dynamips node properties.
|
||||
|
@ -14,7 +14,7 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic import BaseModel, Field, validator
|
||||
from typing import Optional, List
|
||||
from uuid import UUID
|
||||
from enum import Enum
|
||||
@ -42,9 +42,17 @@ class EthernetSwitchPort(BaseModel):
|
||||
name: str
|
||||
port_number: int
|
||||
type: EthernetSwitchPortType = Field(..., description="Port type")
|
||||
vlan: Optional[int] = Field(None, ge=1, description="VLAN number")
|
||||
vlan: int = Field(..., ge=1, le=4094, description="VLAN number")
|
||||
ethertype: Optional[EthernetSwitchEtherType] = Field(None, description="QinQ Ethertype")
|
||||
|
||||
@validator("ethertype")
|
||||
def validate_ethertype(cls, v, values):
|
||||
|
||||
if v is not None:
|
||||
if "type" not in values or values["type"] != EthernetSwitchPortType.qinq:
|
||||
raise ValueError("Ethertype is only for QinQ port type")
|
||||
return v
|
||||
|
||||
|
||||
class TelnetConsoleType(str, Enum):
|
||||
"""
|
||||
|
@ -14,6 +14,8 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import socket
|
||||
|
||||
from enum import Enum
|
||||
from pydantic import BaseModel, Field, SecretStr, FilePath, DirectoryPath, validator
|
||||
from typing import List
|
||||
@ -123,6 +125,7 @@ class BuiltinSymbolTheme(str, Enum):
|
||||
class ServerSettings(BaseModel):
|
||||
|
||||
local: bool = False
|
||||
name: str = f"{socket.gethostname()} (controller)"
|
||||
protocol: ServerProtocol = ServerProtocol.http
|
||||
host: str = "0.0.0.0"
|
||||
port: int = Field(3080, gt=0, le=65535)
|
||||
@ -136,6 +139,8 @@ class ServerSettings(BaseModel):
|
||||
symbols_path: str = "~/GNS3/symbols"
|
||||
configs_path: str = "~/GNS3/configs"
|
||||
default_symbol_theme: BuiltinSymbolTheme = BuiltinSymbolTheme.affinity_square_blue
|
||||
allow_raw_images: bool = True
|
||||
auto_discover_images: bool = True
|
||||
report_errors: bool = True
|
||||
additional_images_paths: List[str] = Field(default_factory=list)
|
||||
console_start_port_range: int = Field(5000, gt=0, le=65535)
|
||||
|
@ -46,6 +46,6 @@
|
||||
|
||||
gtag('config', 'G-5D6FZL9923');
|
||||
</script>
|
||||
<script src="runtime.b694e7eebb80e122.js" type="module"></script><script src="polyfills.7e7f4a715088fcc2.js" type="module"></script><script src="main.7309c0d705101a52.js" type="module"></script>
|
||||
<script src="runtime.b694e7eebb80e122.js" type="module"></script><script src="polyfills.7e7f4a715088fcc2.js" type="module"></script><script src="main.3903ccaada4e6da7.js" type="module"></script>
|
||||
|
||||
</body></html>
|
1
gns3server/static/web-ui/main.3903ccaada4e6da7.js
Normal file
1
gns3server/static/web-ui/main.3903ccaada4e6da7.js
Normal file
File diff suppressed because one or more lines are too long
1
gns3server/static/web-ui/main.41e1ff185162d1659203.js
Normal file
1
gns3server/static/web-ui/main.41e1ff185162d1659203.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -15,6 +15,7 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import sys
|
||||
import asyncio
|
||||
|
||||
|
||||
@ -39,7 +40,11 @@ class Pool:
|
||||
while len(self._tasks) > 0 or len(pending) > 0:
|
||||
while len(self._tasks) > 0 and len(pending) < self._concurrency:
|
||||
task, args, kwargs = self._tasks.pop(0)
|
||||
pending.add(task(*args, **kwargs))
|
||||
if sys.version_info >= (3, 7):
|
||||
t = asyncio.create_task(task(*args, **kwargs))
|
||||
else:
|
||||
t = asyncio.get_event_loop().create_task(task(*args, **kwargs))
|
||||
pending.add(t)
|
||||
(done, pending) = await asyncio.wait(pending, return_when=asyncio.FIRST_COMPLETED)
|
||||
for task in done:
|
||||
if task.exception():
|
||||
|
@ -14,34 +14,18 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import tempfile
|
||||
import pkg_resources
|
||||
import atexit
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import importlib_resources
|
||||
|
||||
from contextlib import ExitStack
|
||||
resource_manager = ExitStack()
|
||||
atexit.register(resource_manager.close)
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
egg_cache_dir = tempfile.mkdtemp()
|
||||
pkg_resources.set_extraction_path(egg_cache_dir)
|
||||
except ValueError:
|
||||
# If the path is already set the module throw an error
|
||||
pass
|
||||
|
||||
|
||||
@atexit.register
|
||||
def clean_egg_cache():
|
||||
try:
|
||||
import shutil
|
||||
|
||||
log.debug("Clean egg cache %s", egg_cache_dir)
|
||||
shutil.rmtree(egg_cache_dir)
|
||||
except Exception:
|
||||
# We don't care if we can not cleanup
|
||||
pass
|
||||
|
||||
|
||||
def get_resource(resource_name):
|
||||
"""
|
||||
@ -51,7 +35,9 @@ def get_resource(resource_name):
|
||||
resource_path = None
|
||||
if hasattr(sys, "frozen"):
|
||||
resource_path = os.path.normpath(os.path.join(os.path.dirname(sys.executable), resource_name))
|
||||
elif not hasattr(sys, "frozen") and pkg_resources.resource_exists("gns3server", resource_name):
|
||||
resource_path = pkg_resources.resource_filename("gns3server", resource_name)
|
||||
resource_path = os.path.normpath(resource_path)
|
||||
else:
|
||||
ref = importlib_resources.files("gns3server") / resource_name
|
||||
path = resource_manager.enter_context(importlib_resources.as_file(ref))
|
||||
if os.path.exists(path):
|
||||
resource_path = os.path.normpath(path)
|
||||
return resource_path
|
||||
|
@ -136,7 +136,8 @@ async def discover_images(image_type: str, skip_image_paths: list = None) -> Lis
|
||||
files = set()
|
||||
images = []
|
||||
|
||||
for directory in images_directories(image_type):
|
||||
for directory in images_directories(image_type, include_parent_directory=False):
|
||||
log.info(f"Discovering images in '{directory}'")
|
||||
for root, _, filenames in os.walk(os.path.normpath(directory)):
|
||||
for filename in filenames:
|
||||
if filename.endswith(".tmp") or filename.endswith(".md5sum") or filename.startswith("."):
|
||||
@ -189,7 +190,7 @@ def default_images_directory(image_type):
|
||||
raise NotImplementedError(f"%s node type is not supported", image_type)
|
||||
|
||||
|
||||
def images_directories(image_type):
|
||||
def images_directories(image_type, include_parent_directory=True):
|
||||
"""
|
||||
Return all directories where we will look for images
|
||||
by priority
|
||||
@ -199,7 +200,7 @@ def images_directories(image_type):
|
||||
|
||||
server_config = Config.instance().settings.Server
|
||||
paths = []
|
||||
img_dir = os.path.expanduser(server_config.images_path)
|
||||
|
||||
type_img_directory = default_images_directory(image_type)
|
||||
try:
|
||||
os.makedirs(type_img_directory, exist_ok=True)
|
||||
@ -208,7 +209,9 @@ def images_directories(image_type):
|
||||
pass
|
||||
for directory in server_config.additional_images_paths:
|
||||
paths.append(directory)
|
||||
if include_parent_directory:
|
||||
# Compatibility with old topologies we look in parent directory
|
||||
img_dir = os.path.expanduser(server_config.images_path)
|
||||
paths.append(img_dir)
|
||||
# Return only the existing paths
|
||||
return [force_unix_path(p) for p in paths if os.path.exists(p)]
|
||||
|
@ -22,7 +22,7 @@
|
||||
# or negative for a release candidate or beta (after the base version
|
||||
# number has been incremented)
|
||||
|
||||
__version__ = "3.0.0a2"
|
||||
__version__ = "3.0.0a3"
|
||||
__version_info__ = (3, 0, 0, -99)
|
||||
|
||||
if "dev" in __version__:
|
||||
@ -30,7 +30,7 @@ if "dev" in __version__:
|
||||
import os
|
||||
import subprocess
|
||||
if os.path.exists(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", ".git")):
|
||||
r = subprocess.check_output(["git", "rev-parse", "--short", "HEAD"]).decode().strip()
|
||||
__version__ = f"{__version__}-{r}"
|
||||
r = subprocess.check_output(["git", "rev-parse", "--short", "HEAD"]).decode().strip("\n")
|
||||
__version__ += "+" + r
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
@ -23,7 +23,7 @@ depend() {
|
||||
|
||||
checkconfig() {
|
||||
if yesno "${GNS3_SERVER_LOG_ENABLED}" ; then
|
||||
command_args+=" --log ${GNS3_SERVER_LOG}";
|
||||
command_args="${command_args} --log ${GNS3_SERVER_LOG}";
|
||||
if [ "${command_user}" ] ; then
|
||||
checkpath --directory --mode 0700 --owner "${command_user}" "${GNS3_SERVER_LOG_PATH}";
|
||||
else
|
||||
|
@ -6,7 +6,7 @@ After=network.target network-online.target
|
||||
[Service]
|
||||
User=gns3
|
||||
Group=gns3
|
||||
ExecStart=/usr/bin/gns3server
|
||||
ExecStart=/usr/local/bin/gns3server
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
@ -1,20 +1,21 @@
|
||||
uvicorn==0.18.3
|
||||
fastapi==0.82.0
|
||||
uvicorn==0.20.0
|
||||
fastapi==0.88.0
|
||||
python-multipart==0.0.5
|
||||
websockets==10.3
|
||||
aiohttp==3.8.1
|
||||
websockets==10.4
|
||||
aiohttp>=3.8.3,<3.9
|
||||
async-timeout==4.0.2
|
||||
aiofiles==0.8.0
|
||||
Jinja2==3.1.2
|
||||
sentry-sdk==1.9.5
|
||||
psutil==5.9.1
|
||||
distro==1.7.0
|
||||
py-cpuinfo==8.0.0
|
||||
sqlalchemy==1.4.40
|
||||
aiosqlite==0.17.0
|
||||
aiofiles==22.1.0
|
||||
Jinja2>=3.1.2,<3.2
|
||||
sentry-sdk==1.12.1,<1.13
|
||||
psutil==5.9.4
|
||||
distro>=1.7.0
|
||||
py-cpuinfo==9.0.0
|
||||
sqlalchemy==1.4.45
|
||||
aiosqlite==0.18.0
|
||||
passlib[bcrypt]==1.7.4
|
||||
python-jose==3.3.0
|
||||
email-validator==1.2.1
|
||||
watchfiles==0.16.1
|
||||
zstandard==0.18.0
|
||||
setuptools==60.6.0 # don't upgrade because of https://github.com/pypa/setuptools/issues/3084
|
||||
email-validator==1.3.0
|
||||
watchfiles==0.18.1
|
||||
zstandard==0.19.0
|
||||
importlib_resources>=1.3
|
||||
setuptools>=60.8.1
|
||||
|
1
setup.py
1
setup.py
@ -105,6 +105,7 @@ setup(
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: Implementation :: CPython",
|
||||
],
|
||||
)
|
||||
|
454
tests/api/routes/compute/test_ethernet_switch_nodes.py
Normal file
454
tests/api/routes/compute/test_ethernet_switch_nodes.py
Normal file
@ -0,0 +1,454 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2022 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/>.
|
||||
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
|
||||
from fastapi import FastAPI, status
|
||||
from httpx import AsyncClient
|
||||
from tests.utils import asyncio_patch, AsyncioMagicMock
|
||||
from unittest.mock import call
|
||||
|
||||
from gns3server.compute.project import Project
|
||||
|
||||
pytestmark = pytest.mark.asyncio
|
||||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
async def ethernet_switch(app: FastAPI, compute_client: AsyncClient, compute_project: Project) -> dict:
|
||||
|
||||
params = {"name": "Ethernet Switch"}
|
||||
with asyncio_patch("gns3server.compute.dynamips.nodes.ethernet_switch.EthernetSwitch.create") as mock:
|
||||
response = await compute_client.post(
|
||||
app.url_path_for("compute:create_ethernet_switch", project_id=compute_project.id),
|
||||
json=params
|
||||
)
|
||||
assert mock.called
|
||||
assert response.status_code == status.HTTP_201_CREATED
|
||||
|
||||
json_response = response.json()
|
||||
node = compute_project.get_node(json_response["node_id"])
|
||||
node._hypervisor = AsyncioMagicMock()
|
||||
node._hypervisor.send = AsyncioMagicMock()
|
||||
node._hypervisor.version = "0.2.16"
|
||||
return json_response
|
||||
|
||||
|
||||
async def test_ethernet_switch_create(app: FastAPI, compute_client: AsyncClient, compute_project: Project) -> None:
|
||||
|
||||
params = {"name": "Ethernet Switch 1"}
|
||||
with asyncio_patch("gns3server.compute.dynamips.nodes.ethernet_switch.EthernetSwitch.create") as mock:
|
||||
response = await compute_client.post(
|
||||
app.url_path_for("compute:create_ethernet_switch", project_id=compute_project.id),
|
||||
json=params
|
||||
)
|
||||
assert mock.called
|
||||
assert response.status_code == status.HTTP_201_CREATED
|
||||
assert response.json()["name"] == "Ethernet Switch 1"
|
||||
assert response.json()["project_id"] == compute_project.id
|
||||
|
||||
|
||||
async def test_ethernet_switch_get(app: FastAPI, compute_client: AsyncClient, compute_project: Project, ethernet_switch: dict) -> None:
|
||||
|
||||
response = await compute_client.get(
|
||||
app.url_path_for(
|
||||
"compute:get_ethernet_switch",
|
||||
project_id=ethernet_switch["project_id"],
|
||||
node_id=ethernet_switch["node_id"]
|
||||
)
|
||||
)
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
assert response.json()["name"] == "Ethernet Switch"
|
||||
assert response.json()["project_id"] == compute_project.id
|
||||
assert response.json()["status"] == "started"
|
||||
|
||||
|
||||
async def test_ethernet_switch_duplicate(
|
||||
app: FastAPI,
|
||||
compute_client: AsyncClient,
|
||||
compute_project: Project,
|
||||
ethernet_switch: dict
|
||||
) -> None:
|
||||
|
||||
# create destination switch first
|
||||
params = {"name": "Ethernet Switch 2"}
|
||||
with asyncio_patch("gns3server.compute.dynamips.nodes.ethernet_switch.EthernetSwitch.create") as mock:
|
||||
response = await compute_client.post(
|
||||
app.url_path_for(
|
||||
"compute:create_ethernet_switch",
|
||||
project_id=compute_project.id),
|
||||
json=params
|
||||
)
|
||||
assert mock.called
|
||||
assert response.status_code == status.HTTP_201_CREATED
|
||||
|
||||
params = {"destination_node_id": response.json()["node_id"]}
|
||||
response = await compute_client.post(
|
||||
app.url_path_for(
|
||||
"compute:duplicate_ethernet_switch",
|
||||
project_id=ethernet_switch["project_id"],
|
||||
node_id=ethernet_switch["node_id"]), json=params
|
||||
)
|
||||
assert response.status_code == status.HTTP_201_CREATED
|
||||
|
||||
|
||||
async def test_ethernet_switch_update(
|
||||
app: FastAPI,
|
||||
compute_client: AsyncClient,
|
||||
compute_project: Project,
|
||||
ethernet_switch: dict
|
||||
) -> None:
|
||||
|
||||
params = {
|
||||
"name": "test",
|
||||
"console_type": "telnet"
|
||||
}
|
||||
|
||||
response = await compute_client.put(
|
||||
app.url_path_for(
|
||||
"compute:update_ethernet_switch",
|
||||
project_id=ethernet_switch["project_id"],
|
||||
node_id=ethernet_switch["node_id"]),
|
||||
json=params
|
||||
)
|
||||
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
assert response.json()["name"] == "test"
|
||||
node = compute_project.get_node(ethernet_switch["node_id"])
|
||||
node._hypervisor.send.assert_called_with("ethsw rename \"Ethernet Switch\" \"test\"")
|
||||
|
||||
|
||||
async def test_ethernet_switch_update_ports(
|
||||
app: FastAPI,
|
||||
compute_client: AsyncClient,
|
||||
compute_project: Project,
|
||||
ethernet_switch: dict
|
||||
) -> None:
|
||||
|
||||
port_params = {
|
||||
"ports_mapping": [
|
||||
{
|
||||
"name": "Ethernet0",
|
||||
"port_number": 0,
|
||||
"type": "qinq",
|
||||
"vlan": 1
|
||||
},
|
||||
{
|
||||
"name": "Ethernet1",
|
||||
"port_number": 1,
|
||||
"type": "qinq",
|
||||
"vlan": 2,
|
||||
"ethertype": "0x88A8"
|
||||
},
|
||||
{
|
||||
"name": "Ethernet2",
|
||||
"port_number": 2,
|
||||
"type": "dot1q",
|
||||
"vlan": 3,
|
||||
},
|
||||
{
|
||||
"name": "Ethernet3",
|
||||
"port_number": 3,
|
||||
"type": "access",
|
||||
"vlan": 4,
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
response = await compute_client.put(
|
||||
app.url_path_for(
|
||||
"compute:update_ethernet_switch",
|
||||
project_id=ethernet_switch["project_id"],
|
||||
node_id=ethernet_switch["node_id"]),
|
||||
json=port_params
|
||||
)
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
|
||||
nio_params = {
|
||||
"type": "nio_udp",
|
||||
"lport": 4242,
|
||||
"rport": 4343,
|
||||
"rhost": "127.0.0.1"
|
||||
}
|
||||
|
||||
for port_mapping in port_params["ports_mapping"]:
|
||||
port_number = port_mapping["port_number"]
|
||||
vlan = port_mapping["vlan"]
|
||||
port_type = port_mapping["type"]
|
||||
ethertype = port_mapping.get("ethertype", "")
|
||||
url = app.url_path_for(
|
||||
"compute:create_ethernet_switch_nio",
|
||||
project_id=ethernet_switch["project_id"],
|
||||
node_id=ethernet_switch["node_id"],
|
||||
adapter_number="0",
|
||||
port_number=f"{port_number}"
|
||||
)
|
||||
await compute_client.post(url, json=nio_params)
|
||||
|
||||
node = compute_project.get_node(ethernet_switch["node_id"])
|
||||
nio = node.get_nio(port_number)
|
||||
calls = [
|
||||
call.send(f'nio create_udp {nio.name} 4242 127.0.0.1 4343'),
|
||||
call.send(f'ethsw add_nio "Ethernet Switch" {nio.name}'),
|
||||
call.send(f'ethsw set_{port_type}_port "Ethernet Switch" {nio.name} {vlan} {ethertype}'.strip())
|
||||
]
|
||||
node._hypervisor.send.assert_has_calls(calls)
|
||||
node._hypervisor.send.reset_mock()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"ports_settings",
|
||||
(
|
||||
(
|
||||
{
|
||||
"name": "Ethernet0",
|
||||
"port_number": 0,
|
||||
"type": "dot42q", # invalid port type
|
||||
"vlan": 1,
|
||||
}
|
||||
),
|
||||
(
|
||||
{
|
||||
"name": "Ethernet0",
|
||||
"port_number": 0,
|
||||
"type": "access", # missing vlan field
|
||||
}
|
||||
),
|
||||
(
|
||||
{
|
||||
"name": "Ethernet0",
|
||||
"port_number": 0,
|
||||
"type": "dot1q",
|
||||
"vlan": 1,
|
||||
"ethertype": "0x88A8" # EtherType is only for QinQ
|
||||
}
|
||||
),
|
||||
(
|
||||
{
|
||||
"name": "Ethernet0",
|
||||
"port_number": 0,
|
||||
"type": "qinq",
|
||||
"vlan": 1,
|
||||
"ethertype": "0x4242" # not a valid EtherType
|
||||
}
|
||||
),
|
||||
(
|
||||
{
|
||||
"name": "Ethernet0",
|
||||
"port_number": 0,
|
||||
"type": "access",
|
||||
"vlan": 0, # minimum vlan number is 1
|
||||
}
|
||||
),
|
||||
(
|
||||
{
|
||||
"name": "Ethernet0",
|
||||
"port_number": 0,
|
||||
"type": "access",
|
||||
"vlan": 4242, # maximum vlan number is 4094
|
||||
}
|
||||
),
|
||||
)
|
||||
)
|
||||
async def test_ethernet_switch_update_ports_invalid(
|
||||
app: FastAPI,
|
||||
compute_client: AsyncClient,
|
||||
ethernet_switch: dict,
|
||||
ports_settings: dict,
|
||||
) -> None:
|
||||
|
||||
port_params = {
|
||||
"ports_mapping": [ports_settings]
|
||||
}
|
||||
|
||||
response = await compute_client.put(
|
||||
app.url_path_for(
|
||||
"compute:update_ethernet_switch",
|
||||
project_id=ethernet_switch["project_id"],
|
||||
node_id=ethernet_switch["node_id"]),
|
||||
json=port_params
|
||||
)
|
||||
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
|
||||
|
||||
|
||||
async def test_ethernet_switch_delete(app: FastAPI, compute_client: AsyncClient, ethernet_switch: dict) -> None:
|
||||
|
||||
response = await compute_client.delete(
|
||||
app.url_path_for(
|
||||
"compute:delete_ethernet_switch",
|
||||
project_id=ethernet_switch["project_id"],
|
||||
node_id=ethernet_switch["node_id"]
|
||||
)
|
||||
)
|
||||
assert response.status_code == status.HTTP_204_NO_CONTENT
|
||||
|
||||
|
||||
async def test_ethernet_switch_start(app: FastAPI, compute_client: AsyncClient, ethernet_switch: dict) -> None:
|
||||
|
||||
response = await compute_client.post(
|
||||
app.url_path_for(
|
||||
"compute:start_ethernet_switch",
|
||||
project_id=ethernet_switch["project_id"],
|
||||
node_id=ethernet_switch["node_id"])
|
||||
)
|
||||
assert response.status_code == status.HTTP_204_NO_CONTENT
|
||||
|
||||
|
||||
async def test_ethernet_switch_stop(app: FastAPI, compute_client: AsyncClient, ethernet_switch: dict) -> None:
|
||||
|
||||
response = await compute_client.post(
|
||||
app.url_path_for(
|
||||
"compute:stop_ethernet_switch",
|
||||
project_id=ethernet_switch["project_id"],
|
||||
node_id=ethernet_switch["node_id"])
|
||||
)
|
||||
assert response.status_code == status.HTTP_204_NO_CONTENT
|
||||
|
||||
|
||||
async def test_ethernet_switch_suspend(app: FastAPI, compute_client: AsyncClient, ethernet_switch: dict) -> None:
|
||||
|
||||
response = await compute_client.post(
|
||||
app.url_path_for(
|
||||
"compute:suspend_ethernet_switch",
|
||||
project_id=ethernet_switch["project_id"],
|
||||
node_id=ethernet_switch["node_id"])
|
||||
)
|
||||
assert response.status_code == status.HTTP_204_NO_CONTENT
|
||||
|
||||
|
||||
async def test_ethernet_switch_reload(app: FastAPI, compute_client: AsyncClient, ethernet_switch: dict) -> None:
|
||||
|
||||
response = await compute_client.post(
|
||||
app.url_path_for(
|
||||
"compute:reload_ethernet_switch",
|
||||
project_id=ethernet_switch["project_id"],
|
||||
node_id=ethernet_switch["node_id"])
|
||||
)
|
||||
assert response.status_code == status.HTTP_204_NO_CONTENT
|
||||
|
||||
|
||||
async def test_ethernet_switch_create_udp(
|
||||
app: FastAPI,
|
||||
compute_client: AsyncClient,
|
||||
compute_project: Project,
|
||||
ethernet_switch: dict
|
||||
) -> None:
|
||||
|
||||
params = {
|
||||
"type": "nio_udp",
|
||||
"lport": 4242,
|
||||
"rport": 4343,
|
||||
"rhost": "127.0.0.1"
|
||||
}
|
||||
|
||||
url = app.url_path_for(
|
||||
"compute:create_ethernet_switch_nio",
|
||||
project_id=ethernet_switch["project_id"],
|
||||
node_id=ethernet_switch["node_id"],
|
||||
adapter_number="0",
|
||||
port_number="0"
|
||||
)
|
||||
response = await compute_client.post(url, json=params)
|
||||
assert response.status_code == status.HTTP_201_CREATED
|
||||
assert response.json()["type"] == "nio_udp"
|
||||
|
||||
node = compute_project.get_node(ethernet_switch["node_id"])
|
||||
nio = node.get_nio(0)
|
||||
calls = [
|
||||
call.send(f'nio create_udp {nio.name} 4242 127.0.0.1 4343'),
|
||||
call.send(f'ethsw add_nio "Ethernet Switch" {nio.name}'),
|
||||
call.send(f'ethsw set_access_port "Ethernet Switch" {nio.name} 1')
|
||||
]
|
||||
node._hypervisor.send.assert_has_calls(calls)
|
||||
|
||||
|
||||
async def test_ethernet_switch_delete_nio(
|
||||
app: FastAPI,
|
||||
compute_client: AsyncClient,
|
||||
compute_project: Project,
|
||||
ethernet_switch: dict
|
||||
) -> None:
|
||||
|
||||
params = {
|
||||
"type": "nio_udp",
|
||||
"lport": 4242,
|
||||
"rport": 4343,
|
||||
"rhost": "127.0.0.1"
|
||||
}
|
||||
|
||||
url = app.url_path_for(
|
||||
"compute:create_ethernet_switch_nio",
|
||||
project_id=ethernet_switch["project_id"],
|
||||
node_id=ethernet_switch["node_id"],
|
||||
adapter_number="0",
|
||||
port_number="0"
|
||||
)
|
||||
await compute_client.post(url, json=params)
|
||||
|
||||
node = compute_project.get_node(ethernet_switch["node_id"])
|
||||
node._hypervisor.send.reset_mock()
|
||||
nio = node.get_nio(0)
|
||||
|
||||
url = app.url_path_for(
|
||||
"compute:delete_ethernet_switch_nio",
|
||||
project_id=ethernet_switch["project_id"],
|
||||
node_id=ethernet_switch["node_id"],
|
||||
adapter_number="0",
|
||||
port_number="0"
|
||||
)
|
||||
response = await compute_client.delete(url)
|
||||
assert response.status_code == status.HTTP_204_NO_CONTENT
|
||||
|
||||
calls = [
|
||||
call(f'ethsw remove_nio "Ethernet Switch" {nio.name}'),
|
||||
call(f'nio delete {nio.name}')
|
||||
]
|
||||
node._hypervisor.send.assert_has_calls(calls)
|
||||
|
||||
|
||||
async def test_ethernet_switch_start_capture(app: FastAPI, compute_client: AsyncClient, ethernet_switch: dict) -> None:
|
||||
|
||||
params = {
|
||||
"capture_file_name": "test.pcap",
|
||||
"data_link_type": "DLT_EN10MB"
|
||||
}
|
||||
|
||||
url = app.url_path_for("compute:start_ethernet_switch_capture",
|
||||
project_id=ethernet_switch["project_id"],
|
||||
node_id=ethernet_switch["node_id"],
|
||||
adapter_number="0",
|
||||
port_number="0")
|
||||
|
||||
with asyncio_patch("gns3server.compute.dynamips.nodes.ethernet_switch.EthernetSwitch.start_capture") as mock:
|
||||
response = await compute_client.post(url, json=params)
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
assert mock.called
|
||||
assert "test.pcap" in response.json()["pcap_file_path"]
|
||||
|
||||
|
||||
async def test_ethernet_switch_stop_capture(app: FastAPI, compute_client: AsyncClient, ethernet_switch: dict) -> None:
|
||||
|
||||
url = app.url_path_for("compute:stop_ethernet_switch_capture",
|
||||
project_id=ethernet_switch["project_id"],
|
||||
node_id=ethernet_switch["node_id"],
|
||||
adapter_number="0",
|
||||
port_number="0")
|
||||
|
||||
with asyncio_patch("gns3server.compute.dynamips.nodes.ethernet_switch.EthernetSwitch.stop_capture") as mock:
|
||||
response = await compute_client.post(url)
|
||||
assert response.status_code == status.HTTP_204_NO_CONTENT
|
||||
assert mock.called
|
@ -21,12 +21,19 @@ import shutil
|
||||
|
||||
from fastapi import FastAPI, status
|
||||
from httpx import AsyncClient
|
||||
from gns3server.controller import Controller
|
||||
|
||||
pytestmark = pytest.mark.asyncio
|
||||
|
||||
|
||||
class TestApplianceRoutes:
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _install_builtin_appliances(self, controller: Controller):
|
||||
|
||||
controller.appliance_manager.install_builtin_appliances()
|
||||
controller.appliance_manager.load_appliances()
|
||||
|
||||
async def test_appliances_list(self, app: FastAPI, client: AsyncClient) -> None:
|
||||
|
||||
response = await client.get(app.url_path_for("get_appliances"))
|
||||
|
@ -263,6 +263,7 @@ class TestImageRoutes:
|
||||
controller: Controller
|
||||
) -> None:
|
||||
|
||||
controller.appliance_manager.install_builtin_appliances()
|
||||
controller.appliance_manager.load_appliances() # make sure appliances are loaded
|
||||
image_path = "tests/resources/empty30G.qcow2"
|
||||
image_name = os.path.basename(image_path)
|
||||
|
@ -356,10 +356,10 @@ async def test_load_base_files(controller, config, tmpdir):
|
||||
with open(str(tmpdir / 'iou_l2_base_startup-config.txt'), 'w+') as f:
|
||||
f.write('test')
|
||||
|
||||
controller.load_base_files()
|
||||
controller._load_base_files()
|
||||
assert os.path.exists(str(tmpdir / 'iou_l3_base_startup-config.txt'))
|
||||
|
||||
# Check is the file has not been overwrite
|
||||
# Check is the file has not been overwritten
|
||||
with open(str(tmpdir / 'iou_l2_base_startup-config.txt')) as f:
|
||||
assert f.read() == 'test'
|
||||
|
||||
@ -380,6 +380,7 @@ def test_appliances(controller, config, tmpdir):
|
||||
json.dump(my_appliance, f)
|
||||
|
||||
config.settings.Server.appliances_path = str(tmpdir)
|
||||
controller.appliance_manager.install_builtin_appliances()
|
||||
controller.appliance_manager.load_appliances()
|
||||
assert len(controller.appliance_manager.appliances) > 0
|
||||
for appliance in controller.appliance_manager.appliances.values():
|
||||
|
@ -1,4 +1,4 @@
|
||||
-r requirements.txt
|
||||
|
||||
pywin32==303
|
||||
pywin32==305
|
||||
wmi==1.5.1
|
||||
|
Loading…
Reference in New Issue
Block a user