Merge pull request #2440 from GNS3/release/v2.2.51

release/v2.2.51
This commit is contained in:
Jeremy Grossmann 2024-11-07 23:13:03 +10:00 committed by GitHub
commit 11a9451098
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 128 additions and 60 deletions

View File

@ -18,7 +18,7 @@ jobs:
strategy: strategy:
matrix: matrix:
os: ["ubuntu-latest"] os: ["ubuntu-latest"]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
#include: #include:
# only test with Python 3.10 on Windows # only test with Python 3.10 on Windows
# - os: windows-latest # - os: windows-latest

View File

@ -1,5 +1,15 @@
# Change Log # Change Log
## 2.2.51 07/11/2024
* Catch error when cannot resize Docker container TTY.
* Do not use "ide" if there is a disk image and no interface type has been explicitly configured.
* Use @locking when sending uBridge commands. Ref https://github.com/GNS3/gns3-gui/issues/3651
* Fix run Docker containers with user namespaces enabled. Fixes #2414
* Python 3.13 support
* Upgrade dependencies
* Fix errors in init.sh. Fixes #2431
## 2.2.50 21/10/2024 ## 2.2.50 21/10/2024
* Bundle web-ui v2.2.50 * Bundle web-ui v2.2.50

View File

@ -0,0 +1,52 @@
{
"appliance_id": "00714342-14b2-4281-aa20-9043ca8dc26e",
"name": "NixOS",
"category": "guest",
"description": "NixOS QEMU Appliance for images created with nixos-generator. Automatically sets hostname based on vmname.",
"vendor_name": "NixOS",
"vendor_url": "https://nixos.org/",
"vendor_logo_url": "https://avatars.githubusercontent.com/u/487568",
"documentation_url": "https://github.com/ob7/gns3-nixos-appliance",
"product_name": "NixOS",
"product_url": "https://github.com/NixOS/nixpkgs",
"registry_version": 4,
"status": "experimental",
"availability": "free",
"maintainer": "ob7dev",
"maintainer_email": "dev@ob7.us",
"usage": "For custom NixOS images, create qcow2 VM with: nixos-generate -f qcow -c ./server.nix Import it into GNS3 as image. VM name is passed into QEMU guest with Advanced Options field entry: -fw_cfg name=opt/vm_hostname,string=%vm-name%",
"symbol": ":/symbols/affinity/circle/gray/template.svg",
"first_port_name": "eth0",
"port_name_format": "eth{0}",
"qemu": {
"adapter_type": "e1000",
"adapters": 4,
"ram": 256,
"cpus": 1,
"hda_disk_interface": "ide",
"arch": "x86_64",
"console_type": "telnet",
"kvm": "allow",
"options": "-fw_cfg name=opt/vm_hostname,string=%vm-name%",
"on_close": "power_off"
},
"images": [
{
"filename": "nixos-24-11.qcow2",
"version": "24.11",
"md5sum": "2459f05136836dd430402d75cba0f205",
"download_url": "https://github.com/nix-community/nixos-generators",
"filesize": 1749483520,
"download_url": "https://f.ob7.us/gns3/",
"direct_download_url": "http://ob7.us/nixos-24-11.qcow2"
}
],
"versions": [
{
"name": "24.11",
"images": {
"hda_disk_image": "nixos-24-11.qcow2"
}
}
]
}

View File

@ -406,13 +406,11 @@ class BaseNode:
Stops the telnet proxy. Stops the telnet proxy.
""" """
if self._wrapper_telnet_server: if self._wrap_console_writer:
self._wrap_console_writer.close() self._wrap_console_writer.close()
if sys.version_info >= (3, 7, 0):
try:
await self._wrap_console_writer.wait_closed() await self._wrap_console_writer.wait_closed()
except ConnectionResetError: self._wrap_console_writer = None
pass if self._wrapper_telnet_server:
self._wrapper_telnet_server.close() self._wrapper_telnet_server.close()
await self._wrapper_telnet_server.wait_closed() await self._wrapper_telnet_server.wait_closed()
self._wrapper_telnet_server = None self._wrapper_telnet_server = None
@ -662,6 +660,7 @@ class BaseNode:
path = shutil.which(path) path = shutil.which(path)
return path return path
@locking
async def _ubridge_send(self, command): async def _ubridge_send(self, command):
""" """
Sends a command to uBridge hypervisor. Sends a command to uBridge hypervisor.

View File

@ -383,8 +383,8 @@ class DockerVM(BaseNode):
"CapAdd": ["ALL"], "CapAdd": ["ALL"],
"Privileged": True, "Privileged": True,
"Binds": self._mount_binds(image_infos), "Binds": self._mount_binds(image_infos),
},
"UsernsMode": "host", "UsernsMode": "host",
},
"Volumes": {}, "Volumes": {},
"Env": ["container=docker"], # Systemd compliant: https://github.com/GNS3/gns3-server/issues/573 "Env": ["container=docker"], # Systemd compliant: https://github.com/GNS3/gns3-server/issues/573
"Cmd": [], "Cmd": [],
@ -710,7 +710,10 @@ class DockerVM(BaseNode):
""" """
# resize the container TTY. # resize the container TTY.
try:
await self._manager.query("POST", "containers/{}/resize?h={}&w={}".format(self._cid, rows, columns)) await self._manager.query("POST", "containers/{}/resize?h={}&w={}".format(self._cid, rows, columns))
except DockerError as e:
log.warning(f"Could not resize the container TTY: {e}")
async def _start_console(self): async def _start_console(self):
""" """

View File

@ -25,7 +25,7 @@ PATH=/gns3/bin:/tmp/gns3/bin:/sbin:$PATH
# bootstrap busybox commands # bootstrap busybox commands
if [ ! -d /tmp/gns3/bin ]; then if [ ! -d /tmp/gns3/bin ]; then
busybox mkdir -p /tmp/gns3/bin busybox mkdir -p /tmp/gns3/bin
for applet in `busybox --list-full` for applet in `busybox --list`
do do
ln -s /gns3/bin/busybox "/tmp/gns3/bin/$applet" ln -s /gns3/bin/busybox "/tmp/gns3/bin/$applet"
done done

View File

@ -1915,11 +1915,6 @@ class QemuVM(BaseNode):
continue continue
interface = getattr(self, "hd{}_disk_interface".format(drive)) interface = getattr(self, "hd{}_disk_interface".format(drive))
# fail-safe: use "ide" if there is a disk image and no interface type has been explicitly configured
if interface == "none":
interface = "ide"
setattr(self, "hd{}_disk_interface".format(drive), interface)
disk_name = "hd" + drive disk_name = "hd" + drive
if not os.path.isfile(disk_image) or not os.path.exists(disk_image): if not os.path.isfile(disk_image) or not os.path.exists(disk_image):
if os.path.islink(disk_image): if os.path.islink(disk_image):

View File

@ -57,7 +57,7 @@ class CrashReport:
Report crash to a third party service Report crash to a third party service
""" """
DSN = "https://decb06a5d7bda07bef342a41c5c9d6e1@o19455.ingest.us.sentry.io/38482" DSN = "https://088679fcf3aa35f775356982a80fe37c@o19455.ingest.us.sentry.io/38482"
_instance = None _instance = None
def __init__(self): def __init__(self):

View File

@ -23,8 +23,8 @@
# 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__ = "2.2.50" __version__ = "2.2.51"
__version_info__ = (2, 2, 50, 0) __version_info__ = (2, 2, 51, 0)
if "dev" in __version__: if "dev" in __version__:
try: try:

View File

@ -1,13 +1,13 @@
jsonschema>=4.23,<4.24 jsonschema>=4.23,<4.24
aiohttp>=3.10.3,<3.11 aiohttp>=3.10.10,<3.11
aiohttp-cors>=0.7.0,<0.8 aiohttp-cors>=0.7.0,<0.8
aiofiles>=24.1.0,<25.0 aiofiles>=24.1.0,<25.0
Jinja2>=3.1.4,<3.2 Jinja2>=3.1.4,<3.2
sentry-sdk==2.12,<2.13 # optional dependency sentry-sdk>=2.17,<2.18 # optional dependency
psutil==6.0.0 psutil>=6.1.0
async-timeout>=4.0.3,<4.1 async-timeout>=4.0.3,<4.1
distro>=1.9.0 distro>=1.9.0
py-cpuinfo>=9.0.0,<10.0 py-cpuinfo>=9.0.0,<10.0
platformdirs>=2.4.0 platformdirs>=2.4.0
importlib-resources>=1.3; python_version < '3.9' importlib-resources>=1.3; python_version < '3.9'
truststore>=0.9.1; python_version >= '3.10' truststore>=0.10.0; python_version >= '3.10'

View File

@ -16,9 +16,6 @@
# 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 sys
import os
import shutil
import subprocess
from setuptools import setup, find_packages from setuptools import setup, find_packages
from setuptools.command.test import test as TestCommand from setuptools.command.test import test as TestCommand
@ -67,8 +64,8 @@ setup(
include_package_data=True, include_package_data=True,
zip_safe=False, zip_safe=False,
platforms="any", platforms="any",
python_requires='>=3.8', python_requires=">=3.8",
setup_requires=["setuptools>=17.1"], setup_requires=["setuptools>=61.0"],
classifiers=[ classifiers=[
"Development Status :: 5 - Production/Stable", "Development Status :: 5 - Production/Stable",
"Environment :: Console", "Environment :: Console",
@ -86,6 +83,7 @@ setup(
"Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: CPython",
], ],
) )

View File

@ -107,9 +107,9 @@ async def test_create(compute_project, manager):
"{}:/gns3:ro".format(Docker.resources_path()), "{}:/gns3:ro".format(Docker.resources_path()),
"{}:/gns3volumes/etc/network".format(os.path.join(vm.working_dir, "etc", "network")) "{}:/gns3volumes/etc/network".format(os.path.join(vm.working_dir, "etc", "network"))
], ],
"Privileged": True "Privileged": True,
"UsernsMode": "host"
}, },
"UsernsMode": "host",
"Volumes": {}, "Volumes": {},
"NetworkDisabled": True, "NetworkDisabled": True,
"Hostname": "test", "Hostname": "test",
@ -146,9 +146,9 @@ async def test_create_with_tag(compute_project, manager):
"{}:/gns3:ro".format(Docker.resources_path()), "{}:/gns3:ro".format(Docker.resources_path()),
"{}:/gns3volumes/etc/network".format(os.path.join(vm.working_dir, "etc", "network")) "{}:/gns3volumes/etc/network".format(os.path.join(vm.working_dir, "etc", "network"))
], ],
"Privileged": True "Privileged": True,
"UsernsMode": "host"
}, },
"UsernsMode": "host",
"Volumes": {}, "Volumes": {},
"NetworkDisabled": True, "NetworkDisabled": True,
"Hostname": "test", "Hostname": "test",
@ -189,9 +189,9 @@ async def test_create_vnc(compute_project, manager):
"{}:/gns3volumes/etc/network".format(os.path.join(vm.working_dir, "etc", "network")), "{}:/gns3volumes/etc/network".format(os.path.join(vm.working_dir, "etc", "network")),
"/tmp/.X11-unix/X{0}:/tmp/.X11-unix/X{0}:ro".format(vm._display) "/tmp/.X11-unix/X{0}:/tmp/.X11-unix/X{0}:ro".format(vm._display)
], ],
"Privileged": True "Privileged": True,
"UsernsMode": "host"
}, },
"UsernsMode": "host",
"Volumes": {}, "Volumes": {},
"NetworkDisabled": True, "NetworkDisabled": True,
"Hostname": "test", "Hostname": "test",
@ -319,9 +319,9 @@ async def test_create_start_cmd(compute_project, manager):
"{}:/gns3:ro".format(Docker.resources_path()), "{}:/gns3:ro".format(Docker.resources_path()),
"{}:/gns3volumes/etc/network".format(os.path.join(vm.working_dir, "etc", "network")) "{}:/gns3volumes/etc/network".format(os.path.join(vm.working_dir, "etc", "network"))
], ],
"Privileged": True "Privileged": True,
"UsernsMode": "host"
}, },
"UsernsMode": "host",
"Volumes": {}, "Volumes": {},
"Entrypoint": ["/gns3/init.sh"], "Entrypoint": ["/gns3/init.sh"],
"Cmd": ["/bin/ls"], "Cmd": ["/bin/ls"],
@ -418,9 +418,9 @@ async def test_create_image_not_available(compute_project, manager):
"{}:/gns3:ro".format(Docker.resources_path()), "{}:/gns3:ro".format(Docker.resources_path()),
"{}:/gns3volumes/etc/network".format(os.path.join(vm.working_dir, "etc", "network")) "{}:/gns3volumes/etc/network".format(os.path.join(vm.working_dir, "etc", "network"))
], ],
"Privileged": True "Privileged": True,
"UsernsMode": "host"
}, },
"UsernsMode": "host",
"Volumes": {}, "Volumes": {},
"NetworkDisabled": True, "NetworkDisabled": True,
"Hostname": "test", "Hostname": "test",
@ -462,9 +462,9 @@ async def test_create_with_user(compute_project, manager):
"{}:/gns3:ro".format(Docker.resources_path()), "{}:/gns3:ro".format(Docker.resources_path()),
"{}:/gns3volumes/etc/network".format(os.path.join(vm.working_dir, "etc", "network")) "{}:/gns3volumes/etc/network".format(os.path.join(vm.working_dir, "etc", "network"))
], ],
"Privileged": True "Privileged": True,
"UsernsMode": "host"
}, },
"UsernsMode": "host",
"Volumes": {}, "Volumes": {},
"NetworkDisabled": True, "NetworkDisabled": True,
"Hostname": "test", "Hostname": "test",
@ -546,9 +546,9 @@ async def test_create_with_extra_volumes_duplicate_1_image(compute_project, mana
"{}:/gns3volumes/etc/network".format(os.path.join(vm.working_dir, "etc", "network")), "{}:/gns3volumes/etc/network".format(os.path.join(vm.working_dir, "etc", "network")),
"{}:/gns3volumes/vol/1".format(os.path.join(vm.working_dir, "vol", "1")), "{}:/gns3volumes/vol/1".format(os.path.join(vm.working_dir, "vol", "1")),
], ],
"Privileged": True "Privileged": True,
"UsernsMode": "host"
}, },
"UsernsMode": "host",
"Volumes": {}, "Volumes": {},
"NetworkDisabled": True, "NetworkDisabled": True,
"Hostname": "test", "Hostname": "test",
@ -586,9 +586,9 @@ async def test_create_with_extra_volumes_duplicate_2_user(compute_project, manag
"{}:/gns3volumes/etc/network".format(os.path.join(vm.working_dir, "etc", "network")), "{}:/gns3volumes/etc/network".format(os.path.join(vm.working_dir, "etc", "network")),
"{}:/gns3volumes/vol/1".format(os.path.join(vm.working_dir, "vol", "1")), "{}:/gns3volumes/vol/1".format(os.path.join(vm.working_dir, "vol", "1")),
], ],
"Privileged": True "Privileged": True,
"UsernsMode": "host"
}, },
"UsernsMode": "host",
"Volumes": {}, "Volumes": {},
"NetworkDisabled": True, "NetworkDisabled": True,
"Hostname": "test", "Hostname": "test",
@ -626,9 +626,9 @@ async def test_create_with_extra_volumes_duplicate_3_subdir(compute_project, man
"{}:/gns3volumes/etc/network".format(os.path.join(vm.working_dir, "etc", "network")), "{}:/gns3volumes/etc/network".format(os.path.join(vm.working_dir, "etc", "network")),
"{}:/gns3volumes/vol".format(os.path.join(vm.working_dir, "vol")), "{}:/gns3volumes/vol".format(os.path.join(vm.working_dir, "vol")),
], ],
"Privileged": True "Privileged": True,
"UsernsMode": "host"
}, },
"UsernsMode": "host",
"Volumes": {}, "Volumes": {},
"NetworkDisabled": True, "NetworkDisabled": True,
"Hostname": "test", "Hostname": "test",
@ -666,9 +666,9 @@ async def test_create_with_extra_volumes_duplicate_4_backslash(compute_project,
"{}:/gns3volumes/etc/network".format(os.path.join(vm.working_dir, "etc", "network")), "{}:/gns3volumes/etc/network".format(os.path.join(vm.working_dir, "etc", "network")),
"{}:/gns3volumes/vol".format(os.path.join(vm.working_dir, "vol")), "{}:/gns3volumes/vol".format(os.path.join(vm.working_dir, "vol")),
], ],
"Privileged": True "Privileged": True,
"UsernsMode": "host"
}, },
"UsernsMode": "host",
"Volumes": {}, "Volumes": {},
"NetworkDisabled": True, "NetworkDisabled": True,
"Hostname": "test", "Hostname": "test",
@ -705,9 +705,9 @@ async def test_create_with_extra_volumes_duplicate_5_subdir_issue_1595(compute_p
"{}:/gns3:ro".format(Docker.resources_path()), "{}:/gns3:ro".format(Docker.resources_path()),
"{}:/gns3volumes/etc".format(os.path.join(vm.working_dir, "etc")), "{}:/gns3volumes/etc".format(os.path.join(vm.working_dir, "etc")),
], ],
"Privileged": True "Privileged": True,
"UsernsMode": "host"
}, },
"UsernsMode": "host",
"Volumes": {}, "Volumes": {},
"NetworkDisabled": True, "NetworkDisabled": True,
"Hostname": "test", "Hostname": "test",
@ -744,9 +744,9 @@ async def test_create_with_extra_volumes_duplicate_6_subdir_issue_1595(compute_p
"{}:/gns3:ro".format(Docker.resources_path()), "{}:/gns3:ro".format(Docker.resources_path()),
"{}:/gns3volumes/etc".format(os.path.join(vm.working_dir, "etc")), "{}:/gns3volumes/etc".format(os.path.join(vm.working_dir, "etc")),
], ],
"Privileged": True "Privileged": True,
"UsernsMode": "host"
}, },
"UsernsMode": "host",
"Volumes": {}, "Volumes": {},
"NetworkDisabled": True, "NetworkDisabled": True,
"Hostname": "test", "Hostname": "test",
@ -791,9 +791,9 @@ async def test_create_with_extra_volumes(compute_project, manager):
"{}:/gns3volumes/vol/1".format(os.path.join(vm.working_dir, "vol", "1")), "{}:/gns3volumes/vol/1".format(os.path.join(vm.working_dir, "vol", "1")),
"{}:/gns3volumes/vol/2".format(os.path.join(vm.working_dir, "vol", "2")), "{}:/gns3volumes/vol/2".format(os.path.join(vm.working_dir, "vol", "2")),
], ],
"Privileged": True "Privileged": True,
"UsernsMode": "host"
}, },
"UsernsMode": "host",
"Volumes": {}, "Volumes": {},
"NetworkDisabled": True, "NetworkDisabled": True,
"Hostname": "test", "Hostname": "test",
@ -1040,9 +1040,9 @@ async def test_update(vm):
"{}:/gns3:ro".format(Docker.resources_path()), "{}:/gns3:ro".format(Docker.resources_path()),
"{}:/gns3volumes/etc/network".format(os.path.join(vm.working_dir, "etc", "network")) "{}:/gns3volumes/etc/network".format(os.path.join(vm.working_dir, "etc", "network"))
], ],
"Privileged": True "Privileged": True,
"UsernsMode": "host"
}, },
"UsernsMode": "host",
"Volumes": {}, "Volumes": {},
"NetworkDisabled": True, "NetworkDisabled": True,
"Hostname": "test", "Hostname": "test",
@ -1109,9 +1109,9 @@ async def test_update_running(vm):
"{}:/gns3:ro".format(Docker.resources_path()), "{}:/gns3:ro".format(Docker.resources_path()),
"{}:/gns3volumes/etc/network".format(os.path.join(vm.working_dir, "etc", "network")) "{}:/gns3volumes/etc/network".format(os.path.join(vm.working_dir, "etc", "network"))
], ],
"Privileged": True "Privileged": True,
"UsernsMode": "host"
}, },
"UsernsMode": "host",
"Volumes": {}, "Volumes": {},
"NetworkDisabled": True, "NetworkDisabled": True,
"Hostname": "test", "Hostname": "test",

View File

@ -353,16 +353,23 @@ async def test_set_platform(compute_project, manager):
async def test_disk_options(vm, tmpdir, fake_qemu_img_binary): async def test_disk_options(vm, tmpdir, fake_qemu_img_binary):
vm._hda_disk_image = str(tmpdir / "test.qcow2") vm._hda_disk_image = str(tmpdir / "test.qcow2")
vm._hda_disk_interface = "ide"
vm._hdb_disk_image = str(tmpdir / "test2.qcow2")
open(vm._hda_disk_image, "w+").close() open(vm._hda_disk_image, "w+").close()
open(vm._hdb_disk_image, "w+").close()
with asyncio_patch("gns3server.compute.qemu.qemu_vm.QemuVM._find_disk_file_format", return_value="qcow2"): with (asyncio_patch("gns3server.compute.qemu.qemu_vm.QemuVM._find_disk_file_format", return_value="qcow2")):
with asyncio_patch("asyncio.create_subprocess_exec", return_value=MagicMock()) as process: with asyncio_patch("asyncio.create_subprocess_exec", return_value=MagicMock()) as process:
options = await vm._disk_options() options = await vm._disk_options()
assert process.called assert process.called
args, kwargs = process.call_args args, kwargs = process.call_args
assert args == (fake_qemu_img_binary, "create", "-o", "backing_file={}".format(vm._hda_disk_image), "-F", "qcow2", "-f", "qcow2", os.path.join(vm.working_dir, "hda_disk.qcow2")) assert args == (fake_qemu_img_binary, "create", "-o", "backing_file={}".format(vm._hda_disk_image), "-F", "qcow2", "-f", "qcow2", os.path.join(vm.working_dir, "hda_disk.qcow2")) or \
args == (fake_qemu_img_binary, "create", "-o", "backing_file={}".format(vm._hdb_disk_image), "-F", "qcow2", "-f", "qcow2", os.path.join(vm.working_dir, "hdb_disk.qcow2"))
assert options == ['-drive', 'file=' + os.path.join(vm.working_dir, "hda_disk.qcow2") + ',if=ide,index=0,media=disk,id=drive0'] assert options == [
'-drive', 'file=' + os.path.join(vm.working_dir, "hda_disk.qcow2") + ',if=ide,index=0,media=disk,id=drive0',
'-drive', 'file=' + os.path.join(vm.working_dir, "hdb_disk.qcow2") + ',if=none,index=1,media=disk,id=drive1',
]
async def test_cdrom_option(vm, tmpdir, fake_qemu_img_binary): async def test_cdrom_option(vm, tmpdir, fake_qemu_img_binary):
@ -440,9 +447,13 @@ async def test_tpm_option(vm, tmpdir, fake_qemu_img_binary):
async def test_disk_options_multiple_disk(vm, tmpdir, fake_qemu_img_binary): async def test_disk_options_multiple_disk(vm, tmpdir, fake_qemu_img_binary):
vm._hda_disk_image = str(tmpdir / "test0.qcow2") vm._hda_disk_image = str(tmpdir / "test0.qcow2")
vm._hda_disk_interface = "ide"
vm._hdb_disk_image = str(tmpdir / "test1.qcow2") vm._hdb_disk_image = str(tmpdir / "test1.qcow2")
vm._hdb_disk_interface = "ide"
vm._hdc_disk_image = str(tmpdir / "test2.qcow2") vm._hdc_disk_image = str(tmpdir / "test2.qcow2")
vm._hdc_disk_interface = "ide"
vm._hdd_disk_image = str(tmpdir / "test3.qcow2") vm._hdd_disk_image = str(tmpdir / "test3.qcow2")
vm._hdd_disk_interface = "ide"
open(vm._hda_disk_image, "w+").close() open(vm._hda_disk_image, "w+").close()
open(vm._hdb_disk_image, "w+").close() open(vm._hdb_disk_image, "w+").close()
open(vm._hdc_disk_image, "w+").close() open(vm._hdc_disk_image, "w+").close()