Merge branch '2.2' into 3.1

# Conflicts:
#	gns3server/compute/qemu/qemu_vm.py
#	gns3server/controller/import_project.py
#	gns3server/controller/project.py
#	gns3server/crash_report.py
#	gns3server/version.py
#	tests/compute/docker/test_docker_vm.py
#	tests/controller/test_import_project.py
#	tests/controller/test_project.py
This commit is contained in:
grossmj committed 2026-08-08 16:27:14 +02:00
commit dff79f65d3
11 files changed
+260 -32

No files matched your search

+16
View File
@@ -1882,3 +1882,19 @@ async def test_memory(compute_project, manager):
"Cmd": ["/bin/sh"]
})
assert vm._cid == "e90e34656806"
async def test_stop_exited_container_no_stop_query(vm):
vm._ubridge_hypervisor = None
vm._fix_permissions = MagicMock()
with asyncio_patch("gns3server.compute.docker.DockerVM._get_container_state", return_value="exited"):
with asyncio_patch("gns3server.compute.docker.Docker.query") as mock_query:
vm._permissions_fixed = False
await vm.stop()
assert not any(
call.args[:2] == ("POST", "containers/e90e34656842/stop")
for call in mock_query.mock_calls
)
assert vm.status == "stopped"
+72 -15
View File
@@ -20,6 +20,8 @@ import os
import uuid
import json
import zipfile
import pytest
import aiohttp
from pathlib import Path
from tests.utils import asyncio_patch, AsyncioMagicMock
@@ -122,24 +124,49 @@ async def write_file(path, z):
f.write(chunk)
@pytest.mark.asyncio
async def test_import_project_containing_symlink(tmpdir, controller):
@pytest.fixture
def export_project_with_symlink(tmpdir, controller):
async def _export(symlink_target):
project = Project(controller=controller, name="test")
project.dump = MagicMock()
project = Project(controller=controller, name="test")
project.dump = MagicMock()
path = project.path
topology = {
"project_id": str(uuid.uuid4()),
"name": "test",
"auto_open": True,
"auto_start": True,
"topology": {
},
"version": "2.0.0"
}
with open(os.path.join(project.path, "project.gns3"), 'w+') as f:
json.dump(topology, f)
os.makedirs(os.path.join(project.path, "vm1", "dynamips"))
symlink_path = os.path.join(project.path, "vm1", "dynamips", "symlink")
os.symlink(symlink_target, symlink_path)
zip_path = str(tmpdir / "project.zip")
with aiozipstream.ZipFile() as z:
with patch("gns3server.compute.Dynamips.get_images_directory", return_value=str(tmpdir / "IOS"),):
await export_project(z, project, str(tmpdir), include_images=False)
await write_file(zip_path, z)
return zip_path
return _export
@pytest.mark.asyncio
async def test_import_project_containing_symlink(controller, export_project_with_symlink):
"""
Test importing a project containing a valid symlink (target inside the project directory).
"""
project_id = str(uuid.uuid4())
os.makedirs(os.path.join(path, "vm1", "dynamips"))
symlink_path = os.path.join(project.path, "vm1", "dynamips", "symlink")
symlink_target = "/tmp/anywhere"
os.symlink(symlink_target, symlink_path)
zip_path = str(tmpdir / "project.zip")
with aiozipstream.ZipFile() as z:
with patch("gns3server.compute.Dynamips.get_images_directory", return_value=str(tmpdir / "IOS"),):
await export_project(z, project, str(tmpdir), include_images=False)
await write_file(zip_path, z)
symlink_target = "../symlink_target"
zip_path = await export_project_with_symlink(symlink_target)
with open(zip_path, "rb") as f:
project = await import_project(controller, project_id, f)
@@ -152,6 +179,36 @@ async def test_import_project_containing_symlink(tmpdir, controller):
@pytest.mark.asyncio
async def test_import_project_containing_absolute_symlink(controller, export_project_with_symlink):
"""
Test importing a project containing an absolute symlink.
This should fail because absolute symlinks are not allowed for security reasons.
"""
project_id = str(uuid.uuid4())
symlink_target = "/tmp/anywhere"
zip_path = await export_project_with_symlink(symlink_target)
with pytest.raises(aiohttp.web.HTTPConflict):
with open(zip_path, "rb") as f:
await import_project(controller, project_id, f)
async def test_import_project_containing_escaping_symlink(controller, export_project_with_symlink):
"""
Test importing a project containing a symlink that escapes the project directory.
This should fail because symlinks that escape the project directory are not allowed for security reasons.
"""
project_id = str(uuid.uuid4())
symlink_target = "../../../../symlink_target"
zip_path = await export_project_with_symlink(symlink_target)
with pytest.raises(aiohttp.web.HTTPConflict):
with open(zip_path, "rb") as f:
await import_project(controller, project_id, f)
async def test_import_upgrade(tmpdir, controller):
"""
Topology made for previous GNS3 version are upgraded during the process
+13
View File
@@ -700,6 +700,19 @@ async def test_delete(project):
@pytest.mark.asyncio
async def test_delete_does_not_start_nodes(project):
"""
Deleting a project must not start its nodes, even when auto_start is enabled.
"""
project.auto_start = True
project.dump()
await project.close()
project.start_all = AsyncioMagicMock()
await project.delete()
assert not project.start_all.called
async def test_dump(projects_dir):
directory = projects_dir