diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml
index 36039595..161878b9 100644
--- a/.github/workflows/testing.yml
+++ b/.github/workflows/testing.yml
@@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"]
+ python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11"]
steps:
- uses: actions/checkout@v2
diff --git a/CHANGELOG b/CHANGELOG
index bf59e33f..19d82820 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,20 @@
# 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
+
## 2.2.34 28/08/2022
* Use original $PATH in init.sh for Docker containers. Ref #2069
diff --git a/conf/gns3_server.conf b/conf/gns3_server.conf
index 9f9e02dc..013ba7dd 100644
--- a/conf/gns3_server.conf
+++ b/conf/gns3_server.conf
@@ -90,3 +90,9 @@ require_kvm = True
enable_hardware_acceleration = True
; Require hardware acceleration in order to start VMs (all platforms)
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
diff --git a/dev-requirements.txt b/dev-requirements.txt
index 86cc066f..8b0b9a2b 100644
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -1,8 +1,8 @@
-rrequirements.txt
-pytest==7.0.1; python_version < '3.7' # last version to support Python 3.6
-pytest==7.1.2; python_version >= '3.7'
+pytest==7.2.0; python_version >= '3.7'
+pytest==7.0.1; python_version < '3.7' # v7.0.1 is the last version to support Python 3.6
flake8==5.0.4
pytest-timeout==2.1.0
-pytest-aiohttp==0.3.0; python_version < '3.7' # last version to support Python 3.6
pytest-aiohttp==1.0.4; python_version >= '3.7'
+pytest-aiohttp==0.3.0; python_version < '3.7' # last version to support Python 3.6
diff --git a/gns3server/appliances/alpine-linux-virt.gns3a b/gns3server/appliances/alpine-linux-virt.gns3a
new file mode 100644
index 00000000..d7c2883d
--- /dev/null
+++ b/gns3server/appliances/alpine-linux-virt.gns3a
@@ -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"
+ }
+ }
+ ]
+}
diff --git a/gns3server/appliances/aruba-arubaoscx.gns3a b/gns3server/appliances/aruba-arubaoscx.gns3a
index db45527b..39afe6a6 100644
--- a/gns3server/appliances/aruba-arubaoscx.gns3a
+++ b/gns3server/appliances/aruba-arubaoscx.gns3a
@@ -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": {
diff --git a/gns3server/appliances/debian.gns3a b/gns3server/appliances/debian.gns3a
index 4b98cc20..a4d33d93 100644
--- a/gns3server/appliances/debian.gns3a
+++ b/gns3server/appliances/debian.gns3a
@@ -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-20220911-1135.qcow2",
+ "version": "11.5",
+ "md5sum": "06e481ddd23682af4326226661c13d8f",
+ "filesize": 254672896,
"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",
- "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.5",
"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"
}
},
{
- "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"
}
}
diff --git a/gns3server/appliances/openwrt.gns3a b/gns3server/appliances/openwrt.gns3a
index 2a293c62..a8bc8bf7 100644
--- a/gns3server/appliances/openwrt.gns3a
+++ b/gns3server/appliances/openwrt.gns3a
@@ -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": {
diff --git a/gns3server/appliances/reactos.gns3a b/gns3server/appliances/reactos.gns3a
new file mode 100644
index 00000000..391abe95
--- /dev/null
+++ b/gns3server/appliances/reactos.gns3a
@@ -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"
+ }
+ }
+ ]
+}
diff --git a/gns3server/appliances/vyos.gns3a b/gns3server/appliances/vyos.gns3a
index 4004a1bc..540bd1ed 100644
--- a/gns3server/appliances/vyos.gns3a
+++ b/gns3server/appliances/vyos.gns3a
@@ -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",
@@ -40,14 +54,6 @@
"filesize": 338690048,
"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",
"version": "1.2.8",
@@ -80,6 +86,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": {
@@ -94,13 +114,6 @@
"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",
"images": {
diff --git a/gns3server/compute/base_node.py b/gns3server/compute/base_node.py
index f07ed957..a7b244cd 100644
--- a/gns3server/compute/base_node.py
+++ b/gns3server/compute/base_node.py
@@ -578,8 +578,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)
diff --git a/gns3server/compute/vmware/__init__.py b/gns3server/compute/vmware/__init__.py
index 36121fb0..3d836923 100644
--- a/gns3server/compute/vmware/__init__.py
+++ b/gns3server/compute/vmware/__init__.py
@@ -360,6 +360,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()
@@ -368,6 +370,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:
@@ -378,6 +381,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
diff --git a/gns3server/compute/vmware/vmware_vm.py b/gns3server/compute/vmware/vmware_vm.py
index db8c6b87..52ebcea3 100644
--- a/gns3server/compute/vmware/vmware_vm.py
+++ b/gns3server/compute/vmware/vmware_vm.py
@@ -465,6 +465,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:
diff --git a/gns3server/controller/__init__.py b/gns3server/controller/__init__.py
index e000ea06..5c5236b9 100644
--- a/gns3server/controller/__init__.py
+++ b/gns3server/controller/__init__.py
@@ -22,6 +22,7 @@ import uuid
import socket
import shutil
import aiohttp
+import importlib_resources
from ..config import Config
from .project import Project
@@ -35,7 +36,6 @@ from .symbols import Symbols
from ..version import __version__
from .topology import load_topology
from .gns3vm import GNS3VM
-from ..utils.get_resource import get_resource
from .gns3vm.gns3_vm_error import GNS3VMError
import logging
@@ -65,7 +65,7 @@ class Controller:
async def start(self):
log.info("Controller is starting")
- self.load_base_files()
+ self._load_base_files()
server_config = Config.instance().get_section_config("Server")
Config.instance().listen_for_config_changes(self._update_config)
host = server_config.get("host", "localhost")
@@ -242,6 +242,7 @@ class Controller:
if "iou_license" in controller_settings:
self._iou_license_settings = controller_settings["iou_license"]
+ self._appliance_manager.install_builtin_appliances()
self._appliance_manager.appliances_etag = controller_settings.get("appliances_etag")
self._appliance_manager.load_appliances()
self._template_manager.load_templates(controller_settings.get("templates"))
@@ -269,20 +270,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):
"""
diff --git a/gns3server/controller/appliance_manager.py b/gns3server/controller/appliance_manager.py
index cebcad58..d15fc69b 100644
--- a/gns3server/controller/appliance_manager.py
+++ b/gns3server/controller/appliance_manager.py
@@ -15,17 +15,18 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+import sys
import os
-import shutil
import json
import uuid
import asyncio
import aiohttp
+import importlib_resources
+import shutil
from .appliance import Appliance
from ..config import Config
from ..utils.asyncio import locking
-from ..utils.get_resource import get_resource
import logging
log = logging.getLogger(__name__)
@@ -65,9 +66,9 @@ class ApplianceManager:
return self._appliances
- def appliances_path(self):
+ def _custom_appliances_path(self):
"""
- Get the image storage directory
+ Get the custom appliance storage directory
"""
server_config = Config.instance().get_section_config("Server")
@@ -75,13 +76,44 @@ 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 load_appliances(self, symbol_theme="Classic"):
"""
Loads appliance files from disk.
"""
self._appliances = {}
- for directory, builtin in ((get_resource('appliances'), True,), (self.appliances_path(), False,)):
+ for directory, builtin in ((self._builtin_appliances_path(), True,), (self._custom_appliances_path(), False,)):
if directory and os.path.isdir(directory):
for file in os.listdir(directory):
if not file.endswith('.gns3a') and not file.endswith('.gns3appliance'):
@@ -181,7 +213,7 @@ class ApplianceManager:
from . import Controller
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":
diff --git a/gns3server/crash_report.py b/gns3server/crash_report.py
index 31d4b37e..36a75f16 100644
--- a/gns3server/crash_report.py
+++ b/gns3server/crash_report.py
@@ -58,7 +58,7 @@ class CrashReport:
Report crash to a third party service
"""
- DSN = "https://3f074423e7664595923ae5895b0819c4@o19455.ingest.sentry.io/38482"
+ DSN = "https://67695b9b3995495d9445159d973d2f0d@o19455.ingest.sentry.io/38482"
_instance = None
def __init__(self):
@@ -93,7 +93,7 @@ class CrashReport:
"os:release": platform.release(),
"os:win_32": " ".join(platform.win32_ver()),
"os:mac": "{} {}".format(platform.mac_ver()[0], platform.mac_ver()[2]),
- "os:linux": " ".join(distro.linux_distribution()),
+ "os:linux": distro.name(pretty=True),
}
diff --git a/gns3server/utils/asyncio/pool.py b/gns3server/utils/asyncio/pool.py
index 0015e29c..22750ead 100644
--- a/gns3server/utils/asyncio/pool.py
+++ b/gns3server/utils/asyncio/pool.py
@@ -15,6 +15,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+import sys
import 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():
diff --git a/gns3server/utils/get_resource.py b/gns3server/utils/get_resource.py
index b988a200..b4b599bd 100644
--- a/gns3server/utils/get_resource.py
+++ b/gns3server/utils/get_resource.py
@@ -15,33 +15,18 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-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 +36,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
diff --git a/gns3server/version.py b/gns3server/version.py
index bdef617c..4f912448 100644
--- a/gns3server/version.py
+++ b/gns3server/version.py
@@ -23,15 +23,15 @@
# or negative for a release candidate or beta (after the base version
# number has been incremented)
-__version__ = "2.2.34"
-__version_info__ = (2, 2, 34, 0)
+__version__ = "2.2.35"
+__version_info__ = (2, 2, 35, 0)
if "dev" in __version__:
try:
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__ = "{}-{}".format(__version__, r)
+ r = subprocess.check_output(["git", "rev-parse", "--short", "HEAD"]).decode().strip("\n")
+ __version__ += "+" + r
except Exception as e:
print(e)
diff --git a/gns3server/web/route.py b/gns3server/web/route.py
index e788a32c..115d209c 100644
--- a/gns3server/web/route.py
+++ b/gns3server/web/route.py
@@ -164,8 +164,6 @@ class Route(object):
"description": kw.get("description", ""),
})
- func = asyncio.coroutine(func)
-
async def control_schema(request):
# This block is executed at each method call
server_config = Config.instance().get_section_config("Server")
@@ -181,7 +179,10 @@ class Route(object):
response = Response(request=request, route=route, output_schema=output_schema)
request = await parse_request(request, None, raw)
- await func(request, response)
+ if asyncio.iscoroutinefunction(func):
+ await func(request, response)
+ else:
+ func(request, response)
return response
# API call
@@ -195,7 +196,10 @@ class Route(object):
except OSError as e:
log.warning("Could not write to the record file {}: {}".format(record_file, e))
response = Response(request=request, route=route, output_schema=output_schema)
- await func(request, response)
+ if asyncio.iscoroutinefunction(func):
+ await func(request, response)
+ else:
+ func(request, response)
except aiohttp.web.HTTPBadRequest as e:
response = Response(request=request, route=route)
response.set_status(e.status)
diff --git a/requirements.txt b/requirements.txt
index 4703e6dc..53f1200d 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,12 +1,16 @@
-jsonschema==3.2.0
-aiohttp==3.8.1
-aiohttp-cors==0.7.0
-aiofiles==0.8.0
-Jinja2==3.0.3
-sentry-sdk==1.9.5
-psutil==5.9.1
-async-timeout==4.0.2
-distro==1.7.0
-py-cpuinfo==8.0.0
-setuptools==60.6.0; python_version >= '3.7' # don't upgrade because of https://github.com/pypa/setuptools/issues/3084
-setuptools==59.6.0; python_version < '3.7' # v59.7.0 dropped support for Python 3.6
+jsonschema>=4.17.0,<4.18; python_version >= '3.7'
+jsonschema==3.2.0; python_version < '3.7' # v3.2.0 is the last version to support Python 3.6
+aiohttp>=3.8.3,<3.9
+aiohttp-cors>=0.7.0,<0.8
+aiofiles>=22.1.0,<22.2; python_version >= '3.7'
+aiofiles==0.8.0; python_version < '3.7' # v0.8.0 is the last version to support Python 3.6
+Jinja2>=3.1.2,<3.2; python_version >= '3.7'
+Jinja2==3.0.3; python_version < '3.7' # v3.0.3 is the last version to support Python 3.6
+sentry-sdk==1.10.1,<1.11
+psutil==5.9.2
+async-timeout>=4.0.2,<4.1
+distro>=1.7.0
+py-cpuinfo>=9.0.0,<10.0
+importlib-resources>=1.3
+setuptools>=60.8.1; python_version >= '3.7'
+setuptools==59.6.0; python_version < '3.7' # v59.6.0 is the last version to support Python 3.6
diff --git a/setup.py b/setup.py
index caf8f125..17cbbb42 100644
--- a/setup.py
+++ b/setup.py
@@ -81,6 +81,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",
],
)
diff --git a/tests/controller/test_controller.py b/tests/controller/test_controller.py
index 48b8d98d..48ba98af 100644
--- a/tests/controller/test_controller.py
+++ b/tests/controller/test_controller.py
@@ -387,10 +387,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'
diff --git a/tests/handlers/api/compute/test_qemu.py b/tests/handlers/api/compute/test_qemu.py
index 00245f25..95611f10 100644
--- a/tests/handlers/api/compute/test_qemu.py
+++ b/tests/handlers/api/compute/test_qemu.py
@@ -185,12 +185,14 @@ async def test_qemu_update(compute_api, vm, free_console_port, fake_qemu_vm):
"hdb_disk_image": "linux载.img"
}
- response = await compute_api.put("/projects/{project_id}/qemu/nodes/{node_id}".format(project_id=vm["project_id"], node_id=vm["node_id"]), params)
- assert response.status == 200
- assert response.json["name"] == "test"
- assert response.json["console"] == free_console_port
- assert response.json["hdb_disk_image"] == "linux载.img"
- assert response.json["ram"] == 1024
+ with patch("gns3server.compute.qemu.qemu_vm.QemuVM.updated") as mock:
+ response = await compute_api.put("/projects/{project_id}/qemu/nodes/{node_id}".format(project_id=vm["project_id"], node_id=vm["node_id"]), params)
+ assert response.status == 200
+ assert response.json["name"] == "test"
+ assert response.json["console"] == free_console_port
+ assert response.json["hdb_disk_image"] == "linux载.img"
+ assert response.json["ram"] == 1024
+ assert mock.called
async def test_qemu_nio_create_udp(compute_api, vm):
diff --git a/win-requirements.txt b/win-requirements.txt
index 5c75f1f1..b2453b87 100644
--- a/win-requirements.txt
+++ b/win-requirements.txt
@@ -1,4 +1,4 @@
-rrequirements.txt
-pywin32==303
+pywin32==305
wmi==1.5.1