Merge branch '2.2' into 3.0

# Conflicts:
#	.github/workflows/testing.yml
#	CHANGELOG
#	appveyor.yml
#	dev-requirements.txt
#	gns3server/compute/base_node.py
#	gns3server/controller/__init__.py
#	gns3server/controller/appliance_manager.py
#	gns3server/crash_report.py
#	gns3server/static/web-ui/index.html
#	gns3server/utils/get_resource.py
#	gns3server/version.py
#	gns3server/web/route.py
#	requirements.txt
#	tests/handlers/api/compute/test_qemu.py
#	win-requirements.txt
This commit is contained in:
grossmj 2022-11-09 20:30:28 +08:00
commit 263febecbc
25 changed files with 328 additions and 92 deletions

View File

@ -17,7 +17,7 @@ jobs:
strategy: strategy:
matrix: matrix:
python-version: ["3.7", "3.8", "3.9", "3.10"] python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2

View File

@ -1,5 +1,20 @@
# Change Log # Change Log
## 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 ## 3.0.0a2 06/09/2022
* Add web-ui v3.0.0a2 * Add web-ui v3.0.0a2

View File

@ -133,3 +133,9 @@ monitor_host = 127.0.0.1
enable_hardware_acceleration = True enable_hardware_acceleration = True
; Require hardware acceleration in order to start VMs ; Require hardware acceleration in order to start VMs
require_hardware_acceleration = False 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

View File

@ -1,6 +1,6 @@
-r requirements.txt -r requirements.txt
pytest==7.1.2 pytest==7.2.0
flake8==5.0.4 flake8==5.0.4
pytest-timeout==2.1.0 pytest-timeout==2.1.0
pytest-asyncio==0.19.0 pytest-asyncio==0.19.0

View 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"
}
}
]
}

View File

@ -30,6 +30,13 @@
"process_priority": "normal" "process_priority": "normal"
}, },
"images": [ "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", "filename": "arubaoscx-disk-image-genericx86-p4-20220616193419.vmdk",
"version": "10.10.0002", "version": "10.10.0002",
@ -95,6 +102,12 @@
} }
], ],
"versions": [ "versions": [
{
"name": "10.10.1000",
"images": {
"hda_disk_image": "arubaoscx-disk-image-genericx86-p4-20220815162137.vmdk"
}
},
{ {
"name": "10.10.0002", "name": "10.10.0002",
"images": { "images": {

View File

@ -24,20 +24,20 @@
}, },
"images": [ "images": [
{ {
"filename": "debian-11-genericcloud-amd64-20220711-1073.qcow2", "filename": "debian-11-genericcloud-amd64-20220911-1135.qcow2",
"version": "11.4", "version": "11.5",
"md5sum": "e8fadf4bbf7324a2e2875a5ba00588e7", "md5sum": "06e481ddd23682af4326226661c13d8f",
"filesize": 253231104, "filesize": 254672896,
"download_url": "https://cloud.debian.org/images/cloud/bullseye/", "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/20220911-1135/debian-11-genericcloud-amd64-20220911-1135.qcow2"
}, },
{ {
"filename": "debian-10-genericcloud-amd64-20220328-962.qcow2", "filename": "debian-10-genericcloud-amd64-20220911-1135.qcow2",
"version": "10.12", "version": "10.13",
"md5sum": "e92dfa1fc779fff807856f6ea6876e42", "md5sum": "9d4d1175bef974caba79dd6ca33d500c",
"filesize": 232980480, "filesize": 234749952,
"download_url": "https://cloud.debian.org/images/cloud/buster/", "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", "filename": "debian-cloud-init-data.iso",
@ -49,16 +49,16 @@
], ],
"versions": [ "versions": [
{ {
"name": "11.4", "name": "11.5",
"images": { "images": {
"hda_disk_image": "debian-11-genericcloud-amd64-20220711-1073.qcow2", "hda_disk_image": "debian-11-genericcloud-amd64-20220911-1135.qcow2",
"cdrom_image": "debian-cloud-init-data.iso" "cdrom_image": "debian-cloud-init-data.iso"
} }
}, },
{ {
"name": "10.12", "name": "10.13",
"images": { "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" "cdrom_image": "debian-cloud-init-data.iso"
} }
} }

View File

@ -23,6 +23,24 @@
"kvm": "allow" "kvm": "allow"
}, },
"images": [ "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", "filename": "openwrt-21.02.1-x86-64-generic-ext4-combined.img",
"version": "21.02.1", "version": "21.02.1",
@ -196,6 +214,18 @@
} }
], ],
"versions": [ "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", "name": "21.02.1",
"images": { "images": {

View 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"
}
}
]
}

View File

@ -2,7 +2,7 @@
"appliance_id": "f82b74c4-0f30-456f-a582-63daca528502", "appliance_id": "f82b74c4-0f30-456f-a582-63daca528502",
"name": "VyOS", "name": "VyOS",
"category": "router", "category": "router",
"description": "VyOS is a community fork of Vyatta, a Linux-based network operating system that provides software-based network routing, firewall, and VPN functionality. 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_name": "Linux",
"vendor_url": "https://vyos.net/", "vendor_url": "https://vyos.net/",
"documentation_url": "https://docs.vyos.io/", "documentation_url": "https://docs.vyos.io/",
@ -26,6 +26,20 @@
"kvm": "allow" "kvm": "allow"
}, },
"images": [ "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", "filename": "vyos-1.3.1-amd64.iso",
"version": "1.3.1", "version": "1.3.1",
@ -40,14 +54,6 @@
"filesize": 338690048, "filesize": 338690048,
"download_url": "https://support.vyos.io/en/downloads/files/vyos-1-3-0-generic-iso-image" "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.8-amd64.iso", "filename": "vyos-1.2.8-amd64.iso",
"version": "1.2.8", "version": "1.2.8",
@ -80,6 +86,20 @@
} }
], ],
"versions": [ "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", "name": "1.3.1",
"images": { "images": {
@ -94,13 +114,6 @@
"cdrom_image": "vyos-1.3.0-amd64.iso" "cdrom_image": "vyos-1.3.0-amd64.iso"
} }
}, },
{
"name": "1.3.0-epa3",
"images": {
"hda_disk_image": "empty8G.qcow2",
"cdrom_image": "vyos-1.3.0-epa3-amd64.iso"
}
},
{ {
"name": "1.2.8", "name": "1.2.8",
"images": { "images": {

View File

@ -638,8 +638,12 @@ class BaseNode:
# no need to allocate a port when the console type is none # no need to allocate a port when the console type is none
self._console = None self._console = None
elif console_type == "vnc": elif console_type == "vnc":
# VNC is a special case and the range must be 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, 5900, 6000) self._console = self._manager.port_manager.get_free_tcp_port(
self._project,
vnc_console_start_port_range,
vnc_console_end_port_range
)
else: else:
self._console = self._manager.port_manager.get_free_tcp_port(self._project) self._console = self._manager.port_manager.get_free_tcp_port(self._project)

View File

@ -306,6 +306,8 @@ class VMware(BaseManager):
def refresh_vmnet_list(self, ubridge=True): def refresh_vmnet_list(self, ubridge=True):
log.debug("Refreshing VMnet list with uBridge={}".format(ubridge))
if ubridge: if ubridge:
# VMnet host adapters must be present when uBridge is used # VMnet host adapters must be present when uBridge is used
vmnet_interfaces = self._get_vmnet_interfaces_ubridge() vmnet_interfaces = self._get_vmnet_interfaces_ubridge()
@ -314,6 +316,7 @@ class VMware(BaseManager):
self._vmnets_info = vmnet_interfaces.copy() self._vmnets_info = vmnet_interfaces.copy()
vmnet_interfaces = list(vmnet_interfaces.keys()) vmnet_interfaces = list(vmnet_interfaces.keys())
log.debug("Found {} VMnet interfaces".format(len(vmnet_interfaces)))
# remove vmnets already in use # remove vmnets already in use
for vmware_vm in self._nodes.values(): for vmware_vm in self._nodes.values():
for used_vmnet in vmware_vm.vmnets: for used_vmnet in vmware_vm.vmnets:
@ -324,6 +327,7 @@ class VMware(BaseManager):
# remove vmnets that are not managed # remove vmnets that are not managed
for vmnet in vmnet_interfaces.copy(): for vmnet in vmnet_interfaces.copy():
if vmnet in vmnet_interfaces and self.is_managed_vmnet(vmnet) is False: 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) vmnet_interfaces.remove(vmnet)
self._vmnets = vmnet_interfaces self._vmnets = vmnet_interfaces

View File

@ -481,6 +481,10 @@ class VMwareVM(BaseNode):
try: try:
if self._ubridge_hypervisor: 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): for adapter_number in range(0, self._adapters):
nio = self._ethernet_adapters[adapter_number].get_nio(0) nio = self._ethernet_adapters[adapter_number].get_nio(0)
if nio: if nio:

View File

@ -22,6 +22,7 @@ import socket
import shutil import shutil
import asyncio import asyncio
import random import random
import importlib_resources
from ..config import Config from ..config import Config
from .project import Project from .project import Project
@ -32,7 +33,6 @@ from .notification import Notification
from .symbols import Symbols from .symbols import Symbols
from .topology import load_topology from .topology import load_topology
from .gns3vm import GNS3VM from .gns3vm import GNS3VM
from ..utils.get_resource import get_resource
from .gns3vm.gns3_vm_error import GNS3VMError from .gns3vm.gns3_vm_error import GNS3VMError
from .controller_error import ControllerError, ControllerNotFoundError from .controller_error import ControllerError, ControllerNotFoundError
@ -62,7 +62,7 @@ class Controller:
async def start(self, computes=None): async def start(self, computes=None):
log.info("Controller is starting") log.info("Controller is starting")
self.load_base_files() self._load_base_files()
server_config = Config.instance().settings.Server server_config = Config.instance().settings.Server
Config.instance().listen_for_config_changes(self._update_config) Config.instance().listen_for_config_changes(self._update_config)
host = server_config.host host = server_config.host
@ -281,6 +281,7 @@ class Controller:
except OSError as e: except OSError as e:
log.error(f"Cannot read Etag appliance file '{etag_appliances_path}': {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._appliance_manager.load_appliances()
self._config_loaded = True self._config_loaded = True
@ -305,20 +306,27 @@ class Controller:
except OSError as e: except OSError as e:
log.error(str(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 At startup we copy base file to the user location to allow
them to customize it them to customize it
""" """
dst_path = self.configs_path() dst_path = self.configs_path()
src_path = get_resource("configs")
try: try:
for file in os.listdir(src_path): if hasattr(sys, "frozen") and sys.platform.startswith("win"):
if not os.path.exists(os.path.join(dst_path, file)): resource_path = os.path.normpath(os.path.join(os.path.dirname(sys.executable), "configs"))
shutil.copy(os.path.join(src_path, file), os.path.join(dst_path, file)) for filename in os.listdir(resource_path):
except OSError: if not os.path.exists(os.path.join(dst_path, filename)):
pass 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): def images_path(self):
""" """

View File

@ -15,10 +15,13 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys
import os import os
import json import json
import asyncio import asyncio
import aiofiles import aiofiles
import importlib_resources
import shutil
from typing import Tuple, List from typing import Tuple, List
from aiohttp.client_exceptions import ClientError from aiohttp.client_exceptions import ClientError
@ -29,7 +32,6 @@ from pydantic import ValidationError
from .appliance import Appliance from .appliance import Appliance
from ..config import Config from ..config import Config
from ..utils.asyncio import locking from ..utils.asyncio import locking
from ..utils.get_resource import get_resource
from ..utils.http_client import HTTPClient from ..utils.http_client import HTTPClient
from .controller_error import ControllerBadRequestError, ControllerNotFoundError, ControllerError from .controller_error import ControllerBadRequestError, ControllerNotFoundError, ControllerError
from .appliance_to_template import ApplianceToTemplate from .appliance_to_template import ApplianceToTemplate
@ -82,9 +84,9 @@ class ApplianceManager:
return self._appliances 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 server_config = Config.instance().settings.Server
@ -92,6 +94,38 @@ class ApplianceManager:
os.makedirs(appliances_path, exist_ok=True) os.makedirs(appliances_path, exist_ok=True)
return appliances_path 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]]: def _find_appliances_from_image_checksum(self, image_checksum: str) -> List[Tuple[Appliance, str]]:
""" """
Find appliances that matches an image checksum. Find appliances that matches an image checksum.
@ -289,11 +323,11 @@ class ApplianceManager:
self._appliances = {} self._appliances = {}
for directory, builtin in ( for directory, builtin in (
( (
get_resource("appliances"), self._builtin_appliances_path(),
True, True,
), ),
( (
self.appliances_path(), self._custom_appliances_path(),
False, False,
), ),
): ):
@ -407,7 +441,7 @@ class ApplianceManager:
Controller.instance().save() Controller.instance().save()
json_data = await response.json() json_data = await response.json()
appliances_dir = get_resource("appliances") appliances_dir = self._builtin_appliances_path()
downloaded_appliance_files = [] downloaded_appliance_files = []
for appliance in json_data: for appliance in json_data:
if appliance["type"] == "file": if appliance["type"] == "file":

View File

@ -95,8 +95,9 @@ class CrashReport:
"os:name": platform.system(), "os:name": platform.system(),
"os:release": platform.release(), "os:release": platform.release(),
"os:win_32": " ".join(platform.win32_ver()), "os:win_32": " ".join(platform.win32_ver()),
"os:mac": f"{platform.mac_ver()[0]} {platform.mac_ver()[2]}", "os:mac": "{} {}".format(platform.mac_ver()[0], platform.mac_ver()[2]),
"os:linux": " ".join(distro.linux_distribution()), "os:linux": distro.name(pretty=True),
} }
with sentry_sdk.configure_scope() as scope: with sentry_sdk.configure_scope() as scope:

View File

@ -15,6 +15,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys
import asyncio import asyncio
@ -39,7 +40,11 @@ class Pool:
while len(self._tasks) > 0 or len(pending) > 0: while len(self._tasks) > 0 or len(pending) > 0:
while len(self._tasks) > 0 and len(pending) < self._concurrency: while len(self._tasks) > 0 and len(pending) < self._concurrency:
task, args, kwargs = self._tasks.pop(0) 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) (done, pending) = await asyncio.wait(pending, return_when=asyncio.FIRST_COMPLETED)
for task in done: for task in done:
if task.exception(): if task.exception():

View File

@ -14,34 +14,18 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import tempfile
import pkg_resources
import atexit import atexit
import logging import logging
import os import os
import sys import sys
import importlib_resources
from contextlib import ExitStack
resource_manager = ExitStack()
atexit.register(resource_manager.close)
log = logging.getLogger(__name__) 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): def get_resource(resource_name):
""" """
@ -51,7 +35,9 @@ def get_resource(resource_name):
resource_path = None resource_path = None
if hasattr(sys, "frozen"): if hasattr(sys, "frozen"):
resource_path = os.path.normpath(os.path.join(os.path.dirname(sys.executable), resource_name)) 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): else:
resource_path = pkg_resources.resource_filename("gns3server", resource_name) ref = importlib_resources.files("gns3server") / resource_name
resource_path = os.path.normpath(resource_path) path = resource_manager.enter_context(importlib_resources.as_file(ref))
if os.path.exists(path):
resource_path = os.path.normpath(path)
return resource_path return resource_path

View File

@ -22,7 +22,7 @@
# or negative for a release candidate or beta (after the base version # or negative for a release candidate or beta (after the base version
# number has been incremented) # number has been incremented)
__version__ = "3.0.0dev5" __version__ = "3.0.0.dev6"
__version_info__ = (3, 0, 0, 99) __version_info__ = (3, 0, 0, 99)
if "dev" in __version__: if "dev" in __version__:
@ -30,7 +30,7 @@ if "dev" in __version__:
import os import os
import subprocess import subprocess
if os.path.exists(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", ".git")): 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() r = subprocess.check_output(["git", "rev-parse", "--short", "HEAD"]).decode().strip("\n")
__version__ = f"{__version__}-{r}" __version__ += "+" + r
except Exception as e: except Exception as e:
print(e) print(e)

View File

@ -23,8 +23,8 @@ depend() {
checkconfig() { checkconfig() {
if yesno "${GNS3_SERVER_LOG_ENABLED}" ; then 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 if [ "${command_user}" ] ; then
checkpath --directory --mode 0700 --owner "${command_user}" "${GNS3_SERVER_LOG_PATH}"; checkpath --directory --mode 0700 --owner "${command_user}" "${GNS3_SERVER_LOG_PATH}";
else else
unset command_user unset command_user

View File

@ -6,7 +6,7 @@ After=network.target network-online.target
[Service] [Service]
User=gns3 User=gns3
Group=gns3 Group=gns3
ExecStart=/usr/bin/gns3server ExecStart=/usr/local/bin/gns3server
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target

View File

@ -2,13 +2,13 @@ uvicorn==0.18.3
fastapi==0.85.0 fastapi==0.85.0
python-multipart==0.0.5 python-multipart==0.0.5
websockets==10.3 websockets==10.3
aiohttp==3.8.1 aiohttp>=3.8.3,<3.9
async-timeout==4.0.2 async-timeout==4.0.2
aiofiles==0.8.0 aiofiles==0.8.0
Jinja2==3.1.2 Jinja2>=3.1.2,<3.2
sentry-sdk==1.9.5 sentry-sdk==1.10.1,<1.11
psutil==5.9.1 psutil==5.9.2
distro==1.7.0 distro>=1.7.0
py-cpuinfo==8.0.0 py-cpuinfo==8.0.0
sqlalchemy==1.4.40 sqlalchemy==1.4.40
aiosqlite==0.17.0 aiosqlite==0.17.0
@ -17,4 +17,4 @@ python-jose==3.3.0
email-validator==1.2.1 email-validator==1.2.1
watchfiles==0.16.1 watchfiles==0.16.1
zstandard==0.18.0 zstandard==0.18.0
setuptools==60.6.0 # don't upgrade because of https://github.com/pypa/setuptools/issues/3084 setuptools>=60.8.1

View File

@ -105,6 +105,7 @@ setup(
"Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: CPython",
], ],
) )

View File

@ -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: with open(str(tmpdir / 'iou_l2_base_startup-config.txt'), 'w+') as f:
f.write('test') f.write('test')
controller.load_base_files() controller._load_base_files()
assert os.path.exists(str(tmpdir / 'iou_l3_base_startup-config.txt')) 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: with open(str(tmpdir / 'iou_l2_base_startup-config.txt')) as f:
assert f.read() == 'test' assert f.read() == 'test'

View File

@ -1,4 +1,4 @@
-r requirements.txt -r requirements.txt
pywin32==304 pywin32==305
wmi==1.5.1 wmi==1.5.1