mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-04 06:21:15 +02:00
refactor: ruff autofixes
This commit is contained in:
455 files changed
+12322
-14762
No files matched your search
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"}},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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]
|
||||
@@ -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"):
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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})
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
#
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
@@ -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")
|
||||
@@ -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
@@ -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,
|
||||
)
|
||||
Reference in new issue
Block a user