diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 1399dac6..3ce91705 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -17,7 +17,7 @@ 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 diff --git a/CHANGELOG b/CHANGELOG index 84b1d639..d6e88ede 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 + ## 3.0.0a2 06/09/2022 * Add web-ui v3.0.0a2 diff --git a/conf/gns3_server.conf b/conf/gns3_server.conf index d1143eb4..249e255a 100644 --- a/conf/gns3_server.conf +++ b/conf/gns3_server.conf @@ -133,3 +133,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 diff --git a/dev-requirements.txt b/dev-requirements.txt index 6f964e26..6d9c5fe3 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,6 +1,6 @@ -r requirements.txt -pytest==7.1.2 +pytest==7.2.0 flake8==5.0.4 pytest-timeout==2.1.0 pytest-asyncio==0.19.0 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 fcc31893..1ae0bb57 100644 --- a/gns3server/compute/base_node.py +++ b/gns3server/compute/base_node.py @@ -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) diff --git a/gns3server/compute/vmware/__init__.py b/gns3server/compute/vmware/__init__.py index 606e8a03..f19e3661 100644 --- a/gns3server/compute/vmware/__init__.py +++ b/gns3server/compute/vmware/__init__.py @@ -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 diff --git a/gns3server/compute/vmware/vmware_vm.py b/gns3server/compute/vmware/vmware_vm.py index 1c7c3861..62a03aa9 100644 --- a/gns3server/compute/vmware/vmware_vm.py +++ b/gns3server/compute/vmware/vmware_vm.py @@ -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: diff --git a/gns3server/controller/__init__.py b/gns3server/controller/__init__.py index cb5d874e..113e712e 100644 --- a/gns3server/controller/__init__.py +++ b/gns3server/controller/__init__.py @@ -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,7 +62,7 @@ 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) host = server_config.host @@ -281,6 +281,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 +306,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 85533106..2ca563c0 100644 --- a/gns3server/controller/appliance_manager.py +++ b/gns3server/controller/appliance_manager.py @@ -15,10 +15,13 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +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,38 @@ 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 +323,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 +441,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": diff --git a/gns3server/crash_report.py b/gns3server/crash_report.py index 53dc218e..7e6b93f8 100644 --- a/gns3server/crash_report.py +++ b/gns3server/crash_report.py @@ -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: diff --git a/gns3server/utils/asyncio/pool.py b/gns3server/utils/asyncio/pool.py index d2993553..eba0ce73 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 ac624c05..4ec038eb 100644 --- a/gns3server/utils/get_resource.py +++ b/gns3server/utils/get_resource.py @@ -14,34 +14,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 +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 diff --git a/gns3server/version.py b/gns3server/version.py index 5493addd..99305487 100644 --- a/gns3server/version.py +++ b/gns3server/version.py @@ -22,7 +22,7 @@ # or negative for a release candidate or beta (after the base version # number has been incremented) -__version__ = "3.0.0dev5" +__version__ = "3.0.0.dev6" __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) diff --git a/init/gns3.service.openrc b/init/gns3.service.openrc index 8b0864cd..5a574fca 100755 --- a/init/gns3.service.openrc +++ b/init/gns3.service.openrc @@ -23,8 +23,8 @@ depend() { checkconfig() { if yesno "${GNS3_SERVER_LOG_ENABLED}" ; then - command_args+=" --log ${GNS3_SERVER_LOG}"; - if [ "${command_user}" ] ; then + command_args="${command_args} --log ${GNS3_SERVER_LOG}"; + if [ "${command_user}" ] ; then checkpath --directory --mode 0700 --owner "${command_user}" "${GNS3_SERVER_LOG_PATH}"; else unset command_user diff --git a/init/gns3.service.systemd b/init/gns3.service.systemd index ce504ee1..cff5cb4d 100644 --- a/init/gns3.service.systemd +++ b/init/gns3.service.systemd @@ -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 \ No newline at end of file +WantedBy=multi-user.target diff --git a/requirements.txt b/requirements.txt index f90ede6b..c10dc9d4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,13 +2,13 @@ uvicorn==0.18.3 fastapi==0.85.0 python-multipart==0.0.5 websockets==10.3 -aiohttp==3.8.1 +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 +Jinja2>=3.1.2,<3.2 +sentry-sdk==1.10.1,<1.11 +psutil==5.9.2 +distro>=1.7.0 py-cpuinfo==8.0.0 sqlalchemy==1.4.40 aiosqlite==0.17.0 @@ -17,4 +17,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 +setuptools>=60.8.1 diff --git a/setup.py b/setup.py index 15d695c5..06a71f98 100644 --- a/setup.py +++ b/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", ], ) diff --git a/tests/controller/test_controller.py b/tests/controller/test_controller.py index 28c77709..ab62adb9 100644 --- a/tests/controller/test_controller.py +++ b/tests/controller/test_controller.py @@ -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' diff --git a/win-requirements.txt b/win-requirements.txt index cac623c0..c3ccc650 100644 --- a/win-requirements.txt +++ b/win-requirements.txt @@ -1,4 +1,4 @@ -r requirements.txt -pywin32==304 +pywin32==305 wmi==1.5.1