refactor: ruff autofixes

This commit is contained in:
Mark Paronyan committed 2026-09-27 15:55:10 +03:00
1 parent 2ac637c662
commit a0450b6bce
455 files changed
+12322 -14762

No files matched your search

+21 -16
View File
@@ -21,6 +21,7 @@ from gns3server.controller.gns3vm.remote_gns3_vm import RemoteGNS3VM
from gns3server.controller.gns3vm.gns3_vm_error import GNS3VMError
from pydantic import SecretStr
@pytest.fixture
def gns3vm(controller):
@@ -38,14 +39,16 @@ async def test_list(gns3vm, controller):
@pytest.mark.asyncio
async def test_start(gns3vm, controller):
await controller.add_compute("r1",
name="R1",
protocol="https",
host="r1.local",
port=8484,
user="hello",
password=SecretStr("world"),
connect=False)
await controller.add_compute(
"r1",
name="R1",
protocol="https",
host="r1.local",
port=8484,
user="hello",
password=SecretStr("world"),
connect=False,
)
gns3vm.vmname = "R1"
await gns3vm.start()
@@ -60,14 +63,16 @@ async def test_start(gns3vm, controller):
@pytest.mark.asyncio
async def test_start_invalid_vm(gns3vm, controller):
await controller.add_compute("r1",
connect=False,
name="R1",
protocol="https",
host="r1.local",
port=8484,
user="hello",
password=SecretStr("world"))
await controller.add_compute(
"r1",
connect=False,
name="R1",
protocol="https",
host="r1.local",
port=8484,
user="hello",
password=SecretStr("world"),
)
gns3vm.vmname = "R2"
with pytest.raises(GNS3VMError):
@@ -55,9 +55,11 @@ vcpfps=25
GuestMemoryBalloon=0
"""
with asyncio_patch("gns3server.controller.gns3vm.virtualbox_gns3_vm.VirtualBoxGNS3VM._execute", return_value=showvminfo) as mock:
with asyncio_patch(
"gns3server.controller.gns3vm.virtualbox_gns3_vm.VirtualBoxGNS3VM._execute", return_value=showvminfo
) as mock:
res = await gns3vm._look_for_interface("nat")
mock.assert_called_with('showvminfo', ['GNS3 VM', '--machinereadable'])
mock.assert_called_with("showvminfo", ["GNS3 VM", "--machinereadable"])
assert res == 2
# with asyncio_patch("gns3server.controller.gns3vm.virtualbox_gns3_vm.VirtualBoxGNS3VM._execute") as mock:
@@ -71,6 +73,7 @@ GuestMemoryBalloon=0
async def test_cpu_vendor_id(gns3vm):
from cpuinfo import get_cpu_info
cpu_info = await wait_run_in_executor(get_cpu_info)
vendor_id = cpu_info.get('vendor_id_raw')
vendor_id = cpu_info.get("vendor_id_raw")
assert vendor_id # vendor id should not be empty
@@ -41,20 +41,19 @@ async def test_set_extra_options(gns3vm, vmx_path, windows_platform):
gns3vm._vmx_path = vmx_path
# when there is not an entry, we modify it
with open(vmx_path, 'w') as f:
with open(vmx_path, "w") as f:
f.write("")
await gns3vm._set_extra_options()
with open(vmx_path, 'r') as f:
with open(vmx_path, "r") as f:
assert f.read() == 'vhv.enable = "TRUE"\n'
# when there is an entry, we don't modify it
with open(vmx_path, 'w') as f:
with open(vmx_path, "w") as f:
f.write('vhv.enable = "FALSE"\n')
await gns3vm._set_extra_options()
with open(vmx_path, 'r') as f:
with open(vmx_path, "r") as f:
assert f.read() == 'vhv.enable = "FALSE"\n'
+20 -11
View File
@@ -54,9 +54,7 @@ XRD_V8 = {
{"adapter_number": 0, "port_name": "MgmtEth0/RP0/CPU0/0"},
{"adapter_number": 1, "port_name": "Gi0/0/0/0"},
],
"extra_configs": [
{"target": "/firstboot.cfg", "content": "!\nend\n"}
],
"extra_configs": [{"target": "/firstboot.cfg", "content": "!\nend\n"}],
},
}
],
@@ -141,8 +139,7 @@ def test_v8_docker_settings_require_image():
appliance = dict(
XRD_V8,
settings=[
{"name": "only", "default": True, "template_type": "docker",
"template_properties": {"adapters": 2}},
{"name": "only", "default": True, "template_type": "docker", "template_properties": {"adapters": 2}},
],
)
with pytest.raises(pydantic.ValidationError):
@@ -158,8 +155,12 @@ def test_v8_template_properties_validated_against_template_type():
appliance = dict(
XRD_V8,
settings=[
{"name": "only", "default": True, "template_type": "qemu",
"template_properties": {"ram": 512, "boot_priority": "zzz"}},
{
"name": "only",
"default": True,
"template_type": "qemu",
"template_properties": {"ram": 512, "boot_priority": "zzz"},
},
],
)
with pytest.raises(pydantic.ValidationError):
@@ -170,8 +171,12 @@ def test_v8_qemu_kvm_property_validates():
appliance = dict(
XRD_V8,
settings=[
{"name": "only", "default": True, "template_type": "qemu",
"template_properties": {"ram": 512, "kvm": "disable"}},
{
"name": "only",
"default": True,
"template_type": "qemu",
"template_properties": {"ram": 512, "kvm": "disable"},
},
],
)
model = ApplianceModel.model_validate(appliance)
@@ -184,8 +189,12 @@ def test_v8_qemu_cpu_throttling_range():
qemu template (int, 0-800).
"""
settings = {"name": "only", "default": True, "template_type": "qemu",
"template_properties": {"ram": 512, "cpu_throttling": 500}}
settings = {
"name": "only",
"default": True,
"template_type": "qemu",
"template_properties": {"ram": 512, "cpu_throttling": 500},
}
model = ApplianceModel.model_validate(dict(XRD_V8, settings=[settings]))
assert model.settings[0].template_properties.cpu_throttling == 500
+13 -6
View File
@@ -75,8 +75,12 @@ async def test_install_docker_version_skips_image_resolution(monkeypatch):
_FakeTemplatesService.created = []
appliance_data = _v8_appliance(
[
{"name": "default", "default": True, "template_type": "docker",
"template_properties": {"image": "xrd:latest"}},
{
"name": "default",
"default": True,
"template_type": "docker",
"template_properties": {"image": "xrd:latest"},
},
],
versions=[{"name": "1.0", "images": {"image": "xrd:1.0"}}],
)
@@ -113,8 +117,12 @@ async def test_install_iou_version_maps_image_to_path(monkeypatch, tmp_path):
_FakeTemplatesService.created = []
appliance_data = _v8_appliance(
[
{"name": "default", "default": True, "template_type": "iou",
"template_properties": {"ethernet_adapters": 4, "ram": 256}},
{
"name": "default",
"default": True,
"template_type": "iou",
"template_properties": {"ethernet_adapters": 4, "ram": 256},
},
],
versions=[{"name": "15.9", "images": {"image": "i86bi-linux-l3-15.9.bin"}}],
)
@@ -141,8 +149,7 @@ async def test_install_iou_version_maps_image_to_path(monkeypatch, tmp_path):
async def test_install_version_not_found(monkeypatch):
manager = ApplianceManager()
appliance_data = _v8_appliance(
[{"name": "only", "default": True, "template_type": "docker",
"template_properties": {"image": "xrd:latest"}}],
[{"name": "only", "default": True, "template_type": "docker", "template_properties": {"image": "xrd:latest"}}],
versions=[{"name": "1.0", "images": {"image": "xrd:1.0"}}],
)
appliance = Appliance("test.gns3a", appliance_data)
+39 -31
View File
@@ -327,8 +327,7 @@ def test_v8_iou_version_images_mapped_to_path():
"name": "IOU L3",
"category": "router",
"settings": [
{"name": "only", "template_type": "iou",
"template_properties": {"ethernet_adapters": 4, "ram": 256}},
{"name": "only", "template_type": "iou", "template_properties": {"ethernet_adapters": 4, "ram": 256}},
],
}
version = {"name": "15.9", "images": {"image": "i86bi-linux-l3-15.9.bin"}}
@@ -346,8 +345,7 @@ def test_v8_dynamips_idlepc_from_version():
"name": "Cisco 7200",
"category": "router",
"settings": [
{"name": "only", "template_type": "dynamips",
"template_properties": {"ram": 512, "platform": "c7200"}},
{"name": "only", "template_type": "dynamips", "template_properties": {"ram": 512, "platform": "c7200"}},
],
}
version = {"name": "12.4", "idlepc": "0x613080c0", "images": {"image": "c7200.bin"}}
@@ -368,8 +366,7 @@ def test_v8_dynamips_settings_idlepc_precedence():
"name": "Cisco 7200",
"category": "router",
"settings": [
{"name": "only", "template_type": "dynamips",
"template_properties": {"ram": 512, "idlepc": "0x6142da40"}},
{"name": "only", "template_type": "dynamips", "template_properties": {"ram": 512, "idlepc": "0x6142da40"}},
],
}
version = {"name": "12.4", "idlepc": "0x613080c0", "images": {"image": "c7200.bin"}}
@@ -390,8 +387,11 @@ def test_v8_qemu_kvm_disable_forces_accel_tcg():
"name": "QEMU VM",
"category": "guest",
"settings": [
{"name": "only", "template_type": "qemu",
"template_properties": {"ram": 512, "options": "-m 512", "kvm": "disable"}},
{
"name": "only",
"template_type": "qemu",
"template_properties": {"ram": 512, "options": "-m 512", "kvm": "disable"},
},
],
}
@@ -407,8 +407,11 @@ def test_v8_qemu_kvm_allow_keeps_options():
"name": "QEMU VM",
"category": "guest",
"settings": [
{"name": "only", "template_type": "qemu",
"template_properties": {"ram": 512, "options": "-m 512", "kvm": "allow"}},
{
"name": "only",
"template_type": "qemu",
"template_properties": {"ram": 512, "options": "-m 512", "kvm": "allow"},
},
],
}
@@ -429,10 +432,17 @@ def test_v8_no_cross_type_inheritance():
"name": "Mixed",
"category": "router",
"settings": [
{"name": "default qemu", "default": True, "template_type": "qemu",
"template_properties": {"ram": 2048, "adapters": 10}},
{"name": "docker alt", "template_type": "docker",
"template_properties": {"image": "xrd:latest", "adapters": 2}},
{
"name": "default qemu",
"default": True,
"template_type": "qemu",
"template_properties": {"ram": 2048, "adapters": 10},
},
{
"name": "docker alt",
"template_type": "docker",
"template_properties": {"image": "xrd:latest", "adapters": 2},
},
],
}
version = {"name": "1.0", "settings": "docker alt", "images": {"image": "xrd:1.0"}}
@@ -456,10 +466,8 @@ def test_v8_multiple_defaults_inherit_first_same_type():
"name": "Multi",
"category": "router",
"settings": [
{"name": "default one", "default": True, "template_type": "qemu",
"template_properties": {"ram": 1024}},
{"name": "default two", "default": True, "template_type": "qemu",
"template_properties": {"ram": 2048}},
{"name": "default one", "default": True, "template_type": "qemu", "template_properties": {"ram": 1024}},
{"name": "default two", "default": True, "template_type": "qemu", "template_properties": {"ram": 2048}},
{"name": "alt", "template_type": "qemu", "template_properties": {"cpus": 2}},
],
}
@@ -482,8 +490,7 @@ def test_v8_symbol_fallback_uses_effective_category():
"name": "Test",
"category": "router",
"settings": [
{"name": "only", "template_type": "qemu",
"template_properties": {"ram": 512, "category": "guest"}},
{"name": "only", "template_type": "qemu", "template_properties": {"ram": 512, "category": "guest"}},
],
}
template = ApplianceToTemplate().new_template(appliance, None, "local")
@@ -514,13 +521,16 @@ def test_v8_reserved_keys_cannot_be_overridden():
"name": "Test",
"category": "router",
"settings": [
{"name": "only", "template_type": "docker",
"template_properties": {
"image": "xrd:latest",
"template_type": "qemu",
"compute_id": "evil-compute",
"version": "9.9",
}},
{
"name": "only",
"template_type": "docker",
"template_properties": {
"image": "xrd:latest",
"template_type": "qemu",
"compute_id": "evil-compute",
"version": "9.9",
},
},
],
}
@@ -543,10 +553,8 @@ def test_v8_get_template_type():
"name": "Mixed",
"category": "router",
"settings": [
{"name": "default", "default": True, "template_type": "qemu",
"template_properties": {"ram": 512}},
{"name": "alt", "template_type": "docker",
"template_properties": {"image": "xrd:latest"}},
{"name": "default", "default": True, "template_type": "qemu", "template_properties": {"ram": 512}},
{"name": "alt", "template_type": "docker", "template_properties": {"image": "xrd:latest"}},
],
}
+144 -93
View File
@@ -68,9 +68,9 @@ def test_getUrl(controller):
def test_get_url(controller):
compute = Compute("my_compute_id", protocol="https", host="localhost", port=84, controller=controller)
with patch('gns3server.controller.compute.Compute._getUrl', return_value="returned") as getURL:
assert compute.get_url("/test") == 'returned'
getURL.assert_called_once_with('/test')
with patch("gns3server.controller.compute.Compute._getUrl", return_value="returned") as getURL:
assert compute.get_url("/test") == "returned"
getURL.assert_called_once_with("/test")
def test_host_ip(controller):
@@ -85,7 +85,9 @@ def test_name():
assert c.name == "https://example.com:84"
c = Compute("world", protocol="https", host="example.com", port=84, controller=MagicMock(), name="hello")
assert c.name == "hello"
c = Compute("world", protocol="https", host="example.com", port=84, controller=MagicMock(), user="azertyuiopqsdfghjklkm")
c = Compute(
"world", protocol="https", host="example.com", port=84, controller=MagicMock(), user="azertyuiopqsdfghjklkm"
)
assert c.name == "https://azertyuiopq...@example.com:84"
@@ -97,7 +99,16 @@ async def test_compute_httpQuery(compute):
response.status = 200
await compute.post("/projects", {"a": "b"})
await compute.close()
mock.assert_called_with("POST", "https://example.com:84/v3/compute/projects", headers={'content-type': 'application/json'}, data=b'{"a": "b"}', auth=None, params=None, chunked=None, timeout=120)
mock.assert_called_with(
"POST",
"https://example.com:84/v3/compute/projects",
headers={"content-type": "application/json"},
data=b'{"a": "b"}',
auth=None,
params=None,
chunked=None,
timeout=120,
)
assert compute._auth is None
@@ -112,13 +123,22 @@ async def test_compute_httpQueryAuth(compute):
compute.password = SecretStr("toor")
await compute.post("/projects", {"a": "b"})
await compute.close()
mock.assert_called_with("POST", "https://example.com:84/v3/compute/projects", headers={'content-type': 'application/json'}, data=b'{"a": "b"}', auth=compute._auth, params=None, chunked=None, timeout=120)
mock.assert_called_with(
"POST",
"https://example.com:84/v3/compute/projects",
headers={"content-type": "application/json"},
data=b'{"a": "b"}',
auth=compute._auth,
params=None,
chunked=None,
timeout=120,
)
assert compute._auth.login == "root"
assert compute._auth.password == "toor"
# @pytest.mark.asyncio
#async def test_compute_httpQueryNotConnected(compute, controller):
# async def test_compute_httpQueryNotConnected(compute, controller):
#
# controller._notification = MagicMock()
# compute._connected = False
@@ -136,7 +156,7 @@ async def test_compute_httpQueryAuth(compute):
# @pytest.mark.asyncio
#async def test_compute_httpQueryNotConnectedGNS3vmNotRunning(compute, controller):
# async def test_compute_httpQueryNotConnectedGNS3vmNotRunning(compute, controller):
# """
# We are not connected to the remote and it's a GNS3 VM. So we need to start it
# """
@@ -172,7 +192,16 @@ async def test_compute_httpQueryNotConnectedInvalidVersion(compute):
with asyncio_patch("aiohttp.ClientSession.request", return_value=response) as mock:
with pytest.raises(ControllerError):
await compute.post("/projects", {"a": "b"})
mock.assert_any_call("GET", "https://example.com:84/v3/compute/capabilities", headers={'content-type': 'application/json'}, data=None, auth=None, params=None, chunked=None, timeout=120)
mock.assert_any_call(
"GET",
"https://example.com:84/v3/compute/capabilities",
headers={"content-type": "application/json"},
data=None,
auth=None,
params=None,
chunked=None,
timeout=120,
)
await compute.close()
@@ -181,12 +210,21 @@ async def test_compute_httpQueryNotConnectedNonGNS3Server(compute):
compute._connected = False
response = AsyncioMagicMock()
response.read = AsyncioMagicMock(return_value=b'Blocked by super antivirus')
response.read = AsyncioMagicMock(return_value=b"Blocked by super antivirus")
response.status = 200
with asyncio_patch("aiohttp.ClientSession.request", return_value=response) as mock:
with pytest.raises(ControllerError):
await compute.post("/projects", {"a": "b"})
mock.assert_any_call("GET", "https://example.com:84/v3/compute/capabilities", headers={'content-type': 'application/json'}, data=None, auth=None, params=None, chunked=None, timeout=120)
mock.assert_any_call(
"GET",
"https://example.com:84/v3/compute/capabilities",
headers={"content-type": "application/json"},
data=None,
auth=None,
params=None,
chunked=None,
timeout=120,
)
await compute.close()
@@ -195,12 +233,21 @@ async def test_compute_httpQueryNotConnectedNonGNS3Server2(compute):
compute._connected = False
response = AsyncioMagicMock()
response.read = AsyncioMagicMock(return_value=b'{}')
response.read = AsyncioMagicMock(return_value=b"{}")
response.status = 200
with asyncio_patch("aiohttp.ClientSession.request", return_value=response) as mock:
with pytest.raises(ControllerError):
await compute.post("/projects", {"a": "b"})
mock.assert_any_call("GET", "https://example.com:84/v3/compute/capabilities", headers={'content-type': 'application/json'}, data=None, auth=None, params=None, chunked=None, timeout=120)
mock.assert_any_call(
"GET",
"https://example.com:84/v3/compute/capabilities",
headers={"content-type": "application/json"},
data=None,
auth=None,
params=None,
chunked=None,
timeout=120,
)
@pytest.mark.asyncio
@@ -234,22 +281,32 @@ async def test_compute_httpQuery_project(compute):
response = MagicMock()
with asyncio_patch("aiohttp.ClientSession.request", return_value=response) as mock:
response.status = 200
with patch('gns3server.controller.project.Project.emit_controller_notification') as mock_notification:
with patch("gns3server.controller.project.Project.emit_controller_notification") as mock_notification:
project = Project(name="Test")
mock_notification.assert_called()
await compute.post("/projects", project)
mock.assert_called_with("POST", "https://example.com:84/v3/compute/projects", headers={'content-type': 'application/json'}, data=json.dumps(project.asdict()), auth=None, params=None, chunked=None, timeout=120)
mock.assert_called_with(
"POST",
"https://example.com:84/v3/compute/projects",
headers={"content-type": "application/json"},
data=json.dumps(project.asdict()),
auth=None,
params=None,
chunked=None,
timeout=120,
)
await compute.close()
# FIXME: https://github.com/aio-libs/aiohttp/issues/2525
# @pytest.mark.asyncio
#async def test_connectNotification(compute):
# async def test_connectNotification(compute):
#
# ws_mock = AsyncioMagicMock()
# call = 0
#
# @pytest.mark.asyncio
#async def receive():
# async def receive():
# nonlocal call
# call += 1
# if call == 1:
@@ -282,7 +339,7 @@ async def test_compute_httpQuery_project(compute):
# call = 0
#
# @pytest.mark.asyncio
#async def receive():
# async def receive():
# nonlocal call
# call += 1
# if call == 1:
@@ -306,6 +363,7 @@ async def test_compute_httpQuery_project(compute):
# assert args[1]["memory_usage_percent"] == 80.7
# assert args[1]["cpu_usage_percent"] == 35.7
@pytest.mark.asyncio
async def test_json(compute):
@@ -322,14 +380,7 @@ async def test_json(compute):
"disk_usage_percent": 0,
"connected": True,
"last_error": None,
"capabilities": {
"version": "",
"platform": "",
"cpus": 0,
"memory": 0,
"disk_size": 0,
"node_types": []
}
"capabilities": {"version": "", "platform": "", "cpus": 0, "memory": 0, "disk_size": 0, "node_types": []},
}
assert compute.asdict(topology_dump=True) == {
"compute_id": "my_compute_id",
@@ -347,7 +398,9 @@ async def test_downloadFile(project, compute):
response.status = 200
with asyncio_patch("aiohttp.ClientSession.request", return_value=response) as mock:
await compute.download_file(project, "test/titi")
mock.assert_called_with("GET", "https://example.com:84/v3/compute/projects/{}/files/test/titi".format(project.id), auth=None)
mock.assert_called_with(
"GET", "https://example.com:84/v3/compute/projects/{}/files/test/titi".format(project.id), auth=None
)
await compute.close()
@@ -382,7 +435,16 @@ async def test_forward_get(compute):
response.status = 200
with asyncio_patch("aiohttp.ClientSession.request", return_value=response) as mock:
await compute.forward("GET", "qemu", "images")
mock.assert_called_with("GET", "https://example.com:84/v3/compute/qemu/images", headers={'content-type': 'application/json'}, data=None, auth=None, params=None, chunked=None, timeout=None)
mock.assert_called_with(
"GET",
"https://example.com:84/v3/compute/qemu/images",
headers={"content-type": "application/json"},
data=None,
auth=None,
params=None,
chunked=None,
timeout=None,
)
await compute.close()
@@ -405,7 +467,16 @@ async def test_forward_post(compute):
response.status = 200
with asyncio_patch("aiohttp.ClientSession.request", return_value=response) as mock:
await compute.forward("POST", "qemu", "img", data={"id": 42})
mock.assert_called_with("POST", "https://example.com:84/v3/compute/qemu/img", headers={'content-type': 'application/json'}, data=b'{"id": 42}', auth=None, params=None, chunked=None, timeout=None)
mock.assert_called_with(
"POST",
"https://example.com:84/v3/compute/qemu/img",
headers={"content-type": "application/json"},
data=b'{"id": 42}',
auth=None,
params=None,
chunked=None,
timeout=None,
)
await compute.close()
@@ -418,7 +489,16 @@ async def test_list_files(project, compute):
response.status = 200
with asyncio_patch("aiohttp.ClientSession.request", return_value=response) as mock:
assert await compute.list_files(project) == res
mock.assert_any_call("GET", "https://example.com:84/v3/compute/projects/{}/files".format(project.id), headers={'content-type': 'application/json'}, data=None, auth=None, params=None, chunked=None, timeout=None)
mock.assert_any_call(
"GET",
"https://example.com:84/v3/compute/projects/{}/files".format(project.id),
headers={"content-type": "application/json"},
data=None,
auth=None,
params=None,
chunked=None,
timeout=None,
)
await compute.close()
@@ -432,7 +512,7 @@ async def test_interfaces(compute):
"mac_address": "00:50:56:c0:00:63",
"name": "vmnet99",
"netmask": "255.255.255.0",
"type": "ethernet"
"type": "ethernet",
}
]
response = AsyncioMagicMock()
@@ -440,7 +520,16 @@ async def test_interfaces(compute):
response.status = 200
with asyncio_patch("aiohttp.ClientSession.request", return_value=response) as mock:
assert await compute.interfaces() == res
mock.assert_any_call("GET", "https://example.com:84/v3/compute/network/interfaces", headers={'content-type': 'application/json'}, data=None, auth=None, params=None, chunked=None, timeout=120)
mock.assert_any_call(
"GET",
"https://example.com:84/v3/compute/network/interfaces",
headers={"content-type": "application/json"},
data=None,
auth=None,
params=None,
chunked=None,
timeout=120,
)
await compute.close()
@@ -449,64 +538,32 @@ async def test_get_ip_on_same_subnet(controller):
compute1 = Compute("compute1", host="192.168.1.1", controller=controller)
compute1._interfaces_cache = [
{
"ip_address": "127.0.0.1",
"netmask": "255.255.255.255"
},
{
"ip_address": "192.168.2.1",
"netmask": "255.255.255.0"
},
{
"ip_address": "192.168.1.1",
"netmask": "255.255.255.0"
},
{"ip_address": "127.0.0.1", "netmask": "255.255.255.255"},
{"ip_address": "192.168.2.1", "netmask": "255.255.255.0"},
{"ip_address": "192.168.1.1", "netmask": "255.255.255.0"},
]
# Case 1 both host are on the same network
compute2 = Compute("compute2", host="192.168.1.2", controller=controller)
compute2._interfaces_cache = [
{
"ip_address": "127.0.0.1",
"netmask": "255.255.255.255"
},
{
"ip_address": "192.168.2.2",
"netmask": "255.255.255.0"
},
{
"ip_address": "192.168.1.2",
"netmask": "255.255.255.0"
}
{"ip_address": "127.0.0.1", "netmask": "255.255.255.255"},
{"ip_address": "192.168.2.2", "netmask": "255.255.255.0"},
{"ip_address": "192.168.1.2", "netmask": "255.255.255.0"},
]
assert await compute1.get_ip_on_same_subnet(compute2) == ("192.168.1.1", "192.168.1.2")
# Case 2 compute2 host is on a different network but a common interface is available
compute2 = Compute("compute2", host="127.0.0.1", controller=controller)
compute2._interfaces_cache = [
{
"ip_address": "127.0.0.1",
"netmask": "255.255.255.255"
},
{
"ip_address": "192.168.4.2",
"netmask": "255.255.255.0"
},
{
"ip_address": "192.168.1.2",
"netmask": "255.255.255.0"
}
{"ip_address": "127.0.0.1", "netmask": "255.255.255.255"},
{"ip_address": "192.168.4.2", "netmask": "255.255.255.0"},
{"ip_address": "192.168.1.2", "netmask": "255.255.255.0"},
]
assert await compute1.get_ip_on_same_subnet(compute2) == ("192.168.1.1", "192.168.1.2")
#No common interface
# No common interface
compute2 = Compute("compute2", host="127.0.0.1", controller=controller)
compute2._interfaces_cache = [
{
"ip_address": "127.0.0.1",
"netmask": "255.255.255.255"
}
]
compute2._interfaces_cache = [{"ip_address": "127.0.0.1", "netmask": "255.255.255.255"}]
with pytest.raises(ValueError):
await compute1.get_ip_on_same_subnet(compute2)
@@ -514,26 +571,14 @@ async def test_get_ip_on_same_subnet(controller):
compute2 = Compute("compute2", host="192.168.1.2", controller=controller)
compute1 = Compute("compute1", host="192.168.2.1", controller=controller)
compute1._interfaces_cache = [
{
"ip_address": "127.0.0.1",
"netmask": "255.255.255.255"
},
{
"ip_address": "169.254.1.1",
"netmask": "255.255.0.0"
},
{"ip_address": "127.0.0.1", "netmask": "255.255.255.255"},
{"ip_address": "169.254.1.1", "netmask": "255.255.0.0"},
]
compute2._interfaces_cache = [
{
"ip_address": "127.0.0.1",
"netmask": "255.255.255.255"
},
{
"ip_address": "169.254.2.1",
"netmask": "255.255.0.0"
},
{"ip_address": "127.0.0.1", "netmask": "255.255.255.255"},
{"ip_address": "169.254.2.1", "netmask": "255.255.0.0"},
]
assert await compute1.get_ip_on_same_subnet(compute2) == ('192.168.2.1', '192.168.1.2')
assert await compute1.get_ip_on_same_subnet(compute2) == ("192.168.2.1", "192.168.1.2")
class FakeWebSocket:
@@ -573,7 +618,12 @@ async def test_connect_notification_poison_frame_autoreconnects(compute, monkeyp
emit_mock = MagicMock()
monkeypatch.setattr(compute._controller.notification, "controller_emit", emit_mock)
frames = [
_text_frame({"action": "ping", "event": {"cpu_usage_percent": 10.0, "memory_usage_percent": 20.0, "disk_usage_percent": 30.0}}),
_text_frame(
{
"action": "ping",
"event": {"cpu_usage_percent": 10.0, "memory_usage_percent": 20.0, "disk_usage_percent": 30.0},
}
),
_text_frame({"event": {"poison": True}}), # missing "action": raises KeyError in the receive loop
]
session = MagicMock()
@@ -590,6 +640,7 @@ async def test_connect_notification_poison_frame_autoreconnects(compute, monkeyp
async def fake_connect():
compute._reconnect_attempted = True
monkeypatch.setattr(compute, "connect", fake_connect)
# must not raise despite the poison frame
+28 -50
View File
@@ -120,7 +120,9 @@ async def test_load_projects_skip_unexpected_errors(controller, projects_dir):
with open(os.path.join(projects_dir, "broken_project", "broken.gns3"), "w+") as f:
f.write("")
with asyncio_patch("gns3server.controller.Controller.load_project", side_effect=Exception("boom")) as mock_load_project:
with asyncio_patch(
"gns3server.controller.Controller.load_project", side_effect=Exception("boom")
) as mock_load_project:
await controller.load_projects()
mock_load_project.assert_called_with(os.path.join(projects_dir, "broken_project", "broken.gns3"), load=False)
@@ -340,16 +342,12 @@ async def test_getProject(controller):
@pytest.mark.asyncio
async def test_start(controller):
controller.gns3vm.settings = {
"enable": False,
"engine": "vmware",
"vmname": "GNS3 VM"
}
controller.gns3vm.settings = {"enable": False, "engine": "vmware", "vmname": "GNS3 VM"}
#with asyncio_patch("gns3server.controller.compute.Compute.connect") as mock:
# with asyncio_patch("gns3server.controller.compute.Compute.connect") as mock:
with asyncio_patch("gns3server.controller.Controller._install_builtin_disks", return_value=[]):
await controller.start()
#assert mock.called
# assert mock.called
assert len(controller.computes) == 1 # Local compute is created
assert controller.computes["local"].name == f"{socket.gethostname()} (controller)"
@@ -360,11 +358,7 @@ async def test_start_vm(controller):
Start the controller with a GNS3 VM
"""
controller.gns3vm.settings = {
"enable": True,
"engine": "vmware",
"vmname": "GNS3 VM"
}
controller.gns3vm.settings = {"enable": True, "engine": "vmware", "vmname": "GNS3 VM"}
with asyncio_patch("gns3server.controller.gns3vm.vmware_gns3_vm.VMwareGNS3VM.start") as mock:
with asyncio_patch("gns3server.controller.gns3vm.GNS3VM._check_network"):
@@ -392,12 +386,7 @@ async def test_stop_vm(controller):
Stop GNS3 VM if configured
"""
controller.gns3vm.settings = {
"enable": True,
"engine": "vmware",
"when_exit": "stop",
"vmname": "GNS3 VM"
}
controller.gns3vm.settings = {"enable": True, "engine": "vmware", "when_exit": "stop", "vmname": "GNS3 VM"}
controller.gns3vm.current_engine().running = True
with asyncio_patch("gns3server.controller.gns3vm.vmware_gns3_vm.VMwareGNS3VM.stop") as mock:
@@ -411,12 +400,7 @@ async def test_suspend_vm(controller):
Suspend GNS3 VM if configured
"""
controller.gns3vm.settings = {
"enable": True,
"engine": "vmware",
"when_exit": "suspend",
"vmname": "GNS3 VM"
}
controller.gns3vm.settings = {"enable": True, "engine": "vmware", "when_exit": "suspend", "vmname": "GNS3 VM"}
controller.gns3vm.current_engine().running = True
with asyncio_patch("gns3server.controller.gns3vm.vmware_gns3_vm.VMwareGNS3VM.suspend") as mock:
@@ -430,12 +414,7 @@ async def test_keep_vm(controller):
Keep GNS3 VM if configured
"""
controller.gns3vm.settings = {
"enable": True,
"engine": "vmware",
"when_exit": "keep",
"vmname": "GNS3 VM"
}
controller.gns3vm.settings = {"enable": True, "engine": "vmware", "when_exit": "keep", "vmname": "GNS3 VM"}
controller.gns3vm.current_engine().running = True
with asyncio_patch("gns3server.controller.gns3vm.vmware_gns3_vm.VMwareGNS3VM.suspend") as mock:
@@ -457,18 +436,18 @@ async def test_get_free_project_name(controller):
async def test_install_base_configs(controller, config, tmpdir):
config.settings.Server.configs_path = str(tmpdir)
with open(str(tmpdir / 'iou_l2_base_startup-config.txt'), 'w+') as f:
f.write('test')
with open(str(tmpdir / "iou_l2_base_startup-config.txt"), "w+") as f:
f.write("test")
await controller._install_base_configs()
assert os.path.exists(str(tmpdir / 'iou_l3_base_startup-config.txt'))
assert os.path.exists(str(tmpdir / "iou_l3_base_startup-config.txt"))
# the IOL docker base config ships with the server too (referenced by the
# GNS3_IOL_STARTUP_CONFIG knob in the documented template)
assert os.path.exists(str(tmpdir / 'iol-xe-base.txt'))
assert os.path.exists(str(tmpdir / "iol-xe-base.txt"))
# Check is the file has not been overwritten
with open(str(tmpdir / 'iou_l2_base_startup-config.txt')) as f:
assert f.read() == 'test'
with open(str(tmpdir / "iou_l2_base_startup-config.txt")) as f:
assert f.read() == "test"
@pytest.mark.asyncio
@@ -486,8 +465,8 @@ async def test_install_base_configs(controller, config, tmpdir):
"empty200G.qcow2",
"empty250G.qcow2",
"empty500G.qcow2",
"empty1T.qcow2"
]
"empty1T.qcow2",
],
)
async def test_install_builtin_disks(controller, config, tmpdir, builtin_disk):
@@ -500,17 +479,12 @@ async def test_install_builtin_disks(controller, config, tmpdir, builtin_disk):
@pytest.mark.asyncio
async def test_appliances(controller, config, tmpdir):
my_appliance = {
"name": "My Appliance",
"status": "stable"
}
with open(str(tmpdir / "my_appliance.gns3a"), 'w+') as f:
my_appliance = {"name": "My Appliance", "status": "stable"}
with open(str(tmpdir / "my_appliance.gns3a"), "w+") as f:
json.dump(my_appliance, f)
# A broken appliance
my_appliance = {
"name": "Broken"
}
with open(str(tmpdir / "my_appliance2.gns3a"), 'w+') as f:
my_appliance = {"name": "Broken"}
with open(str(tmpdir / "my_appliance2.gns3a"), "w+") as f:
json.dump(my_appliance, f)
config.settings.Server.appliances_path = str(tmpdir)
@@ -624,8 +598,12 @@ async def test_find_projects_skips_unreadable_topology(controller):
project = await controller.add_project(name="Broken")
await project.add_node(
compute, "n1", None, node_type="vpcs",
template_id=str(uuid.uuid4()), properties={"hda_disk_image": "lost.qcow2"},
compute,
"n1",
None,
node_type="vpcs",
template_id=str(uuid.uuid4()),
properties={"hda_disk_image": "lost.qcow2"},
)
await project.close()
os.remove(project.topology_file)
+7 -5
View File
@@ -55,7 +55,7 @@ def test_json(project):
"z": i.z,
"locked": i.locked,
"svg": i.svg,
"rotation": i.rotation
"rotation": i.rotation,
}
assert i.asdict(topology_dump=True) == {
"drawing_id": i.id,
@@ -64,7 +64,7 @@ def test_json(project):
"z": i.z,
"rotation": i.rotation,
"locked": i.locked,
"svg": i.svg
"svg": i.svg,
}
@@ -99,7 +99,7 @@ def test_image_base64(project):
If image are embed as base 64 we need to dump them on disk
"""
svg = "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" height=\"128\" width=\"128\">\n<image height=\"128\" width=\"128\" xlink:href=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAAN2AAADdgF91YLMAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAHm5JREFUeJztnXl8FGXSx7/VIYAKKCoe4IEIq6ALuQnI4YXXKihulCSACIIsu4qKNyqXByqK67EeiMtyJGrwXnfABSu/L3I31aclPub3oBIQ/YD2zdUBZ+T37l7Dt0OAKoIYcUf07mBhpkieonJe+FcAUOgeX8dL/4ysTCSmTiwwsx1FPLJfuas89mXsByu/N/BR43E09+xMafDrYFI6wmzINu7QreFo1kD4EQIW/m8ICm1iAdBXWp0wuusiJp+Q7ilok3VE02RR+MoWPTYMXTYNlarAx6c6iQU7X/RbQ4DZA2m1F44CnrdYjDPG8ZcLBe/1kzz2Z9ybfUZQALAS\" />\n</svg>"
svg = '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="128" width="128">\n<image height="128" width="128" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAAN2AAADdgF91YLMAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAHm5JREFUeJztnXl8FGXSx7/VIYAKKCoe4IEIq6ALuQnI4YXXKihulCSACIIsu4qKNyqXByqK67EeiMtyJGrwXnfABSu/L3I31aclPub3oBIQ/YD2zdUBZ+T37l7Dt0OAKoIYcUf07mBhpkieonJe+FcAUOgeX8dL/4ysTCSmTiwwsx1FPLJfuas89mXsByu/N/BR43E09+xMafDrYFI6wmzINu7QreFo1kD4EQIW/m8ICm1iAdBXWp0wuusiJp+Q7ilok3VE02RR+MoWPTYMXTYNlarAx6c6iQU7X/RbQ4DZA2m1F44CnrdYjDPG8ZcLBe/1kzz2Z9ybfUZQALAS" />\n</svg>'
drawing = Drawing(project, None, svg=svg)
assert drawing._svg == "8418154b760b4e8023650e04c4992e24.png"
@@ -113,10 +113,12 @@ def test_image_svg(project):
Large SVG are dump on disk
"""
svg = "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" height=\"128\" width=\"128\">\n"
svg = (
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="128" width="128">\n'
)
for i in range(0, 1000):
svg += "<rect width=\"100\"></rect>"
svg += '<rect width="100"></rect>'
svg += "</svg>"
drawing = Drawing(project, None, svg=svg)
+58 -123
View File
@@ -59,7 +59,7 @@ async def node(controller, project):
@pytest.mark.asyncio
async def write_file(path, z):
with open(path, 'wb') as f:
with open(path, "wb") as f:
async for chunk in z:
f.write(chunk)
@@ -85,7 +85,7 @@ async def test_export(tmpdir, project):
f.write("AAA")
# The .gns3 should be renamed project.gns3 in order to simplify import
with open(os.path.join(path, "test.gns3"), 'w+') as f:
with open(os.path.join(path, "test.gns3"), "w+") as f:
data = {
"topology": {
"computes": [
@@ -94,52 +94,53 @@ async def test_export(tmpdir, project):
"host": "127.0.0.1",
"name": "Remote 1",
"port": 8001,
"protocol": "http"
"protocol": "http",
}
],
"nodes": [
{
"compute_id": "6b7149c8-7d6e-4ca0-ab6b-daa8ab567be0",
"node_type": "dynamips",
"properties": {
"image": "test.image"
}
"properties": {"image": "test.image"},
}
]
],
}
}
json.dump(data, f)
with open(os.path.join(path, "vm-1", "dynamips", "test"), 'w+') as f:
with open(os.path.join(path, "vm-1", "dynamips", "test"), "w+") as f:
f.write("HELLO")
with open(os.path.join(path, "vm-1", "dynamips", "test_log.txt"), 'w+') as f:
with open(os.path.join(path, "vm-1", "dynamips", "test_log.txt"), "w+") as f:
f.write("LOG")
os.makedirs(os.path.join(path, "vm-1", "dynamips", "empty-dir"))
os.makedirs(os.path.join(path, "project-files", "snapshots"))
with open(os.path.join(path, "project-files", "snapshots", "test"), 'w+') as f:
with open(os.path.join(path, "project-files", "snapshots", "test"), "w+") as f:
f.write("WORLD")
os.symlink("/tmp/anywhere", os.path.join(path, "vm-1", "dynamips", "symlink"))
with aiozipstream.ZipFile() as z:
with patch("gns3server.compute.Dynamips.get_images_directory", return_value=str(tmpdir / "IOS"),):
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(str(tmpdir / 'zipfile.zip'), z)
await write_file(str(tmpdir / "zipfile.zip"), z)
with zipfile.ZipFile(str(tmpdir / 'zipfile.zip')) as myzip:
with zipfile.ZipFile(str(tmpdir / "zipfile.zip")) as myzip:
with myzip.open("vm-1/dynamips/test") as myfile:
content = myfile.read()
assert content == b"HELLO"
assert 'test.gns3' not in myzip.namelist()
assert 'project.gns3' in myzip.namelist()
assert 'vm-1/dynamips/empty-dir/' in myzip.namelist()
assert 'project-files/snapshots/test' not in myzip.namelist()
assert 'vm-1/dynamips/test_log.txt' not in myzip.namelist()
assert 'images/IOS/test.image' not in myzip.namelist()
assert "test.gns3" not in myzip.namelist()
assert "project.gns3" in myzip.namelist()
assert "vm-1/dynamips/empty-dir/" in myzip.namelist()
assert "project-files/snapshots/test" not in myzip.namelist()
assert "vm-1/dynamips/test_log.txt" not in myzip.namelist()
assert "images/IOS/test.image" not in myzip.namelist()
assert 'vm-1/dynamips/symlink' in myzip.namelist()
zip_info = myzip.getinfo('vm-1/dynamips/symlink')
assert "vm-1/dynamips/symlink" in myzip.namelist()
zip_info = myzip.getinfo("vm-1/dynamips/symlink")
assert stat.S_ISLNK(zip_info.external_attr >> 16)
with myzip.open("project.gns3") as myfile:
@@ -194,17 +195,9 @@ async def test_export_disallow_running(tmpdir, project, node):
path = project.path
topology = {
"topology": {
"nodes": [
{
"node_type": "dynamips"
}
]
}
}
topology = {"topology": {"nodes": [{"node_type": "dynamips"}]}}
with open(os.path.join(path, "test.gns3"), 'w+') as f:
with open(os.path.join(path, "test.gns3"), "w+") as f:
json.dump(topology, f)
node._status = "started"
@@ -221,17 +214,9 @@ async def test_export_disallow_some_type(tmpdir, project):
path = project.path
topology = {
"topology": {
"nodes": [
{
"node_type": "vmware"
}
]
}
}
topology = {"topology": {"nodes": [{"node_type": "vmware"}]}}
with open(os.path.join(path, "test.gns3"), 'w+') as f:
with open(os.path.join(path, "test.gns3"), "w+") as f:
json.dump(topology, f)
with pytest.raises(ControllerError):
@@ -241,19 +226,8 @@ async def test_export_disallow_some_type(tmpdir, project):
await export_project(z, project, str(tmpdir), allow_all_nodes=True)
# VirtualBox is always disallowed
topology = {
"topology": {
"nodes": [
{
"node_type": "virtualbox",
"properties": {
"linked_clone": True
}
}
]
}
}
with open(os.path.join(path, "test.gns3"), 'w+') as f:
topology = {"topology": {"nodes": [{"node_type": "virtualbox", "properties": {"linked_clone": True}}]}}
with open(os.path.join(path, "test.gns3"), "w+") as f:
json.dump(topology, f)
with pytest.raises(ControllerError):
with aiozipstream.ZipFile() as z:
@@ -271,30 +245,20 @@ async def test_export_fix_path(tmpdir, project):
topology = {
"topology": {
"nodes": [
{
"properties": {
"image": "/tmp/c3725-adventerprisek9-mz.124-25d.image"
},
"node_type": "dynamips"
},
{
"properties": {
"image": "gns3/webterm:lastest"
},
"node_type": "docker"
}
{"properties": {"image": "/tmp/c3725-adventerprisek9-mz.124-25d.image"}, "node_type": "dynamips"},
{"properties": {"image": "gns3/webterm:lastest"}, "node_type": "docker"},
]
}
}
with open(os.path.join(path, "test.gns3"), 'w+') as f:
with open(os.path.join(path, "test.gns3"), "w+") as f:
json.dump(topology, f)
with aiozipstream.ZipFile() as z:
await export_project(z, project, str(tmpdir))
await write_file(str(tmpdir / 'zipfile.zip'), z)
await write_file(str(tmpdir / "zipfile.zip"), z)
with zipfile.ZipFile(str(tmpdir / 'zipfile.zip')) as myzip:
with zipfile.ZipFile(str(tmpdir / "zipfile.zip")) as myzip:
with myzip.open("project.gns3") as myfile:
content = myfile.read().decode()
topology = json.loads(content)
@@ -313,28 +277,20 @@ async def test_export_with_images(tmpdir, project):
with open(str(tmpdir / "IOS" / "test.image"), "w+") as f:
f.write("AAA")
topology = {
"topology": {
"nodes": [
{
"properties": {
"image": "test.image"
},
"node_type": "dynamips"
}
]
}
}
topology = {"topology": {"nodes": [{"properties": {"image": "test.image"}, "node_type": "dynamips"}]}}
with open(os.path.join(path, "test.gns3"), 'w+') as f:
with open(os.path.join(path, "test.gns3"), "w+") as f:
json.dump(topology, f)
with aiozipstream.ZipFile() as z:
with patch("gns3server.compute.Dynamips.get_images_directory", return_value=str(tmpdir / "IOS"),):
with patch(
"gns3server.compute.Dynamips.get_images_directory",
return_value=str(tmpdir / "IOS"),
):
await export_project(z, project, str(tmpdir), include_images=True)
await write_file(str(tmpdir / 'zipfile.zip'), z)
await write_file(str(tmpdir / "zipfile.zip"), z)
with zipfile.ZipFile(str(tmpdir / 'zipfile.zip')) as myzip:
with zipfile.ZipFile(str(tmpdir / "zipfile.zip")) as myzip:
myzip.getinfo("images/IOS/test.image")
@@ -345,7 +301,7 @@ async def test_export_keep_compute_ids(tmpdir, project):
in the file
"""
with open(os.path.join(project.path, "test.gns3"), 'w+') as f:
with open(os.path.join(project.path, "test.gns3"), "w+") as f:
data = {
"topology": {
"computes": [
@@ -354,24 +310,19 @@ async def test_export_keep_compute_ids(tmpdir, project):
"host": "127.0.0.1",
"name": "Remote 1",
"port": 8001,
"protocol": "http"
"protocol": "http",
}
],
"nodes": [
{
"compute_id": "6b7149c8-7d6e-4ca0-ab6b-daa8ab567be0",
"node_type": "vpcs"
}
]
"nodes": [{"compute_id": "6b7149c8-7d6e-4ca0-ab6b-daa8ab567be0", "node_type": "vpcs"}],
}
}
json.dump(data, f)
with aiozipstream.ZipFile() as z:
await export_project(z, project, str(tmpdir), keep_compute_ids=True)
await write_file(str(tmpdir / 'zipfile.zip'), z)
await write_file(str(tmpdir / "zipfile.zip"), z)
with zipfile.ZipFile(str(tmpdir / 'zipfile.zip')) as myzip:
with zipfile.ZipFile(str(tmpdir / "zipfile.zip")) as myzip:
with myzip.open("project.gns3") as myfile:
topo = json.loads(myfile.read().decode())["topology"]
assert topo["nodes"][0]["compute_id"] == "6b7149c8-7d6e-4ca0-ab6b-daa8ab567be0"
@@ -404,36 +355,25 @@ async def test_export_images_from_vm(tmpdir, project):
mock_response.status = 200
compute.download_image = AsyncioMagicMock(return_value=mock_response)
project._project_created_on_compute.add(compute)
path = project.path
os.makedirs(os.path.join(path, "vm-1", "dynamips"))
topology = {
"topology": {
"nodes": [
{
"compute_id": "vm",
"properties": {
"image": "test.image"
},
"node_type": "dynamips"
}
]
}
"topology": {"nodes": [{"compute_id": "vm", "properties": {"image": "test.image"}, "node_type": "dynamips"}]}
}
# The .gns3 should be renamed project.gns3 in order to simplify import
with open(os.path.join(path, "test.gns3"), 'w+') as f:
with open(os.path.join(path, "test.gns3"), "w+") as f:
f.write(json.dumps(topology))
with aiozipstream.ZipFile() as z:
await export_project(z, project, str(tmpdir), include_images=True)
assert compute.list_files.called
await write_file(str(tmpdir / 'zipfile.zip'), z)
await write_file(str(tmpdir / "zipfile.zip"), z)
with zipfile.ZipFile(str(tmpdir / 'zipfile.zip')) as myzip:
with zipfile.ZipFile(str(tmpdir / "zipfile.zip")) as myzip:
with myzip.open("vm-1/dynamips/test") as myfile:
content = myfile.read()
assert content == b"HELLO"
@@ -446,7 +386,7 @@ async def test_export_images_from_vm(tmpdir, project):
@pytest.mark.asyncio
async def test_export_with_ignoring_snapshots(tmpdir, project):
with open(os.path.join(project.path, "test.gns3"), 'w+') as f:
with open(os.path.join(project.path, "test.gns3"), "w+") as f:
data = {
"topology": {
"computes": [
@@ -455,27 +395,22 @@ async def test_export_with_ignoring_snapshots(tmpdir, project):
"host": "127.0.0.1",
"name": "Remote 1",
"port": 8001,
"protocol": "http"
"protocol": "http",
}
],
"nodes": [
{
"compute_id": "6b7149c8-7d6e-4ca0-ab6b-daa8ab567be0",
"node_type": "vpcs"
}
]
"nodes": [{"compute_id": "6b7149c8-7d6e-4ca0-ab6b-daa8ab567be0", "node_type": "vpcs"}],
}
}
json.dump(data, f)
# create snapshot directory
snapshots_dir = os.path.join(project.path, 'snapshots')
snapshots_dir = os.path.join(project.path, "snapshots")
os.makedirs(snapshots_dir)
Path(os.path.join(snapshots_dir, 'snap.gns3project')).touch()
Path(os.path.join(snapshots_dir, "snap.gns3project")).touch()
with aiozipstream.ZipFile() as z:
await export_project(z, project, str(tmpdir), keep_compute_ids=True)
await write_file(str(tmpdir / 'zipfile.zip'), z)
await write_file(str(tmpdir / "zipfile.zip"), z)
with zipfile.ZipFile(str(tmpdir / 'zipfile.zip')) as myzip:
assert not os.path.join('snapshots', 'snap.gns3project') in [f.filename for f in myzip.filelist]
with zipfile.ZipFile(str(tmpdir / "zipfile.zip")) as myzip:
assert not os.path.join("snapshots", "snap.gns3project") in [f.filename for f in myzip.filelist]
+8 -7
View File
@@ -52,10 +52,15 @@ def dummy_gns3vm(controller, dummy_engine):
async def test_list(controller):
vm = GNS3VM(controller)
with asyncio_patch("gns3server.controller.gns3vm.vmware_gns3_vm.VMwareGNS3VM.list", return_value=[{"vmname": "test", "vmx_path": "test"}]):
with asyncio_patch(
"gns3server.controller.gns3vm.vmware_gns3_vm.VMwareGNS3VM.list",
return_value=[{"vmname": "test", "vmx_path": "test"}],
):
res = await vm.list("vmware")
assert res == [{"vmname": "test"}] # Information specific to VMware is stripped
with asyncio_patch("gns3server.controller.gns3vm.virtualbox_gns3_vm.VirtualBoxGNS3VM.list", return_value=[{"vmname": "test"}]):
with asyncio_patch(
"gns3server.controller.gns3vm.virtualbox_gns3_vm.VirtualBoxGNS3VM.list", return_value=[{"vmname": "test"}]
):
res = await vm.list("virtualbox")
assert res == [{"vmname": "test"}]
with pytest.raises(NotImplementedError):
@@ -73,11 +78,7 @@ async def test_json(controller):
async def test_update_settings(controller):
vm = GNS3VM(controller)
vm.settings = {
"enable": True,
"engine": "vmware",
"vmname": "GNS3 VM"
}
vm.settings = {"enable": True, "engine": "vmware", "vmname": "GNS3 VM"}
with asyncio_patch("gns3server.controller.gns3vm.vmware_gns3_vm.VMwareGNS3VM.start"):
with asyncio_patch("gns3server.controller.gns3vm.GNS3VM._check_network"):
+101 -121
View File
@@ -43,18 +43,17 @@ async def test_import_project(tmpdir, controller):
"name": "test",
"auto_open": True,
"auto_start": True,
"topology": {
},
"version": "2.0.0"
"topology": {},
"version": "2.0.0",
}
with open(str(tmpdir / "project.gns3"), 'w+') as f:
with open(str(tmpdir / "project.gns3"), "w+") as f:
json.dump(topology, f)
with open(str(tmpdir / "b.png"), 'w+') as f:
with open(str(tmpdir / "b.png"), "w+") as f:
f.write("B")
zip_path = str(tmpdir / "project.zip")
with zipfile.ZipFile(zip_path, 'w') as myzip:
with zipfile.ZipFile(zip_path, "w") as myzip:
myzip.write(str(tmpdir / "project.gns3"), "project.gns3")
myzip.write(str(tmpdir / "b.png"), "b.png")
myzip.write(str(tmpdir / "b.png"), "project-files/dynamips/test")
@@ -90,19 +89,13 @@ async def test_import_project_override(projects_dir, controller):
tmpdir = Path(projects_dir) / "override-location"
tmpdir.mkdir(parents=True, exist_ok=True)
project_id = str(uuid.uuid4())
topology = {
"project_id": project_id,
"name": "test",
"topology": {
},
"version": "2.0.0"
}
topology = {"project_id": project_id, "name": "test", "topology": {}, "version": "2.0.0"}
with open(str(tmpdir / "project.gns3"), 'w+') as f:
with open(str(tmpdir / "project.gns3"), "w+") as f:
json.dump(topology, f)
zip_path = str(tmpdir / "project.zip")
with zipfile.ZipFile(zip_path, 'w') as myzip:
with zipfile.ZipFile(zip_path, "w") as myzip:
myzip.write(str(tmpdir / "project.gns3"), "project.gns3")
with open(zip_path, "rb") as f:
@@ -120,7 +113,7 @@ async def test_import_project_override(projects_dir, controller):
async def write_file(path, z):
with open(path, 'wb') as f:
with open(path, "wb") as f:
async for chunk in z:
f.write(chunk)
@@ -136,12 +129,11 @@ def export_project_with_symlink(tmpdir, controller):
"name": "test",
"auto_open": True,
"auto_start": True,
"topology": {
},
"version": "2.0.0"
"topology": {},
"version": "2.0.0",
}
with open(os.path.join(project.path, "project.gns3"), 'w+') as f:
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"))
@@ -150,7 +142,10 @@ def export_project_with_symlink(tmpdir, controller):
zip_path = str(tmpdir / "project.zip")
with aiozipstream.ZipFile() as z:
with patch("gns3server.compute.Dynamips.get_images_directory", return_value=str(tmpdir / "IOS"),):
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)
@@ -218,19 +213,13 @@ async def test_import_upgrade(tmpdir, controller):
"""
project_id = str(uuid.uuid4())
topology = {
"project_id": str(uuid.uuid4()),
"name": "test",
"topology": {
},
"version": "1.4.2"
}
topology = {"project_id": str(uuid.uuid4()), "name": "test", "topology": {}, "version": "1.4.2"}
with open(str(tmpdir / "project.gns3"), 'w+') as f:
with open(str(tmpdir / "project.gns3"), "w+") as f:
json.dump(topology, f)
zip_path = str(tmpdir / "project.zip")
with zipfile.ZipFile(zip_path, 'w') as myzip:
with zipfile.ZipFile(zip_path, "w") as myzip:
myzip.write(str(tmpdir / "project.gns3"), "project.gns3")
with open(zip_path, "rb") as f:
@@ -245,22 +234,16 @@ async def test_import_upgrade(tmpdir, controller):
async def test_import_with_images(config, tmpdir, controller):
project_id = str(uuid.uuid4())
topology = {
"project_id": str(uuid.uuid4()),
"name": "test",
"topology": {
},
"version": "2.0.0"
}
topology = {"project_id": str(uuid.uuid4()), "name": "test", "topology": {}, "version": "2.0.0"}
with open(str(tmpdir / "project.gns3"), 'w+') as f:
with open(str(tmpdir / "project.gns3"), "w+") as f:
json.dump(topology, f)
with open(str(tmpdir / "test.image"), 'w+') as f:
with open(str(tmpdir / "test.image"), "w+") as f:
f.write("B")
zip_path = str(tmpdir / "project.zip")
with zipfile.ZipFile(zip_path, 'w') as myzip:
with zipfile.ZipFile(zip_path, "w") as myzip:
myzip.write(str(tmpdir / "project.gns3"), "project.gns3")
myzip.write(str(tmpdir / "test.image"), "images/IOS/test.image")
@@ -287,27 +270,20 @@ async def test_import_iou_linux_no_vm(linux_platform, tmpdir, controller):
"name": "test",
"type": "topology",
"topology": {
"nodes": [
{
"compute_id": "local",
"node_type": "iou",
"name": "test",
"properties": {}
}
],
"nodes": [{"compute_id": "local", "node_type": "iou", "name": "test", "properties": {}}],
"links": [],
"computes": [],
"drawings": []
"drawings": [],
},
"revision": 5,
"version": "2.0.0"
"version": "2.0.0",
}
with open(str(tmpdir / "project.gns3"), 'w+') as f:
with open(str(tmpdir / "project.gns3"), "w+") as f:
json.dump(topology, f)
zip_path = str(tmpdir / "project.zip")
with zipfile.ZipFile(zip_path, 'w') as myzip:
with zipfile.ZipFile(zip_path, "w") as myzip:
myzip.write(str(tmpdir / "project.gns3"), "project.gns3")
with open(zip_path, "rb") as f:
@@ -338,22 +314,22 @@ async def test_import_iou_linux_with_vm(linux_platform, tmpdir, controller):
"node_id": "0fd3dd4d-dc93-4a04-a9b9-7396a9e22e8b",
"node_type": "iou",
"name": "test",
"properties": {}
"properties": {},
}
],
"links": [],
"computes": [],
"drawings": []
"drawings": [],
},
"revision": 5,
"version": "2.0.0"
"version": "2.0.0",
}
with open(str(tmpdir / "project.gns3"), 'w+') as f:
with open(str(tmpdir / "project.gns3"), "w+") as f:
json.dump(topology, f)
zip_path = str(tmpdir / "project.zip")
with zipfile.ZipFile(zip_path, 'w') as myzip:
with zipfile.ZipFile(zip_path, "w") as myzip:
myzip.write(str(tmpdir / "project.gns3"), "project.gns3")
with open(zip_path, "rb") as f:
@@ -384,22 +360,22 @@ async def test_import_nat_non_linux(windows_platform, tmpdir, controller):
"node_id": "0fd3dd4d-dc93-4a04-a9b9-7396a9e22e8b",
"node_type": "nat",
"name": "test",
"properties": {}
"properties": {},
}
],
"links": [],
"computes": [],
"drawings": []
"drawings": [],
},
"revision": 5,
"version": "2.0.0"
"version": "2.0.0",
}
with open(str(tmpdir / "project.gns3"), 'w+') as f:
with open(str(tmpdir / "project.gns3"), "w+") as f:
json.dump(topology, f)
zip_path = str(tmpdir / "project.zip")
with zipfile.ZipFile(zip_path, 'w') as myzip:
with zipfile.ZipFile(zip_path, "w") as myzip:
myzip.write(str(tmpdir / "project.gns3"), "project.gns3")
with open(zip_path, "rb") as f:
@@ -430,41 +406,43 @@ async def test_import_iou_non_linux(windows_platform, tmpdir, controller):
"node_id": "0fd3dd4d-dc93-4a04-a9b9-7396a9e22e8b",
"node_type": "iou",
"name": "test",
"properties": {}
"properties": {},
},
{
"compute_id": "local",
"node_type": "vpcs",
"name": "test2",
"properties": {}
}
{"compute_id": "local", "node_type": "vpcs", "name": "test2", "properties": {}},
],
"links": [],
"computes": [],
"drawings": []
"drawings": [],
},
"revision": 5,
"version": "2.0.0"
"version": "2.0.0",
}
with open(str(tmpdir / "project.gns3"), 'w+') as f:
with open(str(tmpdir / "project.gns3"), "w+") as f:
json.dump(topology, f)
zip_path = str(tmpdir / "project.zip")
with zipfile.ZipFile(zip_path, 'w') as myzip:
with zipfile.ZipFile(zip_path, "w") as myzip:
myzip.write(str(tmpdir / "project.gns3"), "project.gns3")
with open(zip_path, "rb") as f:
with asyncio_patch("gns3server.controller.import_project._move_files_to_compute") as mock:
project = await import_project(controller, project_id, f)
controller._computes["vm"].post.assert_called_with('/projects', data={'name': 'test', 'project_id': project_id})
controller._computes["vm"].post.assert_called_with(
"/projects", data={"name": "test", "project_id": project_id}
)
with open(os.path.join(project.path, "test.gns3")) as f:
topo = json.load(f)
assert topo["topology"]["nodes"][0]["compute_id"] == "vm"
assert topo["topology"]["nodes"][1]["compute_id"] == "local"
mock.assert_called_with(controller._computes["vm"], project_id, project.path, os.path.join('project-files', 'iou', topo["topology"]["nodes"][0]['node_id']))
mock.assert_called_with(
controller._computes["vm"],
project_id,
project.path,
os.path.join("project-files", "iou", topo["topology"]["nodes"][0]["node_id"]),
)
@pytest.mark.asyncio
@@ -487,54 +465,40 @@ async def test_import_node_id(linux_platform, tmpdir, controller):
"node_id": "0fd3dd4d-dc93-4a04-a9b9-7396a9e22e8b",
"node_type": "iou",
"name": "test",
"properties": {}
"properties": {},
},
{
"compute_id": "local",
"node_id": "c3ae286c-c81f-40d9-a2d0-5874b2f2478d",
"node_type": "iou",
"name": "test2",
"properties": {}
}
"properties": {},
},
],
"links": [
{
"link_id": "b570a150-c09f-47d9-8d32-9ca5b03234d6",
"nodes": [
{
"adapter_number": 0,
"node_id": "0fd3dd4d-dc93-4a04-a9b9-7396a9e22e8b",
"port_number": 0
},
{
"adapter_number": 0,
"node_id": "c3ae286c-c81f-40d9-a2d0-5874b2f2478d",
"port_number": 0
}
]
{"adapter_number": 0, "node_id": "0fd3dd4d-dc93-4a04-a9b9-7396a9e22e8b", "port_number": 0},
{"adapter_number": 0, "node_id": "c3ae286c-c81f-40d9-a2d0-5874b2f2478d", "port_number": 0},
],
}
],
"computes": [],
"drawings": [
{
"drawing_id": "08d665ba-e982-4d54-82b4-aa0c4d5ba6a3",
"rotation": 0,
"x": -210,
"y": -108,
"z": 0
}
]
{"drawing_id": "08d665ba-e982-4d54-82b4-aa0c4d5ba6a3", "rotation": 0, "x": -210, "y": -108, "z": 0}
],
},
"revision": 5,
"version": "2.0.0"
"version": "2.0.0",
}
with open(str(tmpdir / "project.gns3"), 'w+') as f:
with open(str(tmpdir / "project.gns3"), "w+") as f:
json.dump(topology, f)
# Fake .gns3project
zip_path = str(tmpdir / "project.zip")
with zipfile.ZipFile(zip_path, 'w') as myzip:
with zipfile.ZipFile(zip_path, "w") as myzip:
myzip.write(str(tmpdir / "project.gns3"), "project.gns3")
myzip.writestr("project-files/iou/0fd3dd4d-dc93-4a04-a9b9-7396a9e22e8b/startup.cfg", "test")
myzip.writestr("project-files/iou/c3ae286c-c81f-40d9-a2d0-5874b2f2478d/startup.cfg", "test")
@@ -545,20 +509,32 @@ async def test_import_node_id(linux_platform, tmpdir, controller):
with open(os.path.join(project.path, "test.gns3")) as f:
topo = json.load(f)
# Node id should have change
assert topo["topology"]["nodes"][0]["node_id"] not in ["0fd3dd4d-dc93-4a04-a9b9-7396a9e22e8b", "c3ae286c-c81f-40d9-a2d0-5874b2f2478d"]
assert topo["topology"]["nodes"][0]["node_id"] not in [
"0fd3dd4d-dc93-4a04-a9b9-7396a9e22e8b",
"c3ae286c-c81f-40d9-a2d0-5874b2f2478d",
]
# Link should have change
link = topo["topology"]["links"][0]
assert link["link_id"] != "b570a150-c09f-47d9-8d32-9ca5b03234d6"
assert link["nodes"][0]["node_id"] not in ["0fd3dd4d-dc93-4a04-a9b9-7396a9e22e8b", "c3ae286c-c81f-40d9-a2d0-5874b2f2478d"]
assert link["nodes"][0]["node_id"] not in [
"0fd3dd4d-dc93-4a04-a9b9-7396a9e22e8b",
"c3ae286c-c81f-40d9-a2d0-5874b2f2478d",
]
# Drawing id should change
assert topo["topology"]["drawings"][0]["drawing_id"] != "08d665ba-e982-4d54-82b4-aa0c4d5ba6a3"
# Node files should have moved to the new node id
assert not os.path.exists(os.path.join(project.path, "project-files", "iou", "0fd3dd4d-dc93-4a04-a9b9-7396a9e22e8b", "startup.cfg"))
assert not os.path.exists(os.path.join(project.path, "project-files", "iou", "c3ae286c-c81f-40d9-a2d0-5874b2f2478d", "startup.cfg"))
assert os.path.exists(os.path.join(project.path, "project-files", "iou", topo["topology"]["nodes"][0]["node_id"], "startup.cfg"))
assert not os.path.exists(
os.path.join(project.path, "project-files", "iou", "0fd3dd4d-dc93-4a04-a9b9-7396a9e22e8b", "startup.cfg")
)
assert not os.path.exists(
os.path.join(project.path, "project-files", "iou", "c3ae286c-c81f-40d9-a2d0-5874b2f2478d", "startup.cfg")
)
assert os.path.exists(
os.path.join(project.path, "project-files", "iou", topo["topology"]["nodes"][0]["node_id"], "startup.cfg")
)
@pytest.mark.asyncio
@@ -581,22 +557,22 @@ async def test_import_keep_compute_ids(windows_platform, tmpdir, controller):
"node_id": "0fd3dd4d-dc93-4a04-a9b9-7396a9e22e8b",
"node_type": "iou",
"name": "test",
"properties": {}
"properties": {},
}
],
"links": [],
"computes": [],
"drawings": []
"drawings": [],
},
"revision": 5,
"version": "2.0.0"
"version": "2.0.0",
}
with open(str(tmpdir / "project.gns3"), 'w+') as f:
with open(str(tmpdir / "project.gns3"), "w+") as f:
json.dump(topology, f)
zip_path = str(tmpdir / "project.zip")
with zipfile.ZipFile(zip_path, 'w') as myzip:
with zipfile.ZipFile(zip_path, "w") as myzip:
myzip.write(str(tmpdir / "project.gns3"), "project.gns3")
with open(zip_path, "rb") as f:
@@ -619,8 +595,18 @@ async def test_move_files_to_compute(tmpdir):
with asyncio_patch("gns3server.controller.import_project._upload_file") as mock:
await _move_files_to_compute(None, project_id, str(tmpdir), os.path.join("project-files", "docker"))
mock.assert_any_call(None, project_id, str(tmpdir / "project-files" / "docker" / "test"), os.path.join("project-files", "docker", "test"))
mock.assert_any_call(None, project_id, str(tmpdir / "project-files" / "docker" / "test2"), os.path.join("project-files", "docker", "test2"))
mock.assert_any_call(
None,
project_id,
str(tmpdir / "project-files" / "docker" / "test"),
os.path.join("project-files", "docker", "test"),
)
mock.assert_any_call(
None,
project_id,
str(tmpdir / "project-files" / "docker" / "test2"),
os.path.join("project-files", "docker", "test2"),
)
assert not os.path.exists(str(tmpdir / "project-files" / "docker"))
@@ -632,19 +618,13 @@ async def test_import_project_name_and_location(projects_dir, controller):
tmpdir = Path(projects_dir)
project_id = str(uuid.uuid4())
topology = {
"project_id": str(uuid.uuid4()),
"name": "test",
"topology": {
},
"version": "2.0.0"
}
topology = {"project_id": str(uuid.uuid4()), "name": "test", "topology": {}, "version": "2.0.0"}
with open(str(tmpdir / "project.gns3"), 'w+') as f:
with open(str(tmpdir / "project.gns3"), "w+") as f:
json.dump(topology, f)
zip_path = str(tmpdir / "project.zip")
with zipfile.ZipFile(zip_path, 'w') as myzip:
with zipfile.ZipFile(zip_path, "w") as myzip:
myzip.write(str(tmpdir / "project.gns3"), "project.gns3")
with open(zip_path, "rb") as f:
+37 -44
View File
@@ -65,10 +65,10 @@ async def test_add_node(project, compute):
"port": node1._ports[0],
"adapter_number": 0,
"port_number": 4,
'label': {
'text': '0/4',
'style': 'font-family: TypeWriter;font-size: 10.0;font-weight: bold;fill: #000000;fill-opacity: 1.0;'
}
"label": {
"text": "0/4",
"style": "font-family: TypeWriter;font-size: 10.0;font-weight: bold;fill: #000000;fill-opacity: 1.0;",
},
}
]
assert project.dump.called
@@ -205,32 +205,32 @@ async def test_json(project, compute):
"node_id": node1.id,
"adapter_number": 0,
"port_number": 4,
'label': {
'text': '0/4',
'style': 'font-family: TypeWriter;font-size: 10.0;font-weight: bold;fill: #000000;fill-opacity: 1.0;'
}
"label": {
"text": "0/4",
"style": "font-family: TypeWriter;font-size: 10.0;font-weight: bold;fill: #000000;fill-opacity: 1.0;",
},
},
{
"node_id": node2.id,
"adapter_number": 1,
"port_number": 3,
'label': {
'text': '1/3',
'style': 'font-family: TypeWriter;font-size: 10.0;font-weight: bold;fill: #000000;fill-opacity: 1.0;'
}
}
"label": {
"text": "1/3",
"style": "font-family: TypeWriter;font-size: 10.0;font-weight: bold;fill: #000000;fill-opacity: 1.0;",
},
},
],
"filters": {},
"markers": {},
"show_filters_icon": True,
"link_style": {},
"suspend": False,
'wireshark': False,
"wireshark": False,
"link_type": "ethernet",
"capturing": False,
"capture_file_name": None,
"capture_file_path": None,
"capture_compute_id": None
"capture_compute_id": None,
}
assert link.asdict(topology_dump=True) == {
"link_id": link.id,
@@ -239,26 +239,26 @@ async def test_json(project, compute):
"node_id": node1.id,
"adapter_number": 0,
"port_number": 4,
'label': {
'text': '0/4',
'style': 'font-family: TypeWriter;font-size: 10.0;font-weight: bold;fill: #000000;fill-opacity: 1.0;'
}
"label": {
"text": "0/4",
"style": "font-family: TypeWriter;font-size: 10.0;font-weight: bold;fill: #000000;fill-opacity: 1.0;",
},
},
{
"node_id": node2.id,
"adapter_number": 1,
"port_number": 3,
'label': {
'text': '1/3',
'style': 'font-family: TypeWriter;font-size: 10.0;font-weight: bold;fill: #000000;fill-opacity: 1.0;'
}
}
"label": {
"text": "1/3",
"style": "font-family: TypeWriter;font-size: 10.0;font-weight: bold;fill: #000000;fill-opacity: 1.0;",
},
},
],
"link_style": {},
"filters": {},
"markers": {},
"show_filters_icon": True,
"suspend": False
"suspend": False,
}
@@ -295,7 +295,6 @@ async def test_default_capture_file_name(project, compute):
@pytest.mark.asyncio
async def test_start_capture(link):
async def fake_reader():
return AsyncioBytesIO()
@@ -355,16 +354,8 @@ async def test_update_filters(project, compute):
link.update = AsyncioMagicMock()
assert link._created
await link.update_filters({
"packet_loss": [10],
"delay": [50, 10],
"frequency_drop": [0],
"bpf": [" \n "]
})
assert link.filters == {
"packet_loss": [10],
"delay": [50, 10]
}
await link.update_filters({"packet_loss": [10], "delay": [50, 10], "frequency_drop": [0], "bpf": [" \n "]})
assert link.filters == {"packet_loss": [10], "delay": [50, 10]}
assert link.update.called
@@ -404,14 +395,16 @@ async def test_update_link_style(project, compute):
await link.add_node(node1, 0, 4)
await link.add_node(node2, 1, 3)
await link.update_link_style({
"color": "#00ff00",
"width": 3,
"type": 1,
"link_type": "flowchart",
"bezier_curviness": 150,
"flowchart_roundness": 20,
})
await link.update_link_style(
{
"color": "#00ff00",
"width": 3,
"type": 1,
"link_type": "flowchart",
"bezier_curviness": 150,
"flowchart_roundness": 20,
}
)
assert link.asdict()["link_style"] == {
"color": "#00ff00",
+49 -30
View File
@@ -99,6 +99,7 @@ async def _make_link(project, port_cls=EthernetPort, node_types=("vpcs", "vpcs")
# UDPLink.start_marker
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_start_marker_stores_entry(project):
@@ -204,8 +205,9 @@ async def test_start_marker_rejects_duplicate(project):
async def test_start_marker_rejects_invalid_bpf(project):
link = await _make_link(project)
with patch("gns3server.controller.udp_link.validate_bpf_syntax",
return_value={"valid": False, "error": "bad expression"}):
with patch(
"gns3server.controller.udp_link.validate_bpf_syntax", return_value={"valid": False, "error": "bad expression"}
):
with pytest.raises(ControllerError):
await link.start_marker("bad", "not a real bpf")
@@ -214,6 +216,7 @@ async def test_start_marker_rejects_invalid_bpf(project):
# UDPLink.stop_marker
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_stop_marker_removes(project):
@@ -260,6 +263,7 @@ async def test_stop_marker_unknown_raises(project):
# UDPLink.update_marker
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_update_marker_preserves_render_hints(project):
"""A partial update (bpf only) must not reset color/highlight_duration/tag."""
@@ -271,9 +275,9 @@ async def test_update_marker_preserves_render_hints(project):
entry = link.markers["m"]
assert entry["bpf"] == "tcp port 80"
assert entry["color"] == "#ff5722" # preserved
assert entry["highlight_duration"] == 800 # preserved
assert entry["tag"] == 1 # preserved
assert entry["color"] == "#ff5722" # preserved
assert entry["highlight_duration"] == 800 # preserved
assert entry["tag"] == 1 # preserved
@pytest.mark.asyncio
@@ -315,6 +319,7 @@ async def test_update_marker_inherited_bypass(project):
# Link.inherit_marker + persistence
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_inherit_marker_creates_global_copy(project):
@@ -408,8 +413,8 @@ async def test_load_marker_preserves_direction_and_highlight_duration(project):
# The link survives (2 attached nodes); pull it back from the project.
link = project._links[link_id]
entry = link._markers["icmp"]
assert entry["direction"] == "rx" # dropped before the fix
assert entry["highlight_duration"] == 800 # dropped before the fix
assert entry["direction"] == "rx" # dropped before the fix
assert entry["highlight_duration"] == 800 # dropped before the fix
assert entry["tag"] == 7
assert entry["color"] == "#ff5722"
assert entry["capture_node_id"] == capture_node_id
@@ -435,6 +440,7 @@ async def test_asdict_markers_runtime_vs_dump(project):
# Project-level marker definitions
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_create_marker_definition_fans_out(project):
@@ -553,23 +559,28 @@ async def test_apply_defs_to_new_link(project):
async def test_create_marker_definition_validates_bpf_once(project):
# A definition validates its BPF once (project layer); the inherited fan-out
# to every link must NOT re-validate — no tcpdump subprocess per link.
with patch("gns3server.controller.project.validate_bpf_syntax",
return_value={"valid": True, "error": None}) as proj_val, \
patch("gns3server.controller.udp_link.validate_bpf_syntax",
return_value={"valid": True, "error": None}) as link_val:
with (
patch(
"gns3server.controller.project.validate_bpf_syntax", return_value={"valid": True, "error": None}
) as proj_val,
patch(
"gns3server.controller.udp_link.validate_bpf_syntax", return_value={"valid": True, "error": None}
) as link_val,
):
await _make_link(project)
await _make_link(project)
await project.create_marker_definition("arp", "arp")
assert proj_val.call_count == 1 # validated once at the def layer
assert link_val.call_count == 0 # fan-out skipped per-link validation
assert proj_val.call_count == 1 # validated once at the def layer
assert link_val.call_count == 0 # fan-out skipped per-link validation
@pytest.mark.asyncio
async def test_create_marker_definition_rejects_invalid_bpf(project):
with patch("gns3server.controller.project.validate_bpf_syntax",
return_value={"valid": False, "error": "syntax error"}):
with patch(
"gns3server.controller.project.validate_bpf_syntax", return_value={"valid": False, "error": "syntax error"}
):
with pytest.raises(ControllerError):
await project.create_marker_definition("arp", "not a real bpf")
assert "arp" not in project.marker_definitions
@@ -579,25 +590,30 @@ async def test_create_marker_definition_rejects_invalid_bpf(project):
async def test_update_marker_definition_skips_per_link_validation(project):
# Updating a def's BPF validates once more (project); the per-link sync
# (update_marker with inherited=True) must NOT re-validate.
with patch("gns3server.controller.project.validate_bpf_syntax",
return_value={"valid": True, "error": None}) as proj_val, \
patch("gns3server.controller.udp_link.validate_bpf_syntax",
return_value={"valid": True, "error": None}) as link_val:
with (
patch(
"gns3server.controller.project.validate_bpf_syntax", return_value={"valid": True, "error": None}
) as proj_val,
patch(
"gns3server.controller.udp_link.validate_bpf_syntax", return_value={"valid": True, "error": None}
) as link_val,
):
await _make_link(project)
await _make_link(project)
await project.create_marker_definition("arp", "arp")
await project.update_marker_definition("arp", bpf="arp or rarp")
assert proj_val.call_count == 2 # once on create, once on update
assert link_val.call_count == 0 # sync skipped per-link validation
assert proj_val.call_count == 2 # once on create, once on update
assert link_val.call_count == 0 # sync skipped per-link validation
@pytest.mark.asyncio
async def test_start_marker_skips_validation_for_inherited(project):
# An inherited marker rides an already-validated definition BPF, so
# start_marker must not call validate_bpf_syntax.
with patch("gns3server.controller.udp_link.validate_bpf_syntax",
return_value={"valid": True, "error": None}) as link_val:
with patch(
"gns3server.controller.udp_link.validate_bpf_syntax", return_value={"valid": True, "error": None}
) as link_val:
link = await _make_link(project)
await link.inherit_marker("arp", {"bpf": "arp"})
@@ -608,8 +624,9 @@ async def test_start_marker_skips_validation_for_inherited(project):
@pytest.mark.asyncio
async def test_start_marker_validates_for_private(project):
# A private (non-inherited) marker still validates inline.
with patch("gns3server.controller.udp_link.validate_bpf_syntax",
return_value={"valid": True, "error": None}) as link_val:
with patch(
"gns3server.controller.udp_link.validate_bpf_syntax", return_value={"valid": True, "error": None}
) as link_val:
link = await _make_link(project)
await link.start_marker("icmp", "icmp")
@@ -636,6 +653,7 @@ async def test_markers_aggregation(project):
# Direction clear/preserve semantics (sentinel _UNSET vs explicit None)
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_update_marker_clears_direction(project):
# Explicit direction=None clears the filter back to "both directions" —
@@ -685,6 +703,7 @@ async def test_update_marker_definition_clears_direction(project):
# Capture-node routing + capability validation
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_pinned_marker_routes_only_to_chosen_node(project):
# The marker rides only the pinned capture node's NIO; the far endpoint sees nothing.
@@ -725,6 +744,7 @@ async def test_start_marker_rejects_non_capable_capture_node(project):
# Part A/B: enabled reaches uBridge + instant per-filter toggle
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_markers_for_node_keeps_disabled_and_carries_enabled(project):
# Part A: a disabled marker is NOT dropped from the NIO payload (so uBridge
@@ -790,6 +810,7 @@ async def test_update_marker_ui_only_does_not_push(project):
# Per-definition pause/resume (toggle every inherited global-{name} copy)
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_pause_marker_definition_toggles_copies_off(project):
with _valid_bpf():
@@ -843,7 +864,7 @@ async def test_create_marker_definition_rejects_directional(project):
@pytest.mark.asyncio
async def test_create_marker_definition_allows_both(project):
await project.create_marker_definition("arp", "arp") # default both
await project.create_marker_definition("arp", "arp") # default both
await project.create_marker_definition("icmp", "icmp", direction=None)
assert project.marker_definitions["arp"]["direction"] is None
assert project.marker_definitions["icmp"]["direction"] is None
@@ -851,7 +872,7 @@ async def test_create_marker_definition_allows_both(project):
@pytest.mark.asyncio
async def test_update_marker_definition_rejects_directional(project):
await project.create_marker_definition("arp", "arp") # both
await project.create_marker_definition("arp", "arp") # both
with pytest.raises(ControllerError):
await project.update_marker_definition("arp", direction="tx")
# omitted direction and explicit clear-to-both are both fine
@@ -872,6 +893,4 @@ async def test_stop_marker_deletes_capture_pcap(project):
capture.delete = AsyncioMagicMock()
await link.stop_marker("icmp")
capture.delete.assert_called_once_with(
"/adapters/0/ports/0/markers/icmp", params={"link_id": link.id}
)
capture.delete.assert_called_once_with("/adapters/0/ports/0/markers/icmp", params={"link_id": link.id})
+201 -135
View File
@@ -98,8 +98,9 @@ def _icmp_frame():
icmp = bytes([8, 0, 0, 0]) + struct.pack(">HHH", 1, 1, 0) + b"payload12"
icmp = icmp[:2] + struct.pack(">H", _cksum(icmp)) + icmp[4:]
ip0 = struct.pack(">BBHHHBBH4s4s", 0x45, 0, 20 + len(icmp), 1, 0, 64, 1, 0,
bytes([10, 0, 0, 1]), bytes([10, 0, 0, 3]))
ip0 = struct.pack(
">BBHHHBBH4s4s", 0x45, 0, 20 + len(icmp), 1, 0, 64, 1, 0, bytes([10, 0, 0, 1]), bytes([10, 0, 0, 3])
)
ip = ip0[:10] + struct.pack(">H", _cksum(ip0)) + ip0[12:]
return bytes.fromhex("0200000000020200000000010800") + ip + icmp
@@ -108,8 +109,9 @@ def _tcp_syn_frame():
"""A minimal TCP SYN (10.0.0.1:472 → 10.0.0.3:22)."""
tcp = struct.pack(">HHIIBBHHH", 472, 22, 0, 0, 0x50, 0x02, 64240, 0, 0)
ip0 = struct.pack(">BBHHHBBH4s4s", 0x45, 0, 20 + len(tcp), 2, 0, 64, 6, 0,
bytes([10, 0, 0, 1]), bytes([10, 0, 0, 3]))
ip0 = struct.pack(
">BBHHHBBH4s4s", 0x45, 0, 20 + len(tcp), 2, 0, 64, 6, 0, bytes([10, 0, 0, 1]), bytes([10, 0, 0, 3])
)
ip = ip0[:10] + struct.pack(">H", _cksum(ip0)) + ip0[12:]
tcp = tcp[:16] + struct.pack(">H", _cksum(ip0[12:] + tcp)) + tcp[18:]
return bytes.fromhex("0200000000020200000000010800") + ip + tcp
@@ -122,10 +124,17 @@ def _fake_project(tmp_path, markers, markers_dir=None):
def _marker_entry(tag, enabled=True, node_id="node-1"):
return {"bpf": "icmp", "tag": tag, "enabled": enabled, "color": None,
"highlight_duration": None, "capture_node_id": node_id,
"direction": None, "data_link_type": "DLT_EN10MB",
"node_id": node_id}
return {
"bpf": "icmp",
"tag": tag,
"enabled": enabled,
"color": None,
"highlight_duration": None,
"capture_node_id": node_id,
"direction": None,
"data_link_type": "DLT_EN10MB",
"node_id": node_id,
}
def _fake_columns(monkeypatch, mapping):
@@ -142,8 +151,7 @@ def _fake_columns(monkeypatch, mapping):
def _cols(src="10.0.0.1", dst="10.0.0.3", proto="ICMP", info="Echo (ping) request"):
return {"src": src, "dst": dst, "proto": proto, "info": info,
"bg": "ffffff", "fg": "000000"}
return {"src": src, "dst": dst, "proto": proto, "info": info, "bg": "ffffff", "fg": "000000"}
class _FakeSession:
@@ -180,17 +188,20 @@ def _pretend_sharkd(monkeypatch):
# pcap scanning (engine-free backbone)
# ---------------------------------------------------------------------------
class TestScanPcap:
class TestScanPcap:
async def test_scans_frames_and_truncated_tail(self, tmp_path):
pcap = tmp_path / "a.pcap"
_write_pcap(pcap, [
(1693472000, 123456, b"x" * 60),
(1693472001, 654321, b"y" * 40),
])
_write_pcap(
pcap,
[
(1693472000, 123456, b"x" * 60),
(1693472001, 654321, b"y" * 40),
],
)
# Tear the final record in half: a snapshot mid-write must not raise.
data = bytearray(pcap.read_bytes())
pcap.write_bytes(data[:len(data) - 20])
pcap.write_bytes(data[: len(data) - 20])
frames = scan_pcap_frames(str(pcap))
assert frames == [(1693472000, 123456, 60)]
@@ -202,10 +213,13 @@ class TestScanPcap:
async def test_read_frame_bytes_offsets(self, tmp_path):
pcap = tmp_path / "b.pcap"
_write_pcap(pcap, [
(100, 0, b"first" + b"0" * 55), # 60 bytes
(200, 0, b"second"), # 6 bytes
])
_write_pcap(
pcap,
[
(100, 0, b"first" + b"0" * 55), # 60 bytes
(200, 0, b"second"), # 6 bytes
],
)
assert read_frame_bytes(str(pcap), 2) == b"second".hex()
assert read_frame_bytes(str(pcap), 1) == (b"first" + b"0" * 55).hex()
assert read_frame_bytes(str(pcap), 3) is None
@@ -221,18 +235,21 @@ class TestScanPcap:
# Tag gate (engine-free — raised before any sharkd work)
# ---------------------------------------------------------------------------
class TestGate:
class TestGate:
async def test_unknown_tag_404(self, tmp_path):
project = _fake_project(tmp_path, {"linkA/icmp": _marker_entry(tag=1)})
with pytest.raises(ControllerNotFoundError):
await build_timeline(project, tag=7)
async def test_gate_409_while_capturing(self, tmp_path):
project = _fake_project(tmp_path, {
"linkA/icmp": _marker_entry(tag=7, enabled=False, node_id="n1"),
"linkB/icmp": _marker_entry(tag=7, enabled=True, node_id="n2"),
})
project = _fake_project(
tmp_path,
{
"linkA/icmp": _marker_entry(tag=7, enabled=False, node_id="n1"),
"linkB/icmp": _marker_entry(tag=7, enabled=True, node_id="n2"),
},
)
with pytest.raises(ControllerError, match="linkB"):
await build_timeline(project, tag=7)
@@ -247,26 +264,38 @@ class TestGate:
# Timeline assembly (columns injected — no engine needed)
# ---------------------------------------------------------------------------
class TestTimeline:
class TestTimeline:
async def test_merge_orders_by_ts_with_stable_tiebreak(self, tmp_path, monkeypatch):
# Two sources, deliberately interleaved in time, colliding on one µs.
_write_pcap(tmp_path / "n1_linkA_icmp.pcap", [
(1693472000, 500000, b"a" * 60), # t1 sourceA
(1693472002, 000000, b"a" * 60), # t3 sourceA
])
_write_pcap(tmp_path / "n2_linkB_icmp.pcap", [
(1693472001, 000000, b"b" * 60), # t2 sourceB
(1693472002, 000000, b"b" * 60), # t3 sourceB — same µs as t3 sourceA
])
_fake_columns(monkeypatch, {
"n1_linkA_icmp.pcap": {1: _cols(), 2: _cols()},
"n2_linkB_icmp.pcap": {1: _cols(src="10.0.0.2"), 2: _cols(src="10.0.0.2")},
})
project = _fake_project(tmp_path, {
"linkA/icmp": _marker_entry(tag=7, enabled=False, node_id="n1"),
"linkB/icmp": _marker_entry(tag=7, enabled=False, node_id="n2"),
})
_write_pcap(
tmp_path / "n1_linkA_icmp.pcap",
[
(1693472000, 500000, b"a" * 60), # t1 sourceA
(1693472002, 000000, b"a" * 60), # t3 sourceA
],
)
_write_pcap(
tmp_path / "n2_linkB_icmp.pcap",
[
(1693472001, 000000, b"b" * 60), # t2 sourceB
(1693472002, 000000, b"b" * 60), # t3 sourceB — same µs as t3 sourceA
],
)
_fake_columns(
monkeypatch,
{
"n1_linkA_icmp.pcap": {1: _cols(), 2: _cols()},
"n2_linkB_icmp.pcap": {1: _cols(src="10.0.0.2"), 2: _cols(src="10.0.0.2")},
},
)
project = _fake_project(
tmp_path,
{
"linkA/icmp": _marker_entry(tag=7, enabled=False, node_id="n1"),
"linkB/icmp": _marker_entry(tag=7, enabled=False, node_id="n2"),
},
)
timeline = await build_timeline(project, tag=7)
assert timeline["frame_count"] == 4
@@ -306,10 +335,14 @@ class TestTimeline:
async def test_frame_list_is_uncapped(self, tmp_path, monkeypatch):
# The list is the whole contract — no truncation flag, no buckets,
# however many frames the tag holds.
_write_pcap(tmp_path / "n1_linkA_icmp.pcap", [
(1693472000, 0, b"a" * 60), (1693472000, 500000, b"a" * 60),
(1693472001, 0, b"a" * 60),
])
_write_pcap(
tmp_path / "n1_linkA_icmp.pcap",
[
(1693472000, 0, b"a" * 60),
(1693472000, 500000, b"a" * 60),
(1693472001, 0, b"a" * 60),
],
)
_fake_columns(monkeypatch, {"n1_linkA_icmp.pcap": {1: _cols(), 2: _cols(), 3: _cols()}})
project = _fake_project(tmp_path, {"linkA/icmp": _marker_entry(tag=7, enabled=False, node_id="n1")})
@@ -321,11 +354,14 @@ class TestTimeline:
async def test_filter_applies_before_count_and_slice(self, tmp_path, monkeypatch):
# Three frames; the injected "matching set" (what a real engine would
# return for the filter) contains only frames 1 and 3.
_write_pcap(tmp_path / "n1_linkA_icmp.pcap", [
(1693472000, 0, b"a" * 60),
(1693472001, 0, b"a" * 60),
(1693472002, 0, b"a" * 60),
])
_write_pcap(
tmp_path / "n1_linkA_icmp.pcap",
[
(1693472000, 0, b"a" * 60),
(1693472001, 0, b"a" * 60),
(1693472002, 0, b"a" * 60),
],
)
_fake_columns(monkeypatch, {"n1_linkA_icmp.pcap": {1: _cols(), 3: _cols(proto="TCP")}})
project = _fake_project(tmp_path, {"linkA/icmp": _marker_entry(tag=7, enabled=False, node_id="n1")})
@@ -346,14 +382,20 @@ class TestLinkFilter:
counting/slicing/bucketing; sources stay the full inventory."""
def _project(self, tmp_path, monkeypatch, columns_override=None):
_write_pcap(tmp_path / "n1_linkA_icmp.pcap", [
(1693472000, 500000, b"a" * 60),
(1693472002, 000000, b"a" * 60),
])
_write_pcap(tmp_path / "n2_linkB_icmp.pcap", [
(1693472001, 000000, b"b" * 60),
(1693472002, 000000, b"b" * 60),
])
_write_pcap(
tmp_path / "n1_linkA_icmp.pcap",
[
(1693472000, 500000, b"a" * 60),
(1693472002, 000000, b"a" * 60),
],
)
_write_pcap(
tmp_path / "n2_linkB_icmp.pcap",
[
(1693472001, 000000, b"b" * 60),
(1693472002, 000000, b"b" * 60),
],
)
async def fake_columns(pcap, filter_expr):
if columns_override is not None:
@@ -362,10 +404,13 @@ class TestLinkFilter:
return {1: _cols(src=src), 2: _cols(src=src)}
monkeypatch.setattr(marker_replay, "_columns_for", fake_columns)
return _fake_project(tmp_path, {
"linkA/icmp": _marker_entry(tag=7, enabled=False, node_id="n1"),
"linkB/icmp": _marker_entry(tag=7, enabled=False, node_id="n2"),
})
return _fake_project(
tmp_path,
{
"linkA/icmp": _marker_entry(tag=7, enabled=False, node_id="n1"),
"linkB/icmp": _marker_entry(tag=7, enabled=False, node_id="n2"),
},
)
async def test_link_narrows_before_count_and_slice(self, tmp_path, monkeypatch):
timeline = await build_timeline(self._project(tmp_path, monkeypatch), tag=7, link_id="linkA")
@@ -373,9 +418,7 @@ class TestLinkFilter:
assert timeline["start"] == "1693472000.500000"
assert [f["link_id"] for f in timeline["frames"]] == ["linkA", "linkA"]
# sources stay the FULL inventory with engine-free totals.
assert sorted((s["link_id"], s["count"]) for s in timeline["sources"]) == [
("linkA", 2), ("linkB", 2)
]
assert sorted((s["link_id"], s["count"]) for s in timeline["sources"]) == [("linkA", 2), ("linkB", 2)]
async def test_unknown_link_is_empty_success(self, tmp_path, monkeypatch):
timeline = await build_timeline(self._project(tmp_path, monkeypatch), tag=7, link_id="nope")
@@ -403,35 +446,35 @@ class TestLinkFilter:
async def test_query_frames_window_over_link_stream(self, tmp_path, monkeypatch):
project = self._project(tmp_path, monkeypatch)
result = await query_frames(project, tag=7, ts="1693472002.000000",
window_ms=0, link_id="linkB")
result = await query_frames(project, tag=7, ts="1693472002.000000", window_ms=0, link_id="linkB")
assert [f["link_id"] for f in result["frames"]] == ["linkB"]
class TestQueryFrames:
def _project(self, tmp_path, monkeypatch):
_write_pcap(tmp_path / "n1_linkA_icmp.pcap", [
(1693472000, 0, b"a" * 60),
(1693472000, 150000, b"a" * 60),
(1693472005, 0, b"a" * 60),
])
_write_pcap(
tmp_path / "n1_linkA_icmp.pcap",
[
(1693472000, 0, b"a" * 60),
(1693472000, 150000, b"a" * 60),
(1693472005, 0, b"a" * 60),
],
)
_fake_columns(monkeypatch, {"n1_linkA_icmp.pcap": {i: _cols() for i in (1, 2, 3)}})
return _fake_project(tmp_path, {"linkA/icmp": _marker_entry(tag=7, enabled=False, node_id="n1")})
async def test_window_inclusive_bounds(self, tmp_path, monkeypatch):
result = await query_frames(self._project(tmp_path, monkeypatch), tag=7,
ts="1693472000.000000", window_ms=150)
result = await query_frames(self._project(tmp_path, monkeypatch), tag=7, ts="1693472000.000000", window_ms=150)
assert [f["ts"] for f in result["frames"]] == ["1693472000.000000", "1693472000.150000"]
async def test_window_miss_is_empty_success(self, tmp_path, monkeypatch):
result = await query_frames(self._project(tmp_path, monkeypatch), tag=7,
ts="1693472001.000000", window_ms=100)
result = await query_frames(self._project(tmp_path, monkeypatch), tag=7, ts="1693472001.000000", window_ms=100)
assert result == {"frames": []}
async def test_limit_applies(self, tmp_path, monkeypatch):
result = await query_frames(self._project(tmp_path, monkeypatch), tag=7,
ts="1693472000.000000", window_ms=150, limit=1)
result = await query_frames(
self._project(tmp_path, monkeypatch), tag=7, ts="1693472000.000000", window_ms=150, limit=1
)
assert len(result["frames"]) == 1
@@ -439,20 +482,30 @@ class TestQueryFrames:
# Tree key renaming
# ---------------------------------------------------------------------------
class TestRename:
class TestRename:
async def test_renames_closed_key_set_and_drops_hf_id(self):
node = {
"t": "proto", "l": "Time to Live: 64", "fn": "ip.ttl",
"f": "ip.ttl == 64", "h": [22, 1], "s": None, "g": False,
"t": "proto",
"l": "Time to Live: 64",
"fn": "ip.ttl",
"f": "ip.ttl == 64",
"h": [22, 1],
"s": None,
"g": False,
"e": 8472,
"n": [{"l": "nested", "h": [23, 2], "n": []}],
}
renamed = _rename_value(node)
assert renamed == {
"element": "proto", "label": "Time to Live: 64", "name": "ip.ttl",
"filter_expr": "ip.ttl == 64", "pos": 22, "size": 1,
"expert": None, "generated": False,
"element": "proto",
"label": "Time to Live: 64",
"name": "ip.ttl",
"filter_expr": "ip.ttl == 64",
"pos": 22,
"size": 1,
"expert": None,
"generated": False,
"children": [{"label": "nested", "pos": 23, "size": 2, "children": []}],
}
@@ -470,8 +523,8 @@ class TestRename:
# sharkd sessions + engine-backed behaviour
# ---------------------------------------------------------------------------
class TestSessions:
class TestSessions:
async def test_missing_sharkd_raises_501_error(self, tmp_path):
_write_pcap(tmp_path / "n1_linkA_icmp.pcap", [(1693472000, 0, b"a" * 60)])
project = _fake_project(tmp_path, {"linkA/icmp": _marker_entry(tag=7, enabled=False, node_id="n1")})
@@ -539,9 +592,7 @@ class TestSessions:
pcap = tmp_path / "n1_linkA_icmp.pcap"
_write_pcap(pcap, [(1693472000, 0, b"a" * 60)])
with patch.object(manager, "_spawn", side_effect=slow_spawn):
first, second = await asyncio.gather(
manager._acquire(str(pcap)), manager._acquire(str(pcap))
)
first, second = await asyncio.gather(manager._acquire(str(pcap)), manager._acquire(str(pcap)))
assert first is second # concurrent requests share one spawn
assert len(spawns) == 1
await manager.close_all()
@@ -553,12 +604,12 @@ class TestSessions:
# A process that never answers: the session must die (never serve the
# late reply to a later request) instead of staying resident.
proc = await asyncio.create_subprocess_exec(
"sleep", "60",
stdin=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE,
)
session = marker_replay._SharkdSession(
str(pcap), str(tmp_path / "scratch"), "unused", proc, os.stat(str(pcap))
"sleep",
"60",
stdin=asyncio.subprocess.PIPE,
stdout=asyncio.subprocess.PIPE,
)
session = marker_replay._SharkdSession(str(pcap), str(tmp_path / "scratch"), "unused", proc, os.stat(str(pcap)))
with pytest.raises(SharkdError, match="timed out"):
await session.rpc("frames", {})
assert session.alive() is False
@@ -579,12 +630,12 @@ class TestSessions:
pcap = tmp_path / "n1_linkA_icmp.pcap"
_write_pcap(pcap, [(1693472000, 0, b"a" * 60)])
proc = await asyncio.create_subprocess_exec(
sys.executable, str(fake),
stdin=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE,
)
session = marker_replay._SharkdSession(
str(pcap), str(tmp_path / "scratch"), "unused", proc, os.stat(str(pcap))
sys.executable,
str(fake),
stdin=asyncio.subprocess.PIPE,
stdout=asyncio.subprocess.PIPE,
)
session = marker_replay._SharkdSession(str(pcap), str(tmp_path / "scratch"), "unused", proc, os.stat(str(pcap)))
with pytest.raises(SharkdError, match="id mismatch"):
await session.rpc("frames", {})
assert session.alive() is False
@@ -635,10 +686,13 @@ class TestSessions:
@sharkd_present
async def test_real_session_columns_and_filter(self, tmp_path):
_write_pcap(tmp_path / "n1_linkA_icmp.pcap", [
(1693472000, 123456, _icmp_frame()),
(1693472001, 0, _tcp_syn_frame()),
])
_write_pcap(
tmp_path / "n1_linkA_icmp.pcap",
[
(1693472000, 123456, _icmp_frame()),
(1693472001, 0, _tcp_syn_frame()),
],
)
manager = marker_replay._get_manager()
try:
columns = await marker_replay._columns_for(str(tmp_path / "n1_linkA_icmp.pcap"), None)
@@ -683,47 +737,51 @@ class TestSessions:
class TestDecodeFrame:
def _project(self, tmp_path):
_write_pcap(tmp_path / "n1_linkA_icmp.pcap", [
(1693472000, 123456, _icmp_frame()),
])
return _fake_project(tmp_path, {
"linkA/icmp": _marker_entry(tag=7, enabled=False, node_id="n1"),
})
_write_pcap(
tmp_path / "n1_linkA_icmp.pcap",
[
(1693472000, 123456, _icmp_frame()),
],
)
return _fake_project(
tmp_path,
{
"linkA/icmp": _marker_entry(tag=7, enabled=False, node_id="n1"),
},
)
async def test_ts_mismatch_guard_404(self, tmp_path):
project = self._project(tmp_path)
with pytest.raises(ControllerNotFoundError, match="rebuilt"):
await decode_frame(project, tag=7, ts="1.000000",
node_id="n1", link_id="linkA", marker="icmp")
await decode_frame(project, tag=7, ts="1.000000", node_id="n1", link_id="linkA", marker="icmp")
async def test_unknown_source_404(self, tmp_path):
project = self._project(tmp_path)
with pytest.raises(ControllerNotFoundError):
await decode_frame(project, tag=7, ts="1693472000.123456",
node_id="nobody", link_id="linkA", marker="icmp")
await decode_frame(project, tag=7, ts="1693472000.123456", node_id="nobody", link_id="linkA", marker="icmp")
async def test_explicit_frame_number_out_of_range_404(self, tmp_path):
project = self._project(tmp_path)
with pytest.raises(ControllerNotFoundError, match="rebuilt"):
await decode_frame(project, tag=7, ts="1693472000.123456",
node_id="n1", link_id="linkA", marker="icmp", frame_number=5)
await decode_frame(
project, tag=7, ts="1693472000.123456", node_id="n1", link_id="linkA", marker="icmp", frame_number=5
)
async def test_explicit_frame_number_ts_mismatch_404(self, tmp_path):
# The frame number must still land on the exact round-tripped ts —
# a rebuilt capture cannot be decoded by stale coordinates.
project = self._project(tmp_path)
with pytest.raises(ControllerNotFoundError, match="rebuilt"):
await decode_frame(project, tag=7, ts="1693472001.000000",
node_id="n1", link_id="linkA", marker="icmp", frame_number=1)
await decode_frame(
project, tag=7, ts="1693472001.000000", node_id="n1", link_id="linkA", marker="icmp", frame_number=1
)
async def test_hex_read_failure_is_404_not_null_hex(self, tmp_path, monkeypatch):
project = self._project(tmp_path)
monkeypatch.setattr(marker_replay, "read_frame_bytes", lambda path, n: None)
with pytest.raises(ControllerNotFoundError, match="rebuilt"):
await decode_frame(project, tag=7, ts="1693472000.123456",
node_id="n1", link_id="linkA", marker="icmp")
await decode_frame(project, tag=7, ts="1693472000.123456", node_id="n1", link_id="linkA", marker="icmp")
@sharkd_present
async def test_same_microsecond_frames_disambiguated_by_frame_number(self, tmp_path):
@@ -732,16 +790,20 @@ class TestDecodeFrame:
pcap = tmp_path / "n1_linkA_icmp.pcap"
icmp, tcp = _icmp_frame(), _tcp_syn_frame()
_write_pcap(pcap, [(1693472000, 123456, icmp), (1693472000, 123456, tcp)])
project = _fake_project(tmp_path, {
"linkA/icmp": _marker_entry(tag=7, enabled=False, node_id="n1"),
})
project = _fake_project(
tmp_path,
{
"linkA/icmp": _marker_entry(tag=7, enabled=False, node_id="n1"),
},
)
manager = marker_replay._get_manager()
try:
default = await decode_frame(project, tag=7, ts="1693472000.123456",
node_id="n1", link_id="linkA", marker="icmp")
second = await decode_frame(project, tag=7, ts="1693472000.123456",
node_id="n1", link_id="linkA", marker="icmp",
frame_number=2)
default = await decode_frame(
project, tag=7, ts="1693472000.123456", node_id="n1", link_id="linkA", marker="icmp"
)
second = await decode_frame(
project, tag=7, ts="1693472000.123456", node_id="n1", link_id="linkA", marker="icmp", frame_number=2
)
finally:
await manager.close_all()
# Without a frame number: first ts match.
@@ -757,8 +819,9 @@ class TestDecodeFrame:
manager = marker_replay._get_manager()
try:
project = self._project(tmp_path)
detail = await decode_frame(project, tag=7, ts="1693472000.123456",
node_id="n1", link_id="linkA", marker="icmp")
detail = await decode_frame(
project, tag=7, ts="1693472000.123456", node_id="n1", link_id="linkA", marker="icmp"
)
finally:
await manager.close_all()
@@ -789,10 +852,13 @@ class TestDecodeFrame:
trees, sharkd's raw key set stays within the census-known keys."""
pcap = tmp_path / "proto_mix.pcap"
_write_pcap(pcap, [
(1693472000, 100000, _icmp_frame()),
(1693472001, 0, _tcp_syn_frame()),
])
_write_pcap(
pcap,
[
(1693472000, 100000, _icmp_frame()),
(1693472001, 0, _tcp_syn_frame()),
],
)
manager = marker_replay._get_manager()
try:
known = set(marker_replay._KEY_RENAME) | {"h", "e"}
+203 -178
View File
@@ -40,11 +40,15 @@ def compute():
@pytest.fixture
def node(compute, project):
node = Node(project, compute, "demo",
node_id=str(uuid.uuid4()),
node_type="vpcs",
console_type="vnc",
properties={"startup_script": "echo test"})
node = Node(
project,
compute,
"demo",
node_id=str(uuid.uuid4()),
node_type="vpcs",
console_type="vnc",
properties={"startup_script": "echo test"},
)
return node
@@ -55,22 +59,29 @@ def test_docker_iol_ports_grouped_in_four_port_units(compute, project):
the native IOU node type. Plain docker nodes keep the flat eth naming.
"""
node = Node(project, compute, "iol",
node_id=str(uuid.uuid4()),
node_type="docker",
properties={"adapters": 2, "environment": "GNS3_IOL_RUNNER=1"})
node = Node(
project,
compute,
"iol",
node_id=str(uuid.uuid4()),
node_type="docker",
properties={"adapters": 2, "environment": "GNS3_IOL_RUNNER=1"},
)
ports = node.ports
assert [p.asdict()["name"] for p in ports] == [
"Ethernet0/0", "Ethernet0/1", "Ethernet0/2", "Ethernet0/3",
"Ethernet1/0", "Ethernet1/1", "Ethernet1/2", "Ethernet1/3",
"Ethernet0/0",
"Ethernet0/1",
"Ethernet0/2",
"Ethernet0/3",
"Ethernet1/0",
"Ethernet1/1",
"Ethernet1/2",
"Ethernet1/3",
]
assert (ports[5].adapter_number, ports[5].port_number) == (1, 1)
assert ports[5].short_name == "e1/1"
node = Node(project, compute, "web",
node_id=str(uuid.uuid4()),
node_type="docker",
properties={"adapters": 2})
node = Node(project, compute, "web", node_id=str(uuid.uuid4()), node_type="docker", properties={"adapters": 2})
assert [p.asdict()["name"] for p in node.ports] == ["eth0", "eth1"]
@@ -82,10 +93,14 @@ def test_docker_iol_startup_config_materialized_once(compute, project, monkeypat
afterwards the node's configuration lives in its NVRAM on the compute.
"""
node = Node(project, compute, "iol",
node_id=str(uuid.uuid4()),
node_type="docker",
properties={"environment": "GNS3_IOL_RUNNER=1\nGNS3_IOL_STARTUP_CONFIG=my-iol-config.txt"})
node = Node(
project,
compute,
"iol",
node_id=str(uuid.uuid4()),
node_type="docker",
properties={"environment": "GNS3_IOL_RUNNER=1\nGNS3_IOL_STARTUP_CONFIG=my-iol-config.txt"},
)
monkeypatch.setattr(node, "_base_config_file_content", lambda path: "hostname %h\n")
data = node._node_data()
@@ -100,10 +115,14 @@ def test_docker_iol_startup_config_materialized_once(compute, project, monkeypat
def test_docker_iol_startup_config_missing_file_keeps_knob(compute, project, monkeypatch):
node = Node(project, compute, "iol",
node_id=str(uuid.uuid4()),
node_type="docker",
properties={"environment": "GNS3_IOL_RUNNER=1\nGNS3_IOL_STARTUP_CONFIG=gone.txt"})
node = Node(
project,
compute,
"iol",
node_id=str(uuid.uuid4()),
node_type="docker",
properties={"environment": "GNS3_IOL_RUNNER=1\nGNS3_IOL_STARTUP_CONFIG=gone.txt"},
)
monkeypatch.setattr(node, "_base_config_file_content", lambda path: None)
data = node._node_data()
@@ -132,23 +151,35 @@ def test_name(compute, project):
If node use a name template generate names
"""
node = Node(project, compute, "PC",
node_id=str(uuid.uuid4()),
node_type="vpcs",
console_type="vnc",
properties={"startup_script": "echo test"})
node = Node(
project,
compute,
"PC",
node_id=str(uuid.uuid4()),
node_type="vpcs",
console_type="vnc",
properties={"startup_script": "echo test"},
)
assert node.name == "PC"
node = Node(project, compute, "PC{0}",
node_id=str(uuid.uuid4()),
node_type="vpcs",
console_type="vnc",
properties={"startup_script": "echo test"})
node = Node(
project,
compute,
"PC{0}",
node_id=str(uuid.uuid4()),
node_type="vpcs",
console_type="vnc",
properties={"startup_script": "echo test"},
)
assert node.name == "PC1"
node = Node(project, compute, "PC{0}",
node_id=str(uuid.uuid4()),
node_type="vpcs",
console_type="vnc",
properties={"startup_script": "echo test"})
node = Node(
project,
compute,
"PC{0}",
node_id=str(uuid.uuid4()),
node_type="vpcs",
console_type="vnc",
properties={"startup_script": "echo test"},
)
assert node.name == "PC2"
@@ -158,10 +189,7 @@ def test_vmname(compute, project):
field
"""
node = Node(project, compute, "PC",
node_id=str(uuid.uuid4()),
node_type="virtualbox",
vmname="test")
node = Node(project, compute, "PC", node_id=str(uuid.uuid4()), node_type="virtualbox", vmname="test")
assert node.properties["vmname"] == "test"
@@ -169,13 +197,9 @@ def test_empty_properties(compute, project):
"""
Empty properties need to be ignored
"""
node = Node(project, compute, "PC",
node_id=str(uuid.uuid4()),
node_type="virtualbox",
aa="",
bb=None,
category=2,
cc="xx")
node = Node(
project, compute, "PC", node_id=str(uuid.uuid4()), node_type="virtualbox", aa="", bb=None, category=2, cc="xx"
)
assert "aa" not in node.properties
assert "bb" not in node.properties
assert "cc" in node.properties
@@ -188,7 +212,9 @@ async def test_eq(compute, project, node, controller):
assert node == Node(project, compute, "demo1", node_id=node.id, node_type="qemu")
assert node != "a"
assert node != Node(project, compute, "demo2", node_id=str(uuid.uuid4()), node_type="qemu")
assert node != Node(Project(str(uuid.uuid4()), controller=controller), compute, "demo3", node_id=node.id, node_type="qemu")
assert node != Node(
Project(str(uuid.uuid4()), controller=controller), compute, "demo3", node_id=node.id, node_type="qemu"
)
def test_json(node, compute):
@@ -233,11 +259,11 @@ def test_json(node, compute):
"link_type": "ethernet",
"name": "Ethernet0",
"port_number": 0,
"short_name": "e0"
"short_name": "e0",
}
],
"missing_image": False,
"missing_images": []
"missing_images": [],
}
assert node.asdict(topology_dump=True) == {
@@ -272,9 +298,7 @@ def test_json(node, compute):
def test_init_without_uuid(project, compute):
node = Node(project, compute, "demo",
node_type="vpcs",
console_type="vnc")
node = Node(project, compute, "demo", node_type="vpcs", console_type="vnc")
assert node.id is not None
@@ -287,13 +311,7 @@ async def test_create(node, compute):
compute.post = AsyncioMagicMock(return_value=response)
assert await node.create() is True
data = {
"console": 2048,
"console_type": "vnc",
"node_id": node.id,
"startup_script": "echo test",
"name": "demo"
}
data = {"console": 2048, "console_type": "vnc", "node_id": node.id, "startup_script": "echo test", "name": "demo"}
compute.post.assert_called_with("/projects/{}/vpcs/nodes".format(node.project.id), data=data, timeout=1200)
assert node._console == 2048
assert node._properties == {"startup_script": "echo test"}
@@ -319,7 +337,7 @@ async def test_create_image_missing(node, compute):
node._upload_missing_image = AsyncioMagicMock(return_value=True)
assert await node.create() is True
#assert node._upload_missing_image.called is True
# assert node._upload_missing_image.called is True
@pytest.mark.asyncio
@@ -330,15 +348,19 @@ async def test_create_image_missing_kept_in_degraded_state(project, compute, tmp
"""
config.settings.Server.images_path = str(tmpdir)
node = Node(project, compute, "r1",
node_id=str(uuid.uuid4()),
node_type="qemu",
properties={"hda_disk_image": "missing.qcow2", "ram": 256})
node = Node(
project,
compute,
"r1",
node_id=str(uuid.uuid4()),
node_type="qemu",
properties={"hda_disk_image": "missing.qcow2", "ram": 256},
)
async def resp(*args, **kwargs):
raise ComputeConflictError(
"/projects/{}/qemu/nodes".format(project.id),
{"message": "The image is missing", "image": "missing.qcow2", "exception": "ImageMissingError"}
{"message": "The image is missing", "image": "missing.qcow2", "exception": "ImageMissingError"},
)
compute.post = AsyncioMagicMock(side_effect=resp)
@@ -346,9 +368,7 @@ async def test_create_image_missing_kept_in_degraded_state(project, compute, tmp
assert await node.create(allow_missing_image=True) is False
assert node.missing_image is True
assert node.missing_images == [
{"property": "hda_disk_image", "image": "missing.qcow2", "image_type": "qemu"}
]
assert node.missing_images == [{"property": "hda_disk_image", "image": "missing.qcow2", "image_type": "qemu"}]
@pytest.mark.asyncio
@@ -359,15 +379,19 @@ async def test_create_image_missing_raises_by_default(project, compute, tmpdir,
"""
config.settings.Server.images_path = str(tmpdir)
node = Node(project, compute, "r1",
node_id=str(uuid.uuid4()),
node_type="qemu",
properties={"hda_disk_image": "missing.qcow2", "ram": 256})
node = Node(
project,
compute,
"r1",
node_id=str(uuid.uuid4()),
node_type="qemu",
properties={"hda_disk_image": "missing.qcow2", "ram": 256},
)
async def resp(*args, **kwargs):
raise ComputeConflictError(
"/projects/{}/qemu/nodes".format(project.id),
{"message": "The image is missing", "image": "missing.qcow2", "exception": "ImageMissingError"}
{"message": "The image is missing", "image": "missing.qcow2", "exception": "ImageMissingError"},
)
compute.post = AsyncioMagicMock(side_effect=resp)
@@ -478,15 +502,19 @@ async def test_create_docker_image_missing_after_failed_pull(project, compute):
degraded state instead of aborting the project open.
"""
node = Node(project, compute, "web",
node_id=str(uuid.uuid4()),
node_type="docker",
properties={"image": "ghost:latest", "adapters": 1})
node = Node(
project,
compute,
"web",
node_id=str(uuid.uuid4()),
node_type="docker",
properties={"image": "ghost:latest", "adapters": 1},
)
async def resp(*args, **kwargs):
raise ComputeConflictError(
"/projects/{}/docker/nodes".format(project.id),
{"message": "The image is missing", "image": "ghost:latest", "exception": "ImageMissingError"}
{"message": "The image is missing", "image": "ghost:latest", "exception": "ImageMissingError"},
)
compute.post = AsyncioMagicMock(side_effect=resp)
@@ -496,9 +524,7 @@ async def test_create_docker_image_missing_after_failed_pull(project, compute):
assert await node.create(allow_missing_image=True) is False
assert node.missing_image is True
assert node.missing_images == [
{"property": "image", "image": "ghost:latest", "image_type": "docker"}
]
assert node.missing_images == [{"property": "image", "image": "ghost:latest", "image_type": "docker"}]
@pytest.mark.asyncio
@@ -507,15 +533,19 @@ async def test_create_docker_image_missing_pull_error_raises_by_default(project,
Without allow_missing_image a failed Docker pull is still surfaced.
"""
node = Node(project, compute, "web",
node_id=str(uuid.uuid4()),
node_type="docker",
properties={"image": "ghost:latest", "adapters": 1})
node = Node(
project,
compute,
"web",
node_id=str(uuid.uuid4()),
node_type="docker",
properties={"image": "ghost:latest", "adapters": 1},
)
async def resp(*args, **kwargs):
raise ComputeConflictError(
"/projects/{}/docker/nodes".format(project.id),
{"message": "The image is missing", "image": "ghost:latest", "exception": "ImageMissingError"}
{"message": "The image is missing", "image": "ghost:latest", "exception": "ImageMissingError"},
)
compute.post = AsyncioMagicMock(side_effect=resp)
@@ -534,15 +564,19 @@ def test_compute_missing_images_lists_all_unavailable_slots(project, compute, tm
"""
config.settings.Server.images_path = str(tmpdir)
node = Node(project, compute, "r1",
node_id=str(uuid.uuid4()),
node_type="qemu",
properties={
"hda_disk_image": "present.qcow2",
"hdc_disk_image": "missing.qcow2",
"initrd": "missing.initrd",
"ram": 256,
})
node = Node(
project,
compute,
"r1",
node_id=str(uuid.uuid4()),
node_type="qemu",
properties={
"hda_disk_image": "present.qcow2",
"hdc_disk_image": "missing.qcow2",
"initrd": "missing.initrd",
"ram": 256,
},
)
# make one image available on the controller
os.makedirs(os.path.join(str(tmpdir), "QEMU"), exist_ok=True)
with open(os.path.join(str(tmpdir), "QEMU", "present.qcow2"), "w") as f:
@@ -556,9 +590,7 @@ def test_compute_missing_images_lists_all_unavailable_slots(project, compute, tm
@pytest.mark.asyncio
async def test_start_missing_image(node):
node._missing_images = [
{"property": "hda_disk_image", "image": "missing.qcow2", "image_type": "qemu"}
]
node._missing_images = [{"property": "hda_disk_image", "image": "missing.qcow2", "image_type": "qemu"}]
with pytest.raises(ControllerError):
await node.start()
@@ -570,9 +602,7 @@ async def test_update_recreates_missing_image_node(project, compute, node):
the missing image state.
"""
node._missing_images = [
{"property": "image", "image": "missing.image", "image_type": "ios"}
]
node._missing_images = [{"property": "image", "image": "missing.image", "image_type": "ios"}]
response = MagicMock()
response.json = {"console": 2048}
compute.post = AsyncioMagicMock(return_value=response)
@@ -600,9 +630,7 @@ async def test_update_replaces_qemu_linked_clone_backing_image(project, compute)
"hda_disk_image_md5sum": "old-checksum",
},
)
qemu_node._missing_images = [
{"property": "hda_disk_image", "image": "missing.qcow2", "image_type": "qemu"}
]
qemu_node._missing_images = [{"property": "hda_disk_image", "image": "missing.qcow2", "image_type": "qemu"}]
response = MagicMock()
response.json = {"console": 2048}
compute.post = AsyncioMagicMock(return_value=response)
@@ -627,18 +655,14 @@ async def test_update_replaces_qemu_linked_clone_backing_image(project, compute)
async def test_update_missing_image_node_rolls_back_after_create_error(project, compute, node):
original_properties = {"image": "missing.image"}
node._properties = original_properties.copy()
node._missing_images = [
{"property": "image", "image": "missing.image", "image_type": "ios"}
]
node._missing_images = [{"property": "image", "image": "missing.image", "image_type": "ios"}]
compute.post = AsyncioMagicMock(side_effect=ControllerError("create failed"))
with pytest.raises(ControllerError):
await node.update(properties={"image": "invalid.image"})
assert node.properties == original_properties
assert node.missing_images == [
{"property": "image", "image": "missing.image", "image_type": "ios"}
]
assert node.missing_images == [{"property": "image", "image": "missing.image", "image_type": "ios"}]
@pytest.mark.asyncio
@@ -659,9 +683,7 @@ async def test_start_retries_ready_deferred_links(node, project):
@pytest.mark.asyncio
async def test_stop_missing_image_node_does_not_contact_compute(node, compute):
node._missing_images = [
{"property": "image", "image": "missing.image", "image_type": "ios"}
]
node._missing_images = [{"property": "image", "image": "missing.image", "image_type": "ios"}]
compute.post = AsyncioMagicMock()
await node.stop()
@@ -673,8 +695,8 @@ async def test_stop_missing_image_node_does_not_contact_compute(node, compute):
async def test_create_base_script(node, config, compute, tmpdir):
config.settings.Server.configs_path = str(tmpdir)
with open(str(tmpdir / 'test.txt'), 'w+') as f:
f.write('hostname test')
with open(str(tmpdir / "test.txt"), "w+") as f:
f.write("hostname test")
node._properties = {"base_script_file": "test.txt"}
node._console = 2048
@@ -689,7 +711,7 @@ async def test_create_base_script(node, config, compute, tmpdir):
"console_type": "vnc",
"node_id": node.id,
"startup_script": "hostname test",
"name": "demo"
"name": "demo",
}
compute.post.assert_called_with("/projects/{}/vpcs/nodes".format(node.project.id), data=data, timeout=1200)
@@ -713,7 +735,9 @@ def test_symbol(node, symbols_dir):
assert node.height == 71
assert node.label["x"] is None
assert node.label["y"] == -40
assert node.label["style"] == None#"font-family: TypeWriter;font-size: 10.0;font-weight: bold;fill: #000000;fill-opacity: 1.0;"
assert (
node.label["style"] == None
) # "font-family: TypeWriter;font-size: 10.0;font-weight: bold;fill: #000000;fill-opacity: 1.0;"
shutil.copy(os.path.join("gns3server", "symbols", "classic", "cloud.svg"), os.path.join(symbols_dir, "cloud2.svg"))
node.symbol = "cloud2.svg"
@@ -734,15 +758,14 @@ def test_label_with_default_label_font(node):
the correct color
"""
node.project.controller.settings = {
"GraphicsView": {
"default_label_color": "#ff0000",
"default_label_font": "TypeWriter,10,-1,5,75,0,0,0,0,0"
}
"GraphicsView": {"default_label_color": "#ff0000", "default_label_font": "TypeWriter,10,-1,5,75,0,0,0,0,0"}
}
node._label = None
node.symbol = ":/symbols/dslam.svg"
assert node.label["style"] == None #"font-family: TypeWriter;font-size: 10;font-weight: bold;fill: #ff0000;fill-opacity: 1.0;"
assert (
node.label["style"] == None
) # "font-family: TypeWriter;font-size: 10;font-weight: bold;fill: #ff0000;fill-opacity: 1.0;"
@pytest.mark.asyncio
@@ -755,17 +778,12 @@ async def test_update(node, compute, project, controller):
project.dump = MagicMock()
await node.update(x=42, console=2048, console_type="vnc", properties={"startup_script": "echo test"}, name="demo")
data = {
"console": 2048,
"console_type": "vnc",
"startup_script": "echo test",
"name": "demo"
}
data = {"console": 2048, "console_type": "vnc", "startup_script": "echo test", "name": "demo"}
compute.put.assert_called_with("/projects/{}/vpcs/nodes/{}".format(node.project.id, node.id), data=data)
assert node._console == 2048
assert node.x == 42
assert node._properties == {"startup_script": "echo test"}
#controller._notification.emit.assert_called_with("node.updated", node.asdict())
# controller._notification.emit.assert_called_with("node.updated", node.asdict())
assert project.dump.called
@@ -780,12 +798,7 @@ async def test_update_properties(node, compute, controller):
controller._notification = AsyncioMagicMock()
await node.update(x=42, console=2048, console_type="vnc", properties={"startup_script": "hello world"}, name="demo")
data = {
"console": 2048,
"console_type": "vnc",
"startup_script": "hello world",
"name": "demo"
}
data = {"console": 2048, "console_type": "vnc", "startup_script": "hello world", "name": "demo"}
compute.put.assert_called_with("/projects/{}/vpcs/nodes/{}".format(node.project.id, node.id), data=data)
assert node._console == 2048
assert node.x == 42
@@ -793,9 +806,9 @@ async def test_update_properties(node, compute, controller):
# The notif should contain the old properties because it's the compute that will emit
# the correct info
#node_notif = copy.deepcopy(node.asdict())
#node_notif["properties"]["startup_script"] = "echo test"
#controller._notification.emit.assert_called_with("node.updated", node_notif)
# node_notif = copy.deepcopy(node.asdict())
# node_notif["properties"]["startup_script"] = "echo test"
# controller._notification.emit.assert_called_with("node.updated", node_notif)
@pytest.mark.asyncio
@@ -888,8 +901,7 @@ def test_default_credentials_from_template_kwargs(compute, project):
default credentials without sending them to the compute.
"""
node = Node(project, compute, "test", node_type="vpcs",
default_username="root", default_password="cisco123")
node = Node(project, compute, "test", node_type="vpcs", default_username="root", default_password="cisco123")
assert node.default_username == "root"
assert node.default_password == "cisco123"
assert "default_username" not in node.properties
@@ -927,18 +939,20 @@ async def test_start(node, compute):
@pytest.mark.asyncio
async def test_start_iou(compute, project, controller):
node = Node(project, compute, "demo",
node_id=str(uuid.uuid4()),
node_type="iou")
node = Node(project, compute, "demo", node_id=str(uuid.uuid4()), node_type="iou")
compute.post = AsyncioMagicMock()
# Without licence configured it should raise an error
#with pytest.raises(aiohttp.web.HTTPConflict):
# with pytest.raises(aiohttp.web.HTTPConflict):
# async_run(node.start())
controller._iou_license_settings = {"license_check": True, "iourc_content": "aa"}
await node.start()
compute.post.assert_called_with("/projects/{}/iou/nodes/{}/start".format(node.project.id, node.id), timeout=240, data={"license_check": True, "iourc_content": "aa"})
compute.post.assert_called_with(
"/projects/{}/iou/nodes/{}/start".format(node.project.id, node.id),
timeout=240,
data={"license_check": True, "iourc_content": "aa"},
)
@pytest.mark.asyncio
@@ -947,7 +961,9 @@ async def test_stop(node, compute):
compute.post = AsyncioMagicMock()
await node.stop()
compute.post.assert_called_with("/projects/{}/vpcs/nodes/{}/stop".format(node.project.id, node.id), timeout=240, dont_connect=True)
compute.post.assert_called_with(
"/projects/{}/vpcs/nodes/{}/stop".format(node.project.id, node.id), timeout=240, dont_connect=True
)
@pytest.mark.asyncio
@@ -977,12 +993,7 @@ async def test_create_without_console(node, compute):
compute.post = AsyncioMagicMock(return_value=response)
await node.create()
data = {
"console_type": "vnc",
"node_id": node.id,
"startup_script": "echo test",
"name": "demo"
}
data = {"console_type": "vnc", "node_id": node.id, "startup_script": "echo test", "name": "demo"}
compute.post.assert_called_with("/projects/{}/vpcs/nodes".format(node.project.id), data=data, timeout=1200)
assert node._console == 2048
assert node._properties == {"test_value": "success", "startup_script": "echo test"}
@@ -1017,7 +1028,9 @@ async def test_dynamips_idle_pc(node, compute):
response.json = {"idlepc": "0x60606f54"}
compute.get = AsyncioMagicMock(return_value=response)
await node.dynamips_auto_idlepc()
compute.get.assert_called_with("/projects/{}/dynamips/nodes/{}/auto_idlepc".format(node.project.id, node.id), timeout=240)
compute.get.assert_called_with(
"/projects/{}/dynamips/nodes/{}/auto_idlepc".format(node.project.id, node.id), timeout=240
)
@pytest.mark.asyncio
@@ -1028,18 +1041,24 @@ async def test_dynamips_idlepc_proposals(node, compute):
response.json = ["0x60606f54", "0x30ff6f37"]
compute.get = AsyncioMagicMock(return_value=response)
await node.dynamips_idlepc_proposals()
compute.get.assert_called_with("/projects/{}/dynamips/nodes/{}/idlepc_proposals".format(node.project.id, node.id), timeout=240)
compute.get.assert_called_with(
"/projects/{}/dynamips/nodes/{}/idlepc_proposals".format(node.project.id, node.id), timeout=240
)
@pytest.mark.asyncio
async def test_upload_missing_image(compute, controller, images_dir):
project = Project(str(uuid.uuid4()), controller=controller)
node = Node(project, compute, "demo",
node_id=str(uuid.uuid4()),
node_type="qemu",
properties={"hda_disk_image": "linux.img"})
open(os.path.join(images_dir, "linux.img"), 'w+').close()
node = Node(
project,
compute,
"demo",
node_id=str(uuid.uuid4()),
node_type="qemu",
properties={"hda_disk_image": "linux.img"},
)
open(os.path.join(images_dir, "linux.img"), "w+").close()
assert await node._upload_missing_image("qemu", "linux.img") is True
compute.post.assert_called_with("/qemu/images/linux.img", data=ANY, timeout=None)
@@ -1048,10 +1067,14 @@ async def test_upload_missing_image(compute, controller, images_dir):
async def test_upload_missing_image_from_nested_directory(compute, controller, images_dir):
project = Project(str(uuid.uuid4()), controller=controller)
node = Node(project, compute, "demo",
node_id=str(uuid.uuid4()),
node_type="qemu",
properties={"hda_disk_image": "nested.img"})
node = Node(
project,
compute,
"demo",
node_id=str(uuid.uuid4()),
node_type="qemu",
properties={"hda_disk_image": "nested.img"},
)
nested_dir = os.path.join(images_dir, "vendor")
os.makedirs(nested_dir)
open(os.path.join(nested_dir, "nested.img"), "w+").close()
@@ -1064,10 +1087,9 @@ async def test_upload_missing_image_from_nested_directory(compute, controller, i
async def test_sync_missing_docker_image_from_controller_daemon(compute, controller):
project = Project(str(uuid.uuid4()), controller=controller)
node = Node(project, compute, "demo",
node_id=str(uuid.uuid4()),
node_type="docker",
properties={"image": "gns3/frr:latest"})
node = Node(
project, compute, "demo", node_id=str(uuid.uuid4()), node_type="docker", properties={"image": "gns3/frr:latest"}
)
response = MagicMock()
response.content = MagicMock()
@@ -1082,10 +1104,9 @@ async def test_sync_missing_docker_image_from_controller_daemon(compute, control
async def test_sync_missing_docker_image_pull_fallback(compute, controller):
project = Project(str(uuid.uuid4()), controller=controller)
node = Node(project, compute, "demo",
node_id=str(uuid.uuid4()),
node_type="docker",
properties={"image": "nginx:latest"})
node = Node(
project, compute, "demo", node_id=str(uuid.uuid4()), node_type="docker", properties={"image": "nginx:latest"}
)
with asyncio_patch("gns3server.compute.docker.Docker.http_query", side_effect=DockerError("404")):
assert await node._upload_missing_image("docker", "nginx:latest") is True
@@ -1096,10 +1117,14 @@ async def test_sync_missing_docker_image_pull_fallback(compute, controller):
async def test_create_docker_node_pins_image_digest(compute, controller):
project = Project(str(uuid.uuid4()), controller=controller)
node = Node(project, compute, "demo",
node_id=str(uuid.uuid4()),
node_type="docker",
properties={"image": "gns3/frr:latest", "adapters": 1})
node = Node(
project,
compute,
"demo",
node_id=str(uuid.uuid4()),
node_type="docker",
properties={"image": "gns3/frr:latest", "adapters": 1},
)
response = MagicMock()
response.status = 200
+81 -151
View File
@@ -33,11 +33,15 @@ def compute():
@pytest.fixture
def node(compute, project):
node = Node(project, compute, "demo",
node_id=str(uuid.uuid4()),
node_type="qemu",
console_type="vnc",
properties={"startup_script": "echo test"})
node = Node(
project,
compute,
"demo",
node_id=str(uuid.uuid4()),
node_type="qemu",
console_type="vnc",
properties={"startup_script": "echo test"},
)
return node
@@ -53,7 +57,7 @@ def test_list_ports(node):
"data_link_types": {"Ethernet": "DLT_EN10MB"},
"port_number": 0,
"adapter_number": 0,
"link_type": "ethernet"
"link_type": "ethernet",
}
]
@@ -71,7 +75,7 @@ def test_list_ports_vpcs(node):
"data_link_types": {"Ethernet": "DLT_EN10MB"},
"port_number": 0,
"adapter_number": 0,
"link_type": "ethernet"
"link_type": "ethernet",
}
]
@@ -90,7 +94,7 @@ def test_list_ports_docker(node):
"data_link_types": {"Ethernet": "DLT_EN10MB"},
"port_number": 0,
"adapter_number": 0,
"link_type": "ethernet"
"link_type": "ethernet",
},
{
"name": "eth1",
@@ -98,8 +102,8 @@ def test_list_ports_docker(node):
"data_link_types": {"Ethernet": "DLT_EN10MB"},
"port_number": 0,
"adapter_number": 1,
"link_type": "ethernet"
}
"link_type": "ethernet",
},
]
@@ -153,7 +157,7 @@ def test_list_ports_adapters(node):
"data_link_types": {"Ethernet": "DLT_EN10MB"},
"port_number": 0,
"adapter_number": 0,
"link_type": "ethernet"
"link_type": "ethernet",
},
{
"name": "Ethernet1",
@@ -161,8 +165,8 @@ def test_list_ports_adapters(node):
"data_link_types": {"Ethernet": "DLT_EN10MB"},
"port_number": 0,
"adapter_number": 1,
"link_type": "ethernet"
}
"link_type": "ethernet",
},
]
@@ -171,17 +175,8 @@ def test_list_ports_adapters_cloud(project, compute):
List port using adapters properties
"""
node = Node(project, compute, "demo",
node_id=str(uuid.uuid4()),
node_type="cloud")
node.properties["ports_mapping"] = [
{
"interface": "eth0",
"name": "eth0",
"port_number": 0,
"type": "ethernet"
}
]
node = Node(project, compute, "demo", node_id=str(uuid.uuid4()), node_type="cloud")
node.properties["ports_mapping"] = [{"interface": "eth0", "name": "eth0", "port_number": 0, "type": "ethernet"}]
assert node.asdict()["ports"] == [
{
@@ -190,7 +185,7 @@ def test_list_ports_adapters_cloud(project, compute):
"data_link_types": {"Ethernet": "DLT_EN10MB"},
"port_number": 0,
"adapter_number": 0,
"link_type": "ethernet"
"link_type": "ethernet",
}
]
@@ -200,18 +195,10 @@ def test_list_ports_ethernet_hub(project, compute):
List port for atm switch
"""
node = Node(project, compute, "demo",
node_id=str(uuid.uuid4()),
node_type="ethernet_hub")
node = Node(project, compute, "demo", node_id=str(uuid.uuid4()), node_type="ethernet_hub")
node.properties["ports_mapping"] = [
{
"name": "Ethernet0",
"port_number": 0
},
{
"name": "Ethernet1",
"port_number": 1
}
{"name": "Ethernet0", "port_number": 0},
{"name": "Ethernet1", "port_number": 1},
]
assert node.asdict()["ports"] == [
@@ -221,7 +208,7 @@ def test_list_ports_ethernet_hub(project, compute):
"data_link_types": {"Ethernet": "DLT_EN10MB"},
"port_number": 0,
"adapter_number": 0,
"link_type": "ethernet"
"link_type": "ethernet",
},
{
"name": "Ethernet1",
@@ -229,8 +216,8 @@ def test_list_ports_ethernet_hub(project, compute):
"data_link_types": {"Ethernet": "DLT_EN10MB"},
"port_number": 1,
"adapter_number": 0,
"link_type": "ethernet"
}
"link_type": "ethernet",
},
]
@@ -239,12 +226,8 @@ def test_list_ports_atm_switch(project, compute):
List port for atm switch
"""
node = Node(project, compute, "demo",
node_id=str(uuid.uuid4()),
node_type="atm_switch")
node.properties["mappings"] = {
"1:0:100": "10:0:200"
}
node = Node(project, compute, "demo", node_id=str(uuid.uuid4()), node_type="atm_switch")
node.properties["mappings"] = {"1:0:100": "10:0:200"}
assert node.asdict()["ports"] == [
{
@@ -253,7 +236,7 @@ def test_list_ports_atm_switch(project, compute):
"data_link_types": {"ATM": "DLT_ATM_RFC1483"},
"port_number": 1,
"adapter_number": 0,
"link_type": "serial"
"link_type": "serial",
},
{
"name": "10",
@@ -261,8 +244,8 @@ def test_list_ports_atm_switch(project, compute):
"data_link_types": {"ATM": "DLT_ATM_RFC1483"},
"port_number": 10,
"adapter_number": 0,
"link_type": "serial"
}
"link_type": "serial",
},
]
@@ -271,13 +254,8 @@ def test_list_ports_frame_relay_switch(project, compute):
List port for frame relay switch
"""
node = Node(project, compute, "demo",
node_id=str(uuid.uuid4()),
node_type="frame_relay_switch")
node.properties["mappings"] = {
"1:101": "10:202",
"2:102": "11:203"
}
node = Node(project, compute, "demo", node_id=str(uuid.uuid4()), node_type="frame_relay_switch")
node.properties["mappings"] = {"1:101": "10:202", "2:102": "11:203"}
assert node.asdict()["ports"] == [
{
@@ -286,7 +264,7 @@ def test_list_ports_frame_relay_switch(project, compute):
"data_link_types": {"Frame Relay": "DLT_FRELAY"},
"port_number": 1,
"adapter_number": 0,
"link_type": "serial"
"link_type": "serial",
},
{
"name": "2",
@@ -294,7 +272,7 @@ def test_list_ports_frame_relay_switch(project, compute):
"data_link_types": {"Frame Relay": "DLT_FRELAY"},
"port_number": 2,
"adapter_number": 0,
"link_type": "serial"
"link_type": "serial",
},
{
"name": "10",
@@ -302,7 +280,7 @@ def test_list_ports_frame_relay_switch(project, compute):
"data_link_types": {"Frame Relay": "DLT_FRELAY"},
"port_number": 10,
"adapter_number": 0,
"link_type": "serial"
"link_type": "serial",
},
{
"name": "11",
@@ -310,8 +288,8 @@ def test_list_ports_frame_relay_switch(project, compute):
"data_link_types": {"Frame Relay": "DLT_FRELAY"},
"port_number": 11,
"adapter_number": 0,
"link_type": "serial"
}
"link_type": "serial",
},
]
@@ -320,9 +298,7 @@ def test_list_ports_iou(compute, project):
IOU has a special behavior 4 port by adapters
"""
node = Node(project, compute, "demo",
node_id=str(uuid.uuid4()),
node_type="iou")
node = Node(project, compute, "demo", node_id=str(uuid.uuid4()), node_type="iou")
node.properties["serial_adapters"] = 2
node.properties["ethernet_adapters"] = 3
assert node.asdict()["ports"] == [
@@ -332,7 +308,7 @@ def test_list_ports_iou(compute, project):
"data_link_types": {"Ethernet": "DLT_EN10MB"},
"port_number": 0,
"adapter_number": 0,
"link_type": "ethernet"
"link_type": "ethernet",
},
{
"name": "Ethernet0/1",
@@ -340,7 +316,7 @@ def test_list_ports_iou(compute, project):
"data_link_types": {"Ethernet": "DLT_EN10MB"},
"port_number": 1,
"adapter_number": 0,
"link_type": "ethernet"
"link_type": "ethernet",
},
{
"name": "Ethernet0/2",
@@ -348,7 +324,7 @@ def test_list_ports_iou(compute, project):
"data_link_types": {"Ethernet": "DLT_EN10MB"},
"port_number": 2,
"adapter_number": 0,
"link_type": "ethernet"
"link_type": "ethernet",
},
{
"name": "Ethernet0/3",
@@ -356,7 +332,7 @@ def test_list_ports_iou(compute, project):
"data_link_types": {"Ethernet": "DLT_EN10MB"},
"port_number": 3,
"adapter_number": 0,
"link_type": "ethernet"
"link_type": "ethernet",
},
{
"name": "Ethernet1/0",
@@ -364,7 +340,7 @@ def test_list_ports_iou(compute, project):
"data_link_types": {"Ethernet": "DLT_EN10MB"},
"port_number": 0,
"adapter_number": 1,
"link_type": "ethernet"
"link_type": "ethernet",
},
{
"name": "Ethernet1/1",
@@ -372,7 +348,7 @@ def test_list_ports_iou(compute, project):
"data_link_types": {"Ethernet": "DLT_EN10MB"},
"port_number": 1,
"adapter_number": 1,
"link_type": "ethernet"
"link_type": "ethernet",
},
{
"name": "Ethernet1/2",
@@ -380,7 +356,7 @@ def test_list_ports_iou(compute, project):
"data_link_types": {"Ethernet": "DLT_EN10MB"},
"port_number": 2,
"adapter_number": 1,
"link_type": "ethernet"
"link_type": "ethernet",
},
{
"name": "Ethernet1/3",
@@ -388,7 +364,7 @@ def test_list_ports_iou(compute, project):
"data_link_types": {"Ethernet": "DLT_EN10MB"},
"port_number": 3,
"adapter_number": 1,
"link_type": "ethernet"
"link_type": "ethernet",
},
{
"name": "Ethernet2/0",
@@ -396,7 +372,7 @@ def test_list_ports_iou(compute, project):
"data_link_types": {"Ethernet": "DLT_EN10MB"},
"port_number": 0,
"adapter_number": 2,
"link_type": "ethernet"
"link_type": "ethernet",
},
{
"name": "Ethernet2/1",
@@ -404,7 +380,7 @@ def test_list_ports_iou(compute, project):
"data_link_types": {"Ethernet": "DLT_EN10MB"},
"port_number": 1,
"adapter_number": 2,
"link_type": "ethernet"
"link_type": "ethernet",
},
{
"name": "Ethernet2/2",
@@ -412,7 +388,7 @@ def test_list_ports_iou(compute, project):
"data_link_types": {"Ethernet": "DLT_EN10MB"},
"port_number": 2,
"adapter_number": 2,
"link_type": "ethernet"
"link_type": "ethernet",
},
{
"name": "Ethernet2/3",
@@ -420,104 +396,72 @@ def test_list_ports_iou(compute, project):
"data_link_types": {"Ethernet": "DLT_EN10MB"},
"port_number": 3,
"adapter_number": 2,
"link_type": "ethernet"
"link_type": "ethernet",
},
{
"name": "Serial3/0",
"short_name": "s3/0",
"data_link_types": {
"Frame Relay": "DLT_FRELAY",
"Cisco HDLC": "DLT_C_HDLC",
"Cisco PPP": "DLT_PPP_SERIAL"
},
"data_link_types": {"Frame Relay": "DLT_FRELAY", "Cisco HDLC": "DLT_C_HDLC", "Cisco PPP": "DLT_PPP_SERIAL"},
"port_number": 0,
"adapter_number": 3,
"link_type": "serial"
"link_type": "serial",
},
{
"name": "Serial3/1",
"short_name": "s3/1",
"data_link_types": {
"Frame Relay": "DLT_FRELAY",
"Cisco HDLC": "DLT_C_HDLC",
"Cisco PPP": "DLT_PPP_SERIAL"
},
"data_link_types": {"Frame Relay": "DLT_FRELAY", "Cisco HDLC": "DLT_C_HDLC", "Cisco PPP": "DLT_PPP_SERIAL"},
"port_number": 1,
"adapter_number": 3,
"link_type": "serial"
"link_type": "serial",
},
{
"name": "Serial3/2",
"short_name": "s3/2",
"data_link_types": {
"Frame Relay": "DLT_FRELAY",
"Cisco HDLC": "DLT_C_HDLC",
"Cisco PPP": "DLT_PPP_SERIAL"
},
"data_link_types": {"Frame Relay": "DLT_FRELAY", "Cisco HDLC": "DLT_C_HDLC", "Cisco PPP": "DLT_PPP_SERIAL"},
"port_number": 2,
"adapter_number": 3,
"link_type": "serial"
"link_type": "serial",
},
{
"name": "Serial3/3",
"short_name": "s3/3",
"data_link_types": {
"Frame Relay": "DLT_FRELAY",
"Cisco HDLC": "DLT_C_HDLC",
"Cisco PPP": "DLT_PPP_SERIAL"
},
"data_link_types": {"Frame Relay": "DLT_FRELAY", "Cisco HDLC": "DLT_C_HDLC", "Cisco PPP": "DLT_PPP_SERIAL"},
"port_number": 3,
"adapter_number": 3,
"link_type": "serial"
"link_type": "serial",
},
{
"name": "Serial4/0",
"short_name": "s4/0",
"data_link_types": {
"Frame Relay": "DLT_FRELAY",
"Cisco HDLC": "DLT_C_HDLC",
"Cisco PPP": "DLT_PPP_SERIAL"
},
"data_link_types": {"Frame Relay": "DLT_FRELAY", "Cisco HDLC": "DLT_C_HDLC", "Cisco PPP": "DLT_PPP_SERIAL"},
"port_number": 0,
"adapter_number": 4,
"link_type": "serial"
"link_type": "serial",
},
{
"name": "Serial4/1",
"short_name": "s4/1",
"data_link_types": {
"Frame Relay": "DLT_FRELAY",
"Cisco HDLC": "DLT_C_HDLC",
"Cisco PPP": "DLT_PPP_SERIAL"
},
"data_link_types": {"Frame Relay": "DLT_FRELAY", "Cisco HDLC": "DLT_C_HDLC", "Cisco PPP": "DLT_PPP_SERIAL"},
"port_number": 1,
"adapter_number": 4,
"link_type": "serial"
"link_type": "serial",
},
{
"name": "Serial4/2",
"short_name": "s4/2",
"data_link_types": {
"Frame Relay": "DLT_FRELAY",
"Cisco HDLC": "DLT_C_HDLC",
"Cisco PPP": "DLT_PPP_SERIAL"
},
"data_link_types": {"Frame Relay": "DLT_FRELAY", "Cisco HDLC": "DLT_C_HDLC", "Cisco PPP": "DLT_PPP_SERIAL"},
"port_number": 2,
"adapter_number": 4,
"link_type": "serial"
"link_type": "serial",
},
{
"name": "Serial4/3",
"short_name": "s4/3",
"data_link_types": {
"Frame Relay": "DLT_FRELAY",
"Cisco HDLC": "DLT_C_HDLC",
"Cisco PPP": "DLT_PPP_SERIAL"
},
"data_link_types": {"Frame Relay": "DLT_FRELAY", "Cisco HDLC": "DLT_C_HDLC", "Cisco PPP": "DLT_PPP_SERIAL"},
"port_number": 3,
"adapter_number": 4,
"link_type": "serial"
}
"link_type": "serial",
},
]
@@ -526,9 +470,7 @@ def test_list_ports_dynamips(project, compute):
List port for dynamips
"""
node = Node(project, compute, "demo",
node_id=str(uuid.uuid4()),
node_type="dynamips")
node = Node(project, compute, "demo", node_id=str(uuid.uuid4()), node_type="dynamips")
node.properties["slot0"] = "C7200-IO-FE"
node.properties["slot1"] = "GT96100-FE"
node.properties["wic0"] = "WIC-2T"
@@ -541,7 +483,7 @@ def test_list_ports_dynamips(project, compute):
"data_link_types": {"Ethernet": "DLT_EN10MB"},
"port_number": 0,
"adapter_number": 0,
"link_type": "ethernet"
"link_type": "ethernet",
},
{
"name": "FastEthernet1/0",
@@ -549,7 +491,7 @@ def test_list_ports_dynamips(project, compute):
"data_link_types": {"Ethernet": "DLT_EN10MB"},
"port_number": 0,
"adapter_number": 1,
"link_type": "ethernet"
"link_type": "ethernet",
},
{
"name": "FastEthernet1/1",
@@ -557,52 +499,40 @@ def test_list_ports_dynamips(project, compute):
"data_link_types": {"Ethernet": "DLT_EN10MB"},
"port_number": 1,
"adapter_number": 1,
"link_type": "ethernet"
"link_type": "ethernet",
},
{
"name": "Serial0/0",
"short_name": "s0/0",
"data_link_types": {
"Cisco HDLC": "DLT_C_HDLC",
"Cisco PPP": "DLT_PPP_SERIAL",
"Frame Relay": "DLT_FRELAY"},
"data_link_types": {"Cisco HDLC": "DLT_C_HDLC", "Cisco PPP": "DLT_PPP_SERIAL", "Frame Relay": "DLT_FRELAY"},
"port_number": 16,
"adapter_number": 0,
"link_type": "serial"
"link_type": "serial",
},
{
"name": "Serial0/1",
"short_name": "s0/1",
"data_link_types": {
"Cisco HDLC": "DLT_C_HDLC",
"Cisco PPP": "DLT_PPP_SERIAL",
"Frame Relay": "DLT_FRELAY"},
"data_link_types": {"Cisco HDLC": "DLT_C_HDLC", "Cisco PPP": "DLT_PPP_SERIAL", "Frame Relay": "DLT_FRELAY"},
"port_number": 17,
"adapter_number": 0,
"link_type": "serial"
"link_type": "serial",
},
{
"name": "Serial0/2",
"short_name": "s0/2",
"data_link_types": {
"Cisco HDLC": "DLT_C_HDLC",
"Cisco PPP": "DLT_PPP_SERIAL",
"Frame Relay": "DLT_FRELAY"},
"data_link_types": {"Cisco HDLC": "DLT_C_HDLC", "Cisco PPP": "DLT_PPP_SERIAL", "Frame Relay": "DLT_FRELAY"},
"port_number": 32,
"adapter_number": 0,
"link_type": "serial"
"link_type": "serial",
},
{
"name": "Serial0/3",
"short_name": "s0/3",
"data_link_types": {
"Cisco HDLC": "DLT_C_HDLC",
"Cisco PPP": "DLT_PPP_SERIAL",
"Frame Relay": "DLT_FRELAY"},
"data_link_types": {"Cisco HDLC": "DLT_C_HDLC", "Cisco PPP": "DLT_PPP_SERIAL", "Frame Relay": "DLT_FRELAY"},
"port_number": 33,
"adapter_number": 0,
"link_type": "serial"
}
"link_type": "serial",
},
]
+16 -20
View File
@@ -43,10 +43,10 @@ async def test_emit_to_all(controller, project):
notif = controller.notification
with notif.project_queue(project.id) as queue:
assert len(notif._project_listeners[project.id]) == 1
await queue.get(0.1) # ping
notif.project_emit('test', {})
await queue.get(0.1) # ping
notif.project_emit("test", {})
msg = await queue.get(5)
assert msg == ('test', {}, {})
assert msg == ("test", {}, {})
assert len(notif._project_listeners[project.id]) == 0
@@ -62,10 +62,10 @@ async def test_emit_to_project(controller, project):
assert len(notif._project_listeners[project.id]) == 1
await queue.get(0.1) # ping
# This event has not listener
notif.project_emit('ignore', {"project_id": 42})
notif.project_emit('test', {"project_id": project.id})
notif.project_emit("ignore", {"project_id": 42})
notif.project_emit("test", {"project_id": project.id})
msg = await queue.get(5)
assert msg == ('test', {"project_id": project.id}, {})
assert msg == ("test", {"project_id": project.id}, {})
assert len(notif._project_listeners[project.id]) == 0
@@ -79,7 +79,7 @@ async def test_dispatch(controller, project):
await queue.get(0.1) # ping
await notif.dispatch("test", {}, project_id=project.id, compute_id=1)
msg = await queue.get(5)
assert msg == ('test', {}, {})
assert msg == ("test", {}, {})
@pytest.mark.asyncio
@@ -91,7 +91,7 @@ async def test_dispatch_ping(controller, project):
await queue.get(0.1) # ping
await notif.dispatch("ping", {}, project_id=project.id, compute_id=12)
msg = await queue.get(5)
assert msg == ('ping', {'compute_id': 12}, {})
assert msg == ("ping", {"compute_id": 12}, {})
@pytest.mark.asyncio
@@ -105,14 +105,12 @@ async def test_dispatch_node_updated(controller, node, project):
with notif.project_queue(project.id) as queue:
assert len(notif._project_listeners[project.id]) == 1
await queue.get(0.1) # ping
await notif.dispatch("node.updated", {
"node_id": node.id,
"project_id": project.id,
"name": "hello",
"startup_config": "ip 192"
},
await notif.dispatch(
"node.updated",
{"node_id": node.id, "project_id": project.id, "name": "hello", "startup_config": "ip 192"},
project_id=project.id,
compute_id=1)
compute_id=1,
)
assert node.name == "hello"
action, event, _ = await queue.get(5)
assert action == "node.updated"
@@ -128,18 +126,16 @@ async def test_dispatch_marker_routed_to_marker_channel(controller, project):
"""
notif = controller.notification
with notif.project_queue(project.id) as project_q, \
notif.project_marker_queue(project.id) as marker_q:
with notif.project_queue(project.id) as project_q, notif.project_marker_queue(project.id) as marker_q:
assert len(notif._project_marker_listeners[project.id]) == 1
await project_q.get(0.1) # consume initial ping
await marker_q.get(0.1) # consume initial ping
await notif.dispatch("marker.match", {"link_id": "abc"},
project_id=project.id, compute_id=1)
await notif.dispatch("marker.match", {"link_id": "abc"}, project_id=project.id, compute_id=1)
# marker.match lands on the marker channel...
msg = await marker_q.get(5)
assert msg == ('marker.match', {"link_id": "abc"}, {})
assert msg == ("marker.match", {"link_id": "abc"}, {})
# ...and does NOT land on the main project queue (times out -> ping)
msg = await project_q.get(0.1)
assert msg[0] == "ping"
+62 -58
View File
@@ -53,18 +53,18 @@ async def node(controller, project):
@pytest.mark.asyncio
async def test_affect_uuid():
with patch('gns3server.controller.project.Project.emit_controller_notification') as mock_notification:
with patch("gns3server.controller.project.Project.emit_controller_notification") as mock_notification:
p = Project(name="Test")
mock_notification.assert_called()
assert len(p.id) == 36
p = Project(project_id='00010203-0405-0607-0809-0a0b0c0d0e0f', name="Test 2")
assert p.id == '00010203-0405-0607-0809-0a0b0c0d0e0f'
p = Project(project_id="00010203-0405-0607-0809-0a0b0c0d0e0f", name="Test 2")
assert p.id == "00010203-0405-0607-0809-0a0b0c0d0e0f"
@pytest.mark.asyncio
async def test_json():
with patch('gns3server.controller.project.Project.emit_controller_notification') as mock_notification:
with patch("gns3server.controller.project.Project.emit_controller_notification") as mock_notification:
p = Project(name="Test")
mock_notification.assert_called()
@@ -89,14 +89,14 @@ async def test_json():
"supplier": None,
"variables": None,
"marker_definitions": {},
"created_by": None
"created_by": None,
}
@pytest.mark.asyncio
async def test_created_by():
with patch('gns3server.controller.project.Project.emit_controller_notification'):
with patch("gns3server.controller.project.Project.emit_controller_notification"):
p = Project(name="Test", created_by="admin")
assert p.created_by == "admin"
@@ -124,7 +124,7 @@ async def test_update_on_compute(controller):
project._project_created_on_compute = [compute]
project.emit_notification = MagicMock()
await project.update(variables=variables)
compute.put.assert_any_call('/projects/{}'.format(project.id), {"variables": variables})
compute.put.assert_any_call("/projects/{}".format(project.id), {"variables": variables})
@pytest.mark.asyncio
@@ -132,7 +132,7 @@ async def test_path(projects_dir):
directory = projects_dir
with patch("gns3server.utils.path.get_default_project_directory", return_value=directory):
with patch('gns3server.controller.project.Project.emit_controller_notification') as mock_notification:
with patch("gns3server.controller.project.Project.emit_controller_notification") as mock_notification:
p = Project(project_id=str(uuid4()), name="Test")
mock_notification.assert_called()
assert p.path == os.path.join(directory, p.id)
@@ -153,7 +153,7 @@ def test_path_exist(tmpdir):
@pytest.mark.asyncio
async def test_init_path(projects_dir):
with patch('gns3server.controller.project.Project.emit_controller_notification') as mock_notification:
with patch("gns3server.controller.project.Project.emit_controller_notification") as mock_notification:
project_id = str(uuid4())
p = Project(project_id=project_id, name="Test")
mock_notification.assert_called()
@@ -164,15 +164,15 @@ async def test_init_path(projects_dir):
async def test_changing_path_with_quote_not_allowed(projects_dir):
with pytest.raises(ControllerForbiddenError):
with patch('gns3server.controller.project.Project.emit_controller_notification'):
with patch("gns3server.controller.project.Project.emit_controller_notification"):
p = Project(project_id=str(uuid4()), name="Test")
p.path = os.path.join(projects_dir, "project\"53")
p.path = os.path.join(projects_dir, 'project"53')
@pytest.mark.asyncio
async def test_captures_directory(tmpdir):
with patch('gns3server.controller.project.Project.emit_controller_notification'):
with patch("gns3server.controller.project.Project.emit_controller_notification"):
p = Project(name="Test")
assert p.captures_directory == str(p.path + os.path.sep + "project-files" + os.path.sep + "captures")
assert os.path.exists(p.captures_directory)
@@ -196,16 +196,19 @@ async def test_add_node_local(controller):
node = await project.add_node(compute, "test", None, node_type="vpcs", properties={"startup_script": "test.cfg"})
assert node.id in project._nodes
compute.post.assert_any_call('/projects', data={
"name": project._name,
"project_id": project._id,
"path": project._path,
})
compute.post.assert_any_call('/projects/{}/vpcs/nodes'.format(project.id),
data={'node_id': node.id,
'startup_script': 'test.cfg',
'name': 'test'},
timeout=1200)
compute.post.assert_any_call(
"/projects",
data={
"name": project._name,
"project_id": project._id,
"path": project._path,
},
)
compute.post.assert_any_call(
"/projects/{}/vpcs/nodes".format(project.id),
data={"node_id": node.id, "startup_script": "test.cfg", "name": "test"},
timeout=1200,
)
assert compute in project._project_created_on_compute
project.emit_notification.assert_any_call("node.created", node.asdict())
@@ -269,13 +272,12 @@ async def test_add_node_non_local(controller):
node = await project.add_node(compute, "test", None, node_type="vpcs", properties={"startup_script": "test.cfg"})
compute.post.assert_any_call('/projects', data={
"name": project._name,
"project_id": project._id
})
compute.post.assert_any_call('/projects/{}/vpcs/nodes'.format(project.id), data={'node_id': node.id,
'startup_script': 'test.cfg',
'name': 'test'}, timeout=1200)
compute.post.assert_any_call("/projects", data={"name": project._name, "project_id": project._id})
compute.post.assert_any_call(
"/projects/{}/vpcs/nodes".format(project.id),
data={"node_id": node.id, "startup_script": "test.cfg", "name": "test"},
timeout=1200,
)
assert compute in project._project_created_on_compute
project.emit_notification.assert_any_call("node.created", node.asdict())
@@ -538,7 +540,7 @@ async def test_delete_node(controller):
await project.delete_node(node.id)
assert node.id not in project._nodes
compute.delete.assert_any_call('/projects/{}/vpcs/nodes/{}'.format(project.id, node.id))
compute.delete.assert_any_call("/projects/{}/vpcs/nodes/{}".format(project.id, node.id))
project.emit_notification.assert_any_call("node.deleted", node.asdict())
@@ -585,7 +587,7 @@ async def test_delete_node_delete_link(controller):
assert node.id not in project._nodes
assert link.id not in project._links
compute.delete.assert_any_call('/projects/{}/vpcs/nodes/{}'.format(project.id, node.id))
compute.delete.assert_any_call("/projects/{}/vpcs/nodes/{}".format(project.id, node.id))
project.emit_notification.assert_any_call("node.deleted", node.asdict())
project.emit_notification.assert_any_call("link.deleted", link.asdict())
@@ -763,10 +765,7 @@ async def test_clean_pictures_and_keep_supplier_logo(project):
When a project is close old pictures should be removed
"""
project.supplier = {
'logo': 'logo.png',
'url': 'http://acme.com'
}
project.supplier = {"logo": "logo.png", "url": "http://acme.com"}
drawing = await project.add_drawing()
drawing._svg = "test.png"
@@ -838,8 +837,8 @@ async def test_dump(projects_dir):
directory = projects_dir
with patch("gns3server.utils.path.get_default_project_directory", return_value=directory):
with patch('gns3server.controller.project.Project.emit_controller_notification'):
p = Project(project_id='00010203-0405-0607-0809-0a0b0c0d0e0f', name="Test")
with patch("gns3server.controller.project.Project.emit_controller_notification"):
p = Project(project_id="00010203-0405-0607-0809-0a0b0c0d0e0f", name="Test")
p.dump()
with open(os.path.join(directory, p.id, "Test.gns3")) as f:
content = f.read()
@@ -849,7 +848,7 @@ async def test_dump(projects_dir):
@pytest.mark.asyncio
async def test_open_close(controller):
with patch('gns3server.controller.project.Project.emit_controller_notification'):
with patch("gns3server.controller.project.Project.emit_controller_notification"):
project = Project(controller=controller, name="Test")
assert project.status == "opened"
await project.close()
@@ -866,13 +865,13 @@ async def test_open_close(controller):
@pytest.mark.asyncio
async def test_open_auto_start(controller):
with patch('gns3server.controller.project.Project.emit_controller_notification'):
with patch("gns3server.controller.project.Project.emit_controller_notification"):
project = Project(controller=controller, name="Test", auto_start=True)
assert project.status == "opened"
await project.close()
#project.start_all = AsyncioMagicMock()
#await project.open()
#assert project.start_all.called
# project.start_all = AsyncioMagicMock()
# await project.open()
# assert project.start_all.called
def test_is_running(project, node):
@@ -901,10 +900,14 @@ async def test_duplicate(project, controller):
response.json = {"console": 2048}
compute.post = AsyncioMagicMock(return_value=response)
remote_vpcs = await project.add_node(compute, "test", None, node_type="vpcs", properties={"startup_config": "test.cfg"})
remote_vpcs = await project.add_node(
compute, "test", None, node_type="vpcs", properties={"startup_config": "test.cfg"}
)
# We allow node not allowed for standard import / export
remote_virtualbox = await project.add_node(compute, "test", None, node_type="vmware", properties={"startup_config": "test.cfg"})
remote_virtualbox = await project.add_node(
compute, "test", None, node_type="vmware", properties={"startup_config": "test.cfg"}
)
new_project = await project.duplicate(name="Hello")
assert new_project.id != project.id
@@ -1092,13 +1095,21 @@ async def test_node_name(project):
response.json = {"console": 2048}
compute.post = AsyncioMagicMock(return_value=response)
node = await project.add_node(compute, "test-{0}", None, node_type="vpcs", properties={"startup_config": "test.cfg"})
node = await project.add_node(
compute, "test-{0}", None, node_type="vpcs", properties={"startup_config": "test.cfg"}
)
assert node.name == "test-1"
node = await project.add_node(compute, "test-{0}", None, node_type="vpcs", properties={"startup_config": "test.cfg"})
node = await project.add_node(
compute, "test-{0}", None, node_type="vpcs", properties={"startup_config": "test.cfg"}
)
assert node.name == "test-2"
node = await project.add_node(compute, "hello world-{0}", None, node_type="vpcs", properties={"startup_config": "test.cfg"})
node = await project.add_node(
compute, "hello world-{0}", None, node_type="vpcs", properties={"startup_config": "test.cfg"}
)
assert node.name == "helloworld-1"
node = await project.add_node(compute, "hello world-{0}", None, node_type="vpcs", properties={"startup_config": "test.cfg"})
node = await project.add_node(
compute, "hello world-{0}", None, node_type="vpcs", properties={"startup_config": "test.cfg"}
)
assert node.name == "helloworld-2"
node = await project.add_node(compute, "VPCS-1", None, node_type="vpcs", properties={"startup_config": "test.cfg"})
assert node.name == "VPCS-1"
@@ -1119,13 +1130,8 @@ async def test_duplicate_node(project):
compute.post = AsyncioMagicMock(return_value=response)
original = await project.add_node(
compute,
"test",
None,
node_type="vpcs",
properties={
"startup_config": "test.cfg"
})
compute, "test", None, node_type="vpcs", properties={"startup_config": "test.cfg"}
)
new_node = await project.duplicate_node(original, 42, 10, 11)
assert new_node.x == 42
@@ -1164,9 +1170,7 @@ async def test_add_node_missing_image_kept_in_degraded_state(controller):
)
assert node.missing_image is True
assert node.id in project._nodes
assert node.missing_images == [
{"property": "hda_disk_image", "image": "missing.qcow2", "image_type": "qemu"}
]
assert node.missing_images == [{"property": "hda_disk_image", "image": "missing.qcow2", "image_type": "qemu"}]
@pytest.mark.asyncio
+30 -41
View File
@@ -42,22 +42,16 @@ def demo_topology():
"revision": 5,
"topology": {
"computes": [
{
"compute_id": "local",
"host": "127.0.0.1",
"name": "atlantis",
"port": 3080,
"protocol": "http"
}
{"compute_id": "local", "host": "127.0.0.1", "name": "atlantis", "port": 3080, "protocol": "http"}
],
"drawings": [
{
"drawing_id": "48bdaa23-326a-4de0-bf7d-cc22709689ec",
"rotation": 0,
"svg": "<svg height=\"100\" width=\"200\"><rect fill=\"#ffffff\" fill-opacity=\"1.0\" height=\"100\" stroke=\"#000000\" stroke-width=\"2\" width=\"200\" /></svg>",
"svg": '<svg height="100" width="200"><rect fill="#ffffff" fill-opacity="1.0" height="100" stroke="#000000" stroke-width="2" width="200" /></svg>',
"x": -226,
"y": 57,
"z": 0
"z": 0,
}
],
"links": [
@@ -71,10 +65,10 @@ def demo_topology():
"style": "font-family: TypeWriter;font-size: 10;font-weight: bold;fill: #000000;fill-opacity: 1.0;",
"text": "Ethernet0",
"x": 72,
"y": 32
"y": 32,
},
"node_id": "64ba8408-afbf-4b66-9cdd-1fd854427478",
"port_number": 0
"port_number": 0,
},
{
"adapter_number": 0,
@@ -83,12 +77,12 @@ def demo_topology():
"style": "font-family: TypeWriter;font-size: 10;font-weight: bold;fill: #000000;fill-opacity: 1.0;",
"text": "Ethernet0",
"x": -7,
"y": 26
"y": 26,
},
"node_id": "748bcd89-624a-40eb-a8d3-1d2e85c99b51",
"port_number": 0
}
]
"port_number": 0,
},
],
}
],
"nodes": [
@@ -102,18 +96,17 @@ def demo_topology():
"style": "font-family: TypeWriter;font-size: 10;font-weight: bold;fill: #000000;fill-opacity: 1.0;",
"text": "PC1",
"x": 18,
"y": -25
"y": -25,
},
"name": "PC1",
"node_id": "64ba8408-afbf-4b66-9cdd-1fd854427478",
"node_type": "vpcs",
"properties": {
},
"properties": {},
"symbol": ":/symbols/computer.svg",
"width": 65,
"x": -300,
"y": -118,
"z": 1
"z": 1,
},
{
"compute_id": "vm",
@@ -125,28 +118,27 @@ def demo_topology():
"style": "font-family: TypeWriter;font-size: 10;font-weight: bold;fill: #000000;fill-opacity: 1.0;",
"text": "PC2",
"x": 18,
"y": -25
"y": -25,
},
"name": "PC2",
"node_id": "748bcd89-624a-40eb-a8d3-1d2e85c99b51",
"node_type": "vpcs",
"properties": {
},
"properties": {},
"symbol": ":/symbols/computer.svg",
"width": 65,
"x": -71,
"y": -98,
"z": 1
}
]
"z": 1,
},
],
},
"type": "topology",
"version": "2.0.0"
"version": "2.0.0",
}
# @pytest.mark.asyncio
#async def test_load_project(controller, tmpdir, demo_topology, http_client):
# async def test_load_project(controller, tmpdir, demo_topology, http_client):
#
# with open(str(tmpdir / "demo.gns3"), "w+") as f:
# json.dump(demo_topology, f)
@@ -182,14 +174,9 @@ async def test_open(controller, projects_dir):
"name": "demo",
"project_id": "3c1be6f9-b4ba-4737-b209-63c47c23359f",
"revision": 5,
"topology": {
"computes": [],
"drawings": [],
"links": [],
"nodes": []
},
"topology": {"computes": [], "drawings": [], "links": [], "nodes": []},
"type": "topology",
"version": "2.0.0"
"version": "2.0.0",
}
project_dir = os.path.join(projects_dir, "demo")
@@ -197,12 +184,14 @@ async def test_open(controller, projects_dir):
with open(os.path.join(project_dir, "demo.gns3"), "w+") as f:
json.dump(simple_topology, f)
project = Project(name="demo",
project_id="64ba8408-afbf-4b66-9cdd-1fd854427478",
path=project_dir,
controller=controller,
filename="demo.gns3",
status="closed")
project = Project(
name="demo",
project_id="64ba8408-afbf-4b66-9cdd-1fd854427478",
path=project_dir,
controller=controller,
filename="demo.gns3",
status="closed",
)
await project.open()
assert project.status == "opened"
@@ -212,7 +201,7 @@ async def test_open(controller, projects_dir):
# @pytest.mark.asyncio
#async def test_open_missing_compute(controller, tmpdir, demo_topology, http_client):
# async def test_open_missing_compute(controller, tmpdir, demo_topology, http_client):
# """
# If a compute is missing the project should not be open and the .gns3 should
# be the one before opening the project
+69 -97
View File
@@ -51,7 +51,6 @@ pytestmark = pytest.mark.asyncio
class TestPrivileges:
@pytest.mark.parametrize(
"privilege, path, result",
(
@@ -69,25 +68,14 @@ class TestPrivileges:
),
)
async def test_default_privileges_user_group(
self,
test_user: User,
db_session: AsyncSession,
privilege: str,
path: str,
result: bool
self, test_user: User, db_session: AsyncSession, privilege: str, path: str, result: bool
) -> None:
# add an ACE for path
if result:
group_id = (await UsersRepository(db_session).get_user_group_by_name("Users")).user_group_id
role_id = (await RbacRepository(db_session).get_role_by_name("User")).role_id
ace = ACECreate(
path=path,
ace_type="group",
propagate=False,
group_id=str(group_id),
role_id=str(role_id)
)
ace = ACECreate(path=path, ace_type="group", propagate=False, group_id=str(group_id), role_id=str(role_id))
await RbacRepository(db_session).create_ace(ace)
authorized = await RbacRepository(db_session).check_user_has_privilege(test_user.user_id, path, privilege)
@@ -122,13 +110,7 @@ class TestPrivileges:
# privileges on deeper levels replace those inherited from an upper level.
group_id = (await UsersRepository(db_session).get_user_group_by_name("Users")).user_group_id
role_id = (await RbacRepository(db_session).get_role_by_name("User")).role_id
ace = ACECreate(
path=path,
ace_type="group",
propagate=False,
group_id=str(group_id),
role_id=str(role_id)
)
ace = ACECreate(path=path, ace_type="group", propagate=False, group_id=str(group_id), role_id=str(role_id))
await RbacRepository(db_session).create_ace(ace)
authorized = await RbacRepository(db_session).check_user_has_privilege(test_user.user_id, path, privilege)
@@ -136,7 +118,6 @@ class TestPrivileges:
class TestResourcePools:
async def test_resource_pool(self, test_user: User, db_session: AsyncSession):
project_id = uuid.uuid4()
@@ -157,7 +138,7 @@ class TestResourcePools:
ace_type="group",
propagate=False,
group_id=str(group_id),
role_id=str(role_id)
role_id=str(role_id),
)
await RbacRepository(db_session).create_ace(ace)
@@ -167,12 +148,7 @@ class TestResourcePools:
assert authorized is True
async def test_list_projects_in_resource_pool(
self,
app: FastAPI,
controller: Controller,
base_client: AsyncClient,
db_session: AsyncSession,
test_user: User
self, app: FastAPI, controller: Controller, base_client: AsyncClient, db_session: AsyncSession, test_user: User
) -> None:
# Clean up any existing ACEs from previous tests
@@ -204,16 +180,16 @@ class TestResourcePools:
ace_type="group",
propagate=False,
group_id=str(group_id),
role_id=str(role_id)
role_id=str(role_id),
)
await RbacRepository(db_session).create_ace(ace)
# Create a new client with test user authentication
access_token = auth_service.create_access_token(test_user.username)
async with AsyncClient(
base_url="http://test-api",
headers={"Content-Type": "application/json", "Authorization": f"Bearer {access_token}"},
transport=ASGIWebSocketTransport(app=app)
base_url="http://test-api",
headers={"Content-Type": "application/json", "Authorization": f"Bearer {access_token}"},
transport=ASGIWebSocketTransport(app=app),
) as user_client:
# user should see only uuid2 (from resource pool)
response = await user_client.get(app.url_path_for("get_projects"))
@@ -228,11 +204,7 @@ class TestResourcePools:
# Now give user access to /projects (in addition to resource pool)
ace = ACECreate(
path="/projects",
ace_type="group",
propagate=True,
group_id=str(group_id),
role_id=str(role_id)
path="/projects", ace_type="group", propagate=True, group_id=str(group_id), role_id=str(role_id)
)
await RbacRepository(db_session).create_ace(ace)
@@ -277,66 +249,66 @@ class TestResourcePools:
# permissions_in_db = await rbac_repo.get_user_permissions(test_user.user_id)
# assert len(permissions_in_db) == 1
# assert permissions_in_db[0].path == f"/projects/{project_id}/*"
#
# response = await authorized_client.get(app.url_path_for("get_projects"))
# assert response.status_code == status.HTTP_200_OK
# projects = response.json()
# assert len(projects) == 1
#
# response = await authorized_client.get(app.url_path_for("get_projects"))
# assert response.status_code == status.HTTP_200_OK
# projects = response.json()
# assert len(projects) == 1
# async def test_admin_access_all_projects(self, app: FastAPI, client: AsyncClient):
#
# response = await client.get(app.url_path_for("get_projects"))
# assert response.status_code == status.HTTP_200_OK
# projects = response.json()
# assert len(projects) == 2
#
# async def test_admin_user_give_permission_on_project(
# self,
# app: FastAPI,
# client: AsyncClient,
# test_user: User
# ):
#
# response = await client.get(app.url_path_for("get_projects"))
# assert response.status_code == status.HTTP_200_OK
# projects = response.json()
# project_id = None
# for project in projects:
# if project["name"] == "Admin project":
# project_id = project["project_id"]
# break
#
# new_permission = {
# "methods": ["GET"],
# "path": f"/projects/{project_id}",
# "action": "ALLOW"
# }
# response = await client.post(app.url_path_for("create_permission"), json=new_permission)
# assert response.status_code == status.HTTP_201_CREATED
# permission_id = response.json()["permission_id"]
#
# response = await client.put(
# app.url_path_for(
# "add_permission_to_user",
# user_id=test_user.user_id,
# permission_id=permission_id
# )
# )
# assert response.status_code == status.HTTP_204_NO_CONTENT
#
# async def test_user_access_admin_project(
# self,
# app: FastAPI,
# authorized_client: AsyncClient,
# test_user: User,
# db_session: AsyncSession
# ) -> None:
#
# response = await authorized_client.get(app.url_path_for("get_projects"))
# assert response.status_code == status.HTTP_200_OK
# projects = response.json()
# assert len(projects) == 2
#
# async def test_admin_access_all_projects(self, app: FastAPI, client: AsyncClient):
#
# response = await client.get(app.url_path_for("get_projects"))
# assert response.status_code == status.HTTP_200_OK
# projects = response.json()
# assert len(projects) == 2
#
# async def test_admin_user_give_permission_on_project(
# self,
# app: FastAPI,
# client: AsyncClient,
# test_user: User
# ):
#
# response = await client.get(app.url_path_for("get_projects"))
# assert response.status_code == status.HTTP_200_OK
# projects = response.json()
# project_id = None
# for project in projects:
# if project["name"] == "Admin project":
# project_id = project["project_id"]
# break
#
# new_permission = {
# "methods": ["GET"],
# "path": f"/projects/{project_id}",
# "action": "ALLOW"
# }
# response = await client.post(app.url_path_for("create_permission"), json=new_permission)
# assert response.status_code == status.HTTP_201_CREATED
# permission_id = response.json()["permission_id"]
#
# response = await client.put(
# app.url_path_for(
# "add_permission_to_user",
# user_id=test_user.user_id,
# permission_id=permission_id
# )
# )
# assert response.status_code == status.HTTP_204_NO_CONTENT
#
# async def test_user_access_admin_project(
# self,
# app: FastAPI,
# authorized_client: AsyncClient,
# test_user: User,
# db_session: AsyncSession
# ) -> None:
#
# response = await authorized_client.get(app.url_path_for("get_projects"))
# assert response.status_code == status.HTTP_200_OK
# projects = response.json()
# assert len(projects) == 2
#
# class TestTemplatesWithRbac:
#
+3 -2
View File
@@ -67,7 +67,7 @@ def test_json(project):
"project_id": project.id,
"description": "Snapshot 'snapshot_test' taken on 2016-07-26 at 10:04:39",
"filename": "snapshot_test_260716_100439.gns3project",
"created_at": 1469527479
"created_at": 1469527479,
}
# new style snapshot
@@ -78,9 +78,10 @@ def test_json(project):
"project_id": project.id,
"filename": "snapshot_test2.gns3snapshot",
"description": mock.ANY,
"created_at": mock.ANY
"created_at": mock.ANY,
}
def test_invalid_snapshot_filename(project):
with pytest.raises(ValueError):
+4 -2
View File
@@ -7,8 +7,10 @@ def test_server_uptime_uses_process_creation_time() -> None:
process = MagicMock()
process.create_time.return_value = 50.25
with patch("gns3server.api.routes.controller.controller.time.time", return_value=200.75), \
patch("gns3server.api.routes.controller.controller.psutil.Process", return_value=process):
with (
patch("gns3server.api.routes.controller.controller.time.time", return_value=200.75),
patch("gns3server.api.routes.controller.controller.psutil.Process", return_value=process),
):
uptime = get_server_uptime_seconds()
assert uptime == 150
+10 -10
View File
@@ -29,16 +29,16 @@ def test_list(symbols_dir):
symbols = Symbols()
assert {
'symbol_id': ':/symbols/classic/firewall.svg',
'filename': 'firewall.svg',
'builtin': True,
'theme': 'Classic'
"symbol_id": ":/symbols/classic/firewall.svg",
"filename": "firewall.svg",
"builtin": True,
"theme": "Classic",
} in symbols.list()
assert {
'symbol_id': 'linux.svg',
'filename': 'linux.svg',
'builtin': False,
'theme': 'Custom symbols'
"symbol_id": "linux.svg",
"filename": "linux.svg",
"builtin": False,
"theme": "Custom symbols",
} in symbols.list()
@@ -46,7 +46,7 @@ def test_get_path():
symbols = Symbols()
symbols.theme = "Classic"
assert symbols.get_path(':/symbols/classic/firewall.svg') == get_resource("symbols/classic/firewall.svg")
assert symbols.get_path(":/symbols/classic/firewall.svg") == get_resource("symbols/classic/firewall.svg")
def test_get_path_with_themed_symbols():
@@ -62,4 +62,4 @@ def test_get_size():
symbols = Symbols()
symbols.theme = "Classic"
assert symbols.get_size(':/symbols/classic/firewall.svg') == (66, 45, 'svg')
assert symbols.get_size(":/symbols/classic/firewall.svg") == (66, 45, "svg")
+20 -51
View File
@@ -31,7 +31,7 @@ from gns3server.version import __version__
@pytest.mark.asyncio
async def test_project_to_topology_empty(tmpdir):
with patch('gns3server.controller.project.Project.emit_controller_notification'):
with patch("gns3server.controller.project.Project.emit_controller_notification"):
project = Project(name="Test")
topo = project_to_topology(project)
assert topo == {
@@ -50,18 +50,13 @@ async def test_project_to_topology_empty(tmpdir):
"snap_to_grid": False,
"grid_size": 75,
"drawing_grid_size": 25,
"topology": {
"nodes": [],
"links": [],
"computes": [],
"drawings": []
},
"topology": {"nodes": [], "links": [], "computes": [], "drawings": []},
"type": "topology",
"supplier": None,
"variables": None,
"version": __version__,
"marker_definitions": {},
"created_by": None
"created_by": None,
}
@@ -94,14 +89,8 @@ async def test_basic_topology(controller):
@pytest.mark.asyncio
async def test_project_to_topology(controller):
variables = [
{"name": "TEST1"},
{"name": "TEST2", "value": "value1"}
]
supplier = {
'logo': 'logo.png',
'url': 'http://example.com'
}
variables = [{"name": "TEST1"}, {"name": "TEST2", "value": "value1"}]
supplier = {"logo": "logo.png", "url": "http://example.com"}
project = Project(name="Test", controller=controller)
compute = Compute("my_compute", controller)
@@ -119,14 +108,10 @@ def test_load_topology(tmpdir):
"project_id": "69f26504-7aa3-48aa-9f29-798d44841211",
"name": "Test",
"revision": GNS3_FILE_FORMAT_REVISION,
"topology": {
"nodes": [],
"links": [],
"computes": [],
"drawings": []
},
"topology": {"nodes": [], "links": [], "computes": [], "drawings": []},
"type": "topology",
"version": __version__}
"version": __version__,
}
path = str(tmpdir / "test.gns3")
with open(path, "w+") as f:
@@ -146,9 +131,7 @@ def test_load_topology_file_error_schema_error(tmpdir):
path = str(tmpdir / "test.gns3")
with open(path, "w+") as f:
json.dump({
"revision": GNS3_FILE_FORMAT_REVISION
}, f)
json.dump({"revision": GNS3_FILE_FORMAT_REVISION}, f)
with pytest.raises(ControllerError):
load_topology(path)
@@ -163,10 +146,10 @@ def test_load_newer_topology(tmpdir):
"project_id": "69f26504-7aa3-48aa-9f29-798d44841211",
"name": "Test",
"revision": 42,
"topology": {
},
"topology": {},
"type": "topology",
"version": __version__}
"version": __version__,
}
path = str(tmpdir / "test.gns3")
with open(path, "w+") as f:
@@ -177,23 +160,16 @@ def test_load_newer_topology(tmpdir):
def test_load_topology_with_variables(tmpdir):
variables = [
{"name": "TEST1"},
{"name": "TEST2", "value": "value1"}
]
variables = [{"name": "TEST1"}, {"name": "TEST2", "value": "value1"}]
data = {
"project_id": "69f26504-7aa3-48aa-9f29-798d44841211",
"name": "Test",
"revision": GNS3_FILE_FORMAT_REVISION,
"topology": {
"nodes": [],
"links": [],
"computes": [],
"drawings": []
},
"topology": {"nodes": [], "links": [], "computes": [], "drawings": []},
"variables": variables,
"type": "topology",
"version": __version__}
"version": __version__,
}
path = str(tmpdir / "test.gns3")
with open(path, "w+") as f:
@@ -204,23 +180,16 @@ def test_load_topology_with_variables(tmpdir):
def test_load_topology_with_supplier(tmpdir):
supplier = {
'logo': 'logo.png',
'url': 'http://example.com'
}
supplier = {"logo": "logo.png", "url": "http://example.com"}
data = {
"project_id": "69f26504-7aa3-48aa-9f29-798d44841211",
"name": "Test",
"revision": GNS3_FILE_FORMAT_REVISION,
"topology": {
"nodes": [],
"links": [],
"computes": [],
"drawings": []
},
"topology": {"nodes": [], "links": [], "computes": [], "drawings": []},
"supplier": supplier,
"type": "topology",
"version": __version__}
"version": __version__,
}
path = str(tmpdir / "test.gns3")
with open(path, "w+") as f:
+149 -103
View File
@@ -72,25 +72,33 @@ async def test_create(project):
compute2.host = "example.org"
await link.add_node(node2, 3, 1)
compute1.post.assert_any_call("/projects/{}/vpcs/nodes/{}/adapters/0/ports/4/nio".format(project.id, node1.id), data={
"lport": 1024,
"rhost": "192.168.1.2",
"rport": 2048,
"type": "nio_udp",
"filters": {"delay": [10, 0]},
"markers": {},
"suspend": False,
}, timeout=120)
compute1.post.assert_any_call(
"/projects/{}/vpcs/nodes/{}/adapters/0/ports/4/nio".format(project.id, node1.id),
data={
"lport": 1024,
"rhost": "192.168.1.2",
"rport": 2048,
"type": "nio_udp",
"filters": {"delay": [10, 0]},
"markers": {},
"suspend": False,
},
timeout=120,
)
compute2.post.assert_any_call("/projects/{}/vpcs/nodes/{}/adapters/3/ports/1/nio".format(project.id, node2.id), data={
"lport": 2048,
"rhost": "192.168.1.1",
"rport": 1024,
"type": "nio_udp",
"filters": {},
"markers": {},
"suspend": False,
}, timeout=120)
compute2.post.assert_any_call(
"/projects/{}/vpcs/nodes/{}/adapters/3/ports/1/nio".format(project.id, node2.id),
data={
"lport": 2048,
"rhost": "192.168.1.1",
"rport": 1024,
"type": "nio_udp",
"filters": {},
"markers": {},
"suspend": False,
},
timeout=120,
)
@pytest.mark.asyncio
@@ -142,27 +150,37 @@ async def test_create_one_side_failure(project):
with pytest.raises(ControllerError):
await link.add_node(node2, 3, 1)
compute1.post.assert_any_call("/projects/{}/vpcs/nodes/{}/adapters/0/ports/4/nio".format(project.id, node1.id), data={
"lport": 1024,
"rhost": "192.168.1.2",
"rport": 2048,
"type": "nio_udp",
"filters": {},
"markers": {},
"suspend": False,
}, timeout=120)
compute1.post.assert_any_call(
"/projects/{}/vpcs/nodes/{}/adapters/0/ports/4/nio".format(project.id, node1.id),
data={
"lport": 1024,
"rhost": "192.168.1.2",
"rport": 2048,
"type": "nio_udp",
"filters": {},
"markers": {},
"suspend": False,
},
timeout=120,
)
compute2.post.assert_any_call("/projects/{}/vpcs/nodes/{}/adapters/3/ports/1/nio".format(project.id, node2.id), data={
"lport": 2048,
"rhost": "192.168.1.1",
"rport": 1024,
"type": "nio_udp",
"filters": {},
"markers": {},
"suspend": False,
}, timeout=120)
compute2.post.assert_any_call(
"/projects/{}/vpcs/nodes/{}/adapters/3/ports/1/nio".format(project.id, node2.id),
data={
"lport": 2048,
"rhost": "192.168.1.1",
"rport": 1024,
"type": "nio_udp",
"filters": {},
"markers": {},
"suspend": False,
},
timeout=120,
)
# The link creation has failed we rollback the nio
compute1.delete.assert_any_call("/projects/{}/vpcs/nodes/{}/adapters/0/ports/4/nio".format(project.id, node1.id), timeout=120)
compute1.delete.assert_any_call(
"/projects/{}/vpcs/nodes/{}/adapters/0/ports/4/nio".format(project.id, node1.id), timeout=120
)
@pytest.mark.asyncio
@@ -183,8 +201,12 @@ async def test_delete(project):
await link.delete()
compute1.delete.assert_any_call("/projects/{}/vpcs/nodes/{}/adapters/0/ports/4/nio".format(project.id, node1.id), timeout=120)
compute2.delete.assert_any_call("/projects/{}/vpcs/nodes/{}/adapters/3/ports/1/nio".format(project.id, node2.id), timeout=120)
compute1.delete.assert_any_call(
"/projects/{}/vpcs/nodes/{}/adapters/0/ports/4/nio".format(project.id, node1.id), timeout=120
)
compute2.delete.assert_any_call(
"/projects/{}/vpcs/nodes/{}/adapters/3/ports/1/nio".format(project.id, node2.id), timeout=120
)
@pytest.mark.asyncio
@@ -286,15 +308,19 @@ async def test_reset(project):
assert link.debug_link_data[0]["lport"] != link.debug_link_data[0]["rport"]
assert link.debug_link_data[1]["lport"] != link.debug_link_data[1]["rport"]
# the committed NIO carries the fresh pair, not the released one
compute1.post.assert_any_call("/projects/{}/vpcs/nodes/{}/adapters/0/ports/4/nio".format(project.id, node1.id), data={
"lport": 4096,
"rhost": "192.168.1.2",
"rport": 8192,
"type": "nio_udp",
"filters": {},
"markers": {},
"suspend": False,
}, timeout=120)
compute1.post.assert_any_call(
"/projects/{}/vpcs/nodes/{}/adapters/0/ports/4/nio".format(project.id, node1.id),
data={
"lport": 4096,
"rhost": "192.168.1.2",
"rport": 8192,
"type": "nio_udp",
"filters": {},
"markers": {},
"suspend": False,
},
timeout=120,
)
@pytest.mark.asyncio
@@ -373,15 +399,17 @@ async def test_capture(project):
await link.start_capture()
assert link.capturing
compute1.post.assert_any_call("/projects/{}/vpcs/nodes/{}/adapters/0/ports/4/capture/start".format(project.id, node_vpcs.id), data={
"capture_file_name": link.default_capture_file_name(),
"data_link_type": "DLT_EN10MB"
})
compute1.post.assert_any_call(
"/projects/{}/vpcs/nodes/{}/adapters/0/ports/4/capture/start".format(project.id, node_vpcs.id),
data={"capture_file_name": link.default_capture_file_name(), "data_link_type": "DLT_EN10MB"},
)
await link.stop_capture()
assert link.capturing is False
compute1.post.assert_any_call("/projects/{}/vpcs/nodes/{}/adapters/0/ports/4/capture/stop".format(project.id, node_vpcs.id))
compute1.post.assert_any_call(
"/projects/{}/vpcs/nodes/{}/adapters/0/ports/4/capture/stop".format(project.id, node_vpcs.id)
)
@pytest.mark.asyncio
@@ -453,40 +481,49 @@ async def test_update(project):
compute2.host = "example.org"
await link.add_node(node2, 3, 1)
compute1.post.assert_any_call("/projects/{}/vpcs/nodes/{}/adapters/0/ports/4/nio".format(project.id, node1.id), data={
"lport": 1024,
"rhost": "192.168.1.2",
"rport": 2048,
"type": "nio_udp",
"suspend": False,
"markers": {},
"filters": {"delay": [10, 0]}
}, timeout=120)
compute1.post.assert_any_call(
"/projects/{}/vpcs/nodes/{}/adapters/0/ports/4/nio".format(project.id, node1.id),
data={
"lport": 1024,
"rhost": "192.168.1.2",
"rport": 2048,
"type": "nio_udp",
"suspend": False,
"markers": {},
"filters": {"delay": [10, 0]},
},
timeout=120,
)
compute2.post.assert_any_call("/projects/{}/vpcs/nodes/{}/adapters/3/ports/1/nio".format(project.id, node2.id), data={
"lport": 2048,
"rhost": "192.168.1.1",
"rport": 1024,
"type": "nio_udp",
"suspend": False,
"markers": {},
"filters": {}
}, timeout=120)
compute2.post.assert_any_call(
"/projects/{}/vpcs/nodes/{}/adapters/3/ports/1/nio".format(project.id, node2.id),
data={
"lport": 2048,
"rhost": "192.168.1.1",
"rport": 1024,
"type": "nio_udp",
"suspend": False,
"markers": {},
"filters": {},
},
timeout=120,
)
assert link.created
await link.update_filters({"frequency_drop": [5], "bpf": ["icmp[icmptype] == 8"]})
compute1.put.assert_any_call("/projects/{}/vpcs/nodes/{}/adapters/0/ports/4/nio".format(project.id, node1.id), data={
"lport": 1024,
"rhost": "192.168.1.2",
"rport": 2048,
"type": "nio_udp",
"suspend": False,
"markers": {},
"filters": {
"frequency_drop": [5],
"bpf": ["icmp[icmptype] == 8"]
}
}, timeout=120)
compute1.put.assert_any_call(
"/projects/{}/vpcs/nodes/{}/adapters/0/ports/4/nio".format(project.id, node1.id),
data={
"lport": 1024,
"rhost": "192.168.1.2",
"rport": 2048,
"type": "nio_udp",
"suspend": False,
"markers": {},
"filters": {"frequency_drop": [5], "bpf": ["icmp[icmptype] == 8"]},
},
timeout=120,
)
@pytest.mark.asyncio
@@ -533,8 +570,9 @@ async def test_update_ethernet_switch_nio(project):
"type": "nio_udp",
"suspend": False,
"markers": {},
"filters": {}
}, timeout=221
"filters": {},
},
timeout=221,
)
@@ -585,22 +623,30 @@ async def test_update_suspend(project):
compute2.host = "example.org"
await link.add_node(node2, 3, 1)
compute1.post.assert_any_call("/projects/{}/vpcs/nodes/{}/adapters/0/ports/4/nio".format(project.id, node1.id), data={
"lport": 1024,
"rhost": "192.168.1.2",
"rport": 2048,
"type": "nio_udp",
"filters": {"frequency_drop": [-1]},
"markers": {},
"suspend": True
}, timeout=120)
compute1.post.assert_any_call(
"/projects/{}/vpcs/nodes/{}/adapters/0/ports/4/nio".format(project.id, node1.id),
data={
"lport": 1024,
"rhost": "192.168.1.2",
"rport": 2048,
"type": "nio_udp",
"filters": {"frequency_drop": [-1]},
"markers": {},
"suspend": True,
},
timeout=120,
)
compute2.post.assert_any_call("/projects/{}/vpcs/nodes/{}/adapters/3/ports/1/nio".format(project.id, node2.id), data={
"lport": 2048,
"rhost": "192.168.1.1",
"rport": 1024,
"type": "nio_udp",
"filters": {},
"markers": {},
"suspend": True
}, timeout=120)
compute2.post.assert_any_call(
"/projects/{}/vpcs/nodes/{}/adapters/3/ports/1/nio".format(project.id, node2.id),
data={
"lport": 2048,
"rhost": "192.168.1.1",
"rport": 1024,
"type": "nio_udp",
"filters": {},
"markers": {},
"suspend": True,
},
timeout=120,
)