mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-10-20 18:02:22 +03:00
fix: repair imports lost in the 3.1 rebase and meet the new lint baseline
The rebase onto upstream 3.1 (mypy + ruff I001/F401 waves) left the tree short of the newly enforced baselines in three ways: * contextlib / UbridgeError imports dropped as false duplicates during conflict resolution left NameErrors in iou_vm and qemu_vm (F821); the Dynamips router grabbed the compute-level NIOTAP instead of the hypervisor one, breaking TAP anchoring (test failure). * unused imports (KERNEL_UNSUPPORTED_FILTERS, UbridgeError, List) and unsorted blocks left behind where the stack's later commits moved the last usage elsewhere — ruff I001/F401/RUF022 now enforce these. * mypy, now baseline-free: cache dict annotations in tc_probe, the locators candidate list, the resolved-params None branch in the MCP runner, and validate_bpf_syntax keeps the upstream Dict[str, Any] return (the strict Optional[str] was wrong for the "valid" bool entry). The e2e raw-frame probe resolves its compiler via shutil.which (S607) and zips with strict= (B905); the remaining subprocess call is S603 by nature and gets a per-file ignore like other test files. ruff check/format clean, mypy at upstream parity (no new errors), full unit suite 2167 passed.
This commit is contained in:
parent
e13e5f3804
commit
7d04d08dba
@ -47,6 +47,7 @@ from nornir.core.task import AggregatedResult, Result, Task
|
|||||||
from nornir_netmiko.tasks import netmiko_send_config
|
from nornir_netmiko.tasks import netmiko_send_config
|
||||||
|
|
||||||
from gns3server.agent.gns3_copilot.gns3_client import get_gns3_server_host
|
from gns3server.agent.gns3_copilot.gns3_client import get_gns3_server_host
|
||||||
|
|
||||||
# Import custom Netmiko device types for GNS3 emulation
|
# Import custom Netmiko device types for GNS3 emulation
|
||||||
# This registers gns3_huawei_telnet_ce and other custom device types
|
# This registers gns3_huawei_telnet_ce and other custom device types
|
||||||
# NOTE: Must be imported BEFORE any Nornir operations to ensure device types are registered
|
# NOTE: Must be imported BEFORE any Nornir operations to ensure device types are registered
|
||||||
|
|||||||
@ -47,6 +47,7 @@ from nornir.core.task import AggregatedResult, Result, Task
|
|||||||
from nornir_netmiko.tasks import netmiko_multiline
|
from nornir_netmiko.tasks import netmiko_multiline
|
||||||
|
|
||||||
from gns3server.agent.gns3_copilot.gns3_client import get_gns3_server_host
|
from gns3server.agent.gns3_copilot.gns3_client import get_gns3_server_host
|
||||||
|
|
||||||
# Import custom Netmiko device types for GNS3 emulation
|
# Import custom Netmiko device types for GNS3 emulation
|
||||||
# This registers gns3_huawei_telnet_ce and other custom device types
|
# This registers gns3_huawei_telnet_ce and other custom device types
|
||||||
# NOTE: Must be imported BEFORE any Nornir operations to ensure device types are registered
|
# NOTE: Must be imported BEFORE any Nornir operations to ensure device types are registered
|
||||||
|
|||||||
@ -47,6 +47,7 @@ from nornir.core.task import AggregatedResult, Result, Task
|
|||||||
from nornir_netmiko.tasks import netmiko_multiline
|
from nornir_netmiko.tasks import netmiko_multiline
|
||||||
|
|
||||||
from gns3server.agent.gns3_copilot.gns3_client import get_gns3_server_host
|
from gns3server.agent.gns3_copilot.gns3_client import get_gns3_server_host
|
||||||
|
|
||||||
# Import custom Netmiko device types for GNS3 emulation
|
# Import custom Netmiko device types for GNS3 emulation
|
||||||
# This registers gns3_vpcs_telnet and other custom device types
|
# This registers gns3_vpcs_telnet and other custom device types
|
||||||
# NOTE: Must be imported BEFORE any Nornir operations to ensure device types are registered
|
# NOTE: Must be imported BEFORE any Nornir operations to ensure device types are registered
|
||||||
|
|||||||
@ -346,13 +346,13 @@ def _run_handler_sync(handler, params: dict[str, Any]) -> list[dict[str, Any]]:
|
|||||||
|
|
||||||
# Name-shaped project_id/node_id values become UUIDs before the handler
|
# Name-shaped project_id/node_id values become UUIDs before the handler
|
||||||
# runs; a name that matches nothing fails fast with the known names.
|
# runs; a name that matches nothing fails fast with the known names.
|
||||||
params, resolution_error = resolve_locators(params, run)
|
resolved_params, resolution_error = resolve_locators(params, run)
|
||||||
if resolution_error is not None:
|
if resolved_params is None:
|
||||||
result = resolution_error
|
result = resolution_error
|
||||||
else:
|
else:
|
||||||
result = handler(params, ctx)
|
result = handler(resolved_params, ctx)
|
||||||
# backstop for UUID transcription slips: "Did you mean ...?"
|
# backstop for UUID transcription slips: "Did you mean ...?"
|
||||||
result = add_did_you_mean(result, params, run)
|
result = add_did_you_mean(result, resolved_params, run)
|
||||||
return [{"type": "text", "text": json.dumps(result, ensure_ascii=False, default=str)}]
|
return [{"type": "text", "text": json.dumps(result, ensure_ascii=False, default=str)}]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -37,9 +37,10 @@ distance 2 earns a "Did you mean ..." hint.
|
|||||||
import re
|
import re
|
||||||
from typing import Any, Callable
|
from typing import Any, Callable
|
||||||
|
|
||||||
from .projects import list_projects_handler
|
|
||||||
from gns3server.agent.gns3_copilot.gns3_client.api_handlers import get_links_handler, get_nodes_handler
|
from gns3server.agent.gns3_copilot.gns3_client.api_handlers import get_links_handler, get_nodes_handler
|
||||||
|
|
||||||
|
from .projects import list_projects_handler
|
||||||
|
|
||||||
_UUID_RE = re.compile(r"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$")
|
_UUID_RE = re.compile(r"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$")
|
||||||
_ID_IN_TEXT_RE = re.compile(r"[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}")
|
_ID_IN_TEXT_RE = re.compile(r"[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}")
|
||||||
|
|
||||||
@ -94,9 +95,7 @@ def resolve_locators(
|
|||||||
return params, None
|
return params, None
|
||||||
|
|
||||||
|
|
||||||
def add_did_you_mean(
|
def add_did_you_mean(result: Any, params: dict[str, Any], run: Callable[[Callable, dict], Any]) -> Any:
|
||||||
result: Any, params: dict[str, Any], run: Callable[[Callable, dict], Any]
|
|
||||||
) -> Any:
|
|
||||||
"""
|
"""
|
||||||
Append a "Did you mean ...?" hint to an error reply that names a
|
Append a "Did you mean ...?" hint to an error reply that names a
|
||||||
non-existent UUID, when exactly one in-scope id is within edit
|
non-existent UUID, when exactly one in-scope id is within edit
|
||||||
@ -118,7 +117,11 @@ def add_did_you_mean(
|
|||||||
|
|
||||||
hints = []
|
hints = []
|
||||||
for bad in bad_ids:
|
for bad in bad_ids:
|
||||||
close = [(cid, label) for cid, label in candidates if bad != cid and _edit_distance_at_most(bad.lower(), cid.lower(), 2)]
|
close = [
|
||||||
|
(cid, label)
|
||||||
|
for cid, label in candidates
|
||||||
|
if bad != cid and _edit_distance_at_most(bad.lower(), cid.lower(), 2)
|
||||||
|
]
|
||||||
if len(close) == 1:
|
if len(close) == 1:
|
||||||
cid, label = close[0]
|
cid, label = close[0]
|
||||||
hints.append(f"Did you mean {label} ({cid})?")
|
hints.append(f"Did you mean {label} ({cid})?")
|
||||||
@ -177,7 +180,7 @@ def _collect_candidates(params: dict[str, Any], run: Callable[[Callable, dict],
|
|||||||
just yields what was gathered so far.
|
just yields what was gathered so far.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
candidates = []
|
candidates: list[tuple[str, str]] = []
|
||||||
try:
|
try:
|
||||||
projects = _as_items(run(list_projects_handler, {}), "projects") or []
|
projects = _as_items(run(list_projects_handler, {}), "projects") or []
|
||||||
candidates.extend((p["project_id"], f"project '{p.get('name', '?')}'") for p in projects if p.get("project_id"))
|
candidates.extend((p["project_id"], f"project '{p.get('name', '?')}'") for p in projects if p.get("project_id"))
|
||||||
|
|||||||
@ -28,8 +28,8 @@ from gns3server.compute import MODULES
|
|||||||
from gns3server.compute.ubridge.tc_probe import (
|
from gns3server.compute.ubridge.tc_probe import (
|
||||||
probe_bridge_tap_support,
|
probe_bridge_tap_support,
|
||||||
probe_iol_tap_support,
|
probe_iol_tap_support,
|
||||||
probe_tc_capabilities,
|
|
||||||
probe_tap_support,
|
probe_tap_support,
|
||||||
|
probe_tc_capabilities,
|
||||||
)
|
)
|
||||||
from gns3server.utils.path import get_default_project_directory
|
from gns3server.utils.path import get_default_project_directory
|
||||||
from gns3server.utils.tc_capabilities import usable_ebpf_modes
|
from gns3server.utils.tc_capabilities import usable_ebpf_modes
|
||||||
|
|||||||
@ -264,8 +264,10 @@ async def duplicate_docker_node(
|
|||||||
dependencies=[Depends(compute_authentication)],
|
dependencies=[Depends(compute_authentication)],
|
||||||
)
|
)
|
||||||
async def create_docker_node_nio(
|
async def create_docker_node_nio(
|
||||||
adapter_number: int, port_number: int, nio_data: Union[schemas.UDPNIO, schemas.BridgeNIO],
|
adapter_number: int,
|
||||||
node: DockerVM = Depends(dep_node)
|
port_number: int,
|
||||||
|
nio_data: Union[schemas.UDPNIO, schemas.BridgeNIO],
|
||||||
|
node: DockerVM = Depends(dep_node),
|
||||||
) -> Union[schemas.UDPNIO, schemas.BridgeNIO]:
|
) -> Union[schemas.UDPNIO, schemas.BridgeNIO]:
|
||||||
"""
|
"""
|
||||||
Add a NIO (Network Input/Output) to the node.
|
Add a NIO (Network Input/Output) to the node.
|
||||||
@ -284,8 +286,10 @@ async def create_docker_node_nio(
|
|||||||
dependencies=[Depends(compute_authentication)],
|
dependencies=[Depends(compute_authentication)],
|
||||||
)
|
)
|
||||||
async def update_docker_node_nio(
|
async def update_docker_node_nio(
|
||||||
adapter_number: int, port_number: int, nio_data: Union[schemas.UDPNIO, schemas.BridgeNIO],
|
adapter_number: int,
|
||||||
node: DockerVM = Depends(dep_node)
|
port_number: int,
|
||||||
|
nio_data: Union[schemas.UDPNIO, schemas.BridgeNIO],
|
||||||
|
node: DockerVM = Depends(dep_node),
|
||||||
) -> Union[schemas.UDPNIO, schemas.BridgeNIO]:
|
) -> Union[schemas.UDPNIO, schemas.BridgeNIO]:
|
||||||
"""
|
"""
|
||||||
Update a NIO (Network Input/Output) on the node.
|
Update a NIO (Network Input/Output) on the node.
|
||||||
|
|||||||
@ -103,20 +103,20 @@ async def update_ethernet_switch(
|
|||||||
Update an Ethernet switch.
|
Update an Ethernet switch.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
data = jsonable_encoder(node_data, exclude_unset=True)
|
||||||
if "name" in node_data and node.name != node_data["name"]:
|
if "name" in data and node.name != data["name"]:
|
||||||
node.name = node_data["name"]
|
node.name = data["name"]
|
||||||
if "usage" in node_data:
|
if "usage" in data:
|
||||||
node.usage = node_data["usage"]
|
node.usage = data["usage"]
|
||||||
if "ports_mapping" in node_data:
|
if "ports_mapping" in data:
|
||||||
# capture the mapping before the setter replaces it: the VLAN
|
# capture the mapping before the setter replaces it: the VLAN
|
||||||
# reconcile diffs old against new to touch only what changed, in
|
# reconcile diffs old against new to touch only what changed, in
|
||||||
# place (the setter itself keeps the port-count guard)
|
# place (the setter itself keeps the port-count guard)
|
||||||
previous_mapping = [dict(port) for port in node.ports_mapping]
|
previous_mapping = [dict(port) for port in node.ports_mapping]
|
||||||
node.ports_mapping = node_data["ports_mapping"]
|
node.ports_mapping = data["ports_mapping"]
|
||||||
await node.update_port_settings(previous_mapping)
|
await node.update_port_settings(previous_mapping)
|
||||||
if "console_type" in node_data:
|
if "console_type" in data:
|
||||||
node.console_type = node_data["console_type"]
|
node.console_type = data["console_type"]
|
||||||
node.updated()
|
node.updated()
|
||||||
return node.asdict()
|
return node.asdict()
|
||||||
|
|
||||||
|
|||||||
@ -24,14 +24,13 @@ markers, attach/detach, adapter carrier — lives in
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import contextlib
|
import contextlib
|
||||||
|
import logging
|
||||||
|
|
||||||
from gns3server.compute.kernel_datapath import KernelDatapathMixin
|
from gns3server.compute.kernel_datapath import KernelDatapathMixin
|
||||||
from gns3server.compute.ubridge.ubridge_error import UbridgeError, UbridgeNamespaceError
|
from gns3server.compute.ubridge.ubridge_error import UbridgeError, UbridgeNamespaceError
|
||||||
|
|
||||||
from .docker_error import DockerError
|
|
||||||
from gns3server.utils.kernel_anchor import anchor_suffix, kernel_anchor_name
|
from gns3server.utils.kernel_anchor import anchor_suffix, kernel_anchor_name
|
||||||
|
|
||||||
import logging
|
from .docker_error import DockerError
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|||||||
@ -33,14 +33,13 @@ import aiohttp
|
|||||||
import psutil
|
import psutil
|
||||||
|
|
||||||
from gns3server.compute.compute_error import ComputeError
|
from gns3server.compute.compute_error import ComputeError
|
||||||
from gns3server.compute.ubridge.ubridge_error import UbridgeError, UbridgeNamespaceError
|
from gns3server.compute.ubridge.ubridge_error import UbridgeNamespaceError
|
||||||
from gns3server.utils import int_to_macaddress, macaddress_to_int
|
from gns3server.utils import int_to_macaddress, macaddress_to_int
|
||||||
from gns3server.utils.asyncio import monitor_process, wait_for_file_creation, wait_run_in_executor
|
from gns3server.utils.asyncio import monitor_process, wait_for_file_creation, wait_run_in_executor
|
||||||
from gns3server.utils.asyncio.raw_command_server import AsyncioRawCommandServer
|
from gns3server.utils.asyncio.raw_command_server import AsyncioRawCommandServer
|
||||||
from gns3server.utils.asyncio.ssh_server import AsyncioSSHServer
|
from gns3server.utils.asyncio.ssh_server import AsyncioSSHServer
|
||||||
from gns3server.utils.asyncio.telnet_server import AsyncioTelnetServer
|
from gns3server.utils.asyncio.telnet_server import AsyncioTelnetServer
|
||||||
from gns3server.utils.hostname import is_rfc1123_hostname_valid
|
from gns3server.utils.hostname import is_rfc1123_hostname_valid
|
||||||
from gns3server.utils.packet_filter_validation import KERNEL_UNSUPPORTED_FILTERS
|
|
||||||
|
|
||||||
from ..adapters.ethernet_adapter import EthernetAdapter
|
from ..adapters.ethernet_adapter import EthernetAdapter
|
||||||
from ..base_node import BaseNode
|
from ..base_node import BaseNode
|
||||||
|
|||||||
@ -672,4 +672,3 @@ class IOLDockerVM(VendorDockerVM):
|
|||||||
|
|
||||||
if self._kernel_host_ifc(adapter_number, port_number) is not None:
|
if self._kernel_host_ifc(adapter_number, port_number) is not None:
|
||||||
await KernelDatapathMixin._set_adapter_carrier(self, adapter_number, connected, port_number)
|
await KernelDatapathMixin._set_adapter_carrier(self, adapter_number, connected, port_number)
|
||||||
|
|
||||||
|
|||||||
@ -42,10 +42,10 @@ from ...base_node import BaseNode
|
|||||||
from ...error import NodeError
|
from ...error import NodeError
|
||||||
from ...kernel_datapath import KernelDatapathMixin
|
from ...kernel_datapath import KernelDatapathMixin
|
||||||
from ...nios.nio_bridge import NIOBridge
|
from ...nios.nio_bridge import NIOBridge
|
||||||
from ...nios.nio_tap import NIOTAP
|
|
||||||
from ...ubridge.ubridge_error import UbridgeError
|
from ...ubridge.ubridge_error import UbridgeError
|
||||||
from ..adapters.adapter import ETHERNET_ADAPTERS, ETHERNET_WICS
|
from ..adapters.adapter import ETHERNET_ADAPTERS, ETHERNET_WICS
|
||||||
from ..dynamips_error import DynamipsError
|
from ..dynamips_error import DynamipsError
|
||||||
|
from ..nios.nio_tap import NIOTAP
|
||||||
|
|
||||||
|
|
||||||
class Router(KernelDatapathMixin, BaseNode):
|
class Router(KernelDatapathMixin, BaseNode):
|
||||||
|
|||||||
@ -22,6 +22,7 @@ order to run an IOU VM.
|
|||||||
import asyncio
|
import asyncio
|
||||||
import binascii
|
import binascii
|
||||||
import configparser
|
import configparser
|
||||||
|
import contextlib
|
||||||
import functools
|
import functools
|
||||||
import glob
|
import glob
|
||||||
import hashlib
|
import hashlib
|
||||||
@ -951,9 +952,7 @@ class IOUVM(KernelDatapathMixin, BaseNode):
|
|||||||
raise self._kernel_error(
|
raise self._kernel_error(
|
||||||
"Bay {bay}/{unit} of IOU '{name}' has no TAP anchor to carry a kernel link "
|
"Bay {bay}/{unit} of IOU '{name}' has no TAP anchor to carry a kernel link "
|
||||||
"(serial ports stay on the relay datapath, and a node whose uBridge lacks "
|
"(serial ports stay on the relay datapath, and a node whose uBridge lacks "
|
||||||
"iol_bridge add_nio_tap runs relay-only)".format(
|
"iol_bridge add_nio_tap runs relay-only)".format(bay=adapter_number, unit=port_number, name=self._name)
|
||||||
bay=adapter_number, unit=port_number, name=self._name
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
await self._ubridge_send(
|
await self._ubridge_send(
|
||||||
'iol_bridge add_nio_tap {name} {iol_id} {bay} {unit} "{tap}"'.format(
|
'iol_bridge add_nio_tap {name} {iol_id} {bay} {unit} "{tap}"'.format(
|
||||||
|
|||||||
@ -41,17 +41,16 @@ A node type using this mixin provides:
|
|||||||
failures surface as ``DockerError`` / ``QemuError`` rather than ``NodeError``.
|
failures surface as ``DockerError`` / ``QemuError`` rather than ``NodeError``.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
|
||||||
import contextlib
|
import contextlib
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
from gns3server.utils.tc_capabilities import FILTER_EBPF_MODES, parse_tc_capabilities, usable_ebpf_modes
|
|
||||||
from gns3server.compute.ubridge.ubridge_error import UbridgeError
|
|
||||||
from gns3server.compute.error import NodeError
|
from gns3server.compute.error import NodeError
|
||||||
|
from gns3server.compute.ubridge.ubridge_error import UbridgeError
|
||||||
|
from gns3server.utils.tc_capabilities import FILTER_EBPF_MODES, parse_tc_capabilities, usable_ebpf_modes
|
||||||
|
|
||||||
from .nios.nio_bridge import NIOBridge
|
from .nios.nio_bridge import NIOBridge
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -20,6 +20,7 @@ order to run a QEMU VM.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import contextlib
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import math
|
import math
|
||||||
@ -53,6 +54,7 @@ from ..kernel_datapath import KernelDatapathMixin
|
|||||||
from ..nios.nio_bridge import NIOBridge
|
from ..nios.nio_bridge import NIOBridge
|
||||||
from ..nios.nio_tap import NIOTAP
|
from ..nios.nio_tap import NIOTAP
|
||||||
from ..nios.nio_udp import NIOUDP
|
from ..nios.nio_udp import NIOUDP
|
||||||
|
from ..ubridge.ubridge_error import UbridgeError
|
||||||
from .qemu_error import QemuError
|
from .qemu_error import QemuError
|
||||||
from .utils.qcow2 import Qcow2, Qcow2Error
|
from .utils.qcow2 import Qcow2, Qcow2Error
|
||||||
from .utils.ziputils import pack_zip, unpack_zip
|
from .utils.ziputils import pack_zip, unpack_zip
|
||||||
|
|||||||
@ -32,29 +32,31 @@ failure (missing binary, old build answering "Unknown command", timeout)
|
|||||||
means unknown, reported as None and cached as such.
|
means unknown, reported as None and cached as such.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
|
||||||
import uuid
|
|
||||||
import shutil
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import tempfile
|
|
||||||
import logging
|
|
||||||
import contextlib
|
import contextlib
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import tempfile
|
||||||
|
import uuid
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from gns3server.config import Config
|
from gns3server.config import Config
|
||||||
from gns3server.utils.tc_capabilities import parse_tc_capabilities
|
from gns3server.utils.tc_capabilities import parse_tc_capabilities
|
||||||
|
|
||||||
from .hypervisor import Hypervisor
|
from .hypervisor import Hypervisor
|
||||||
from .ubridge_error import UbridgeError
|
from .ubridge_error import UbridgeError
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
# binary identity -> parsed report (None = probed and unusable)
|
# binary identity -> parsed report (None = probed and unusable)
|
||||||
_cache = {}
|
_cache: dict[tuple[str, int, int], Optional[dict[str, str]]] = {}
|
||||||
# binary identity -> whether the tap module works (None = unknown)
|
# binary identity -> whether the tap module works (None = unknown)
|
||||||
_tap_cache = {}
|
_tap_cache: dict[tuple[str, int, int], Optional[bool]] = {}
|
||||||
# binary identity -> whether iol_bridge can bind a port to a TAP (None = unknown)
|
# binary identity -> whether iol_bridge can bind a port to a TAP (None = unknown)
|
||||||
_iol_tap_cache = {}
|
_iol_tap_cache: dict[tuple[str, int, int], Optional[bool]] = {}
|
||||||
# binary identity -> whether the bridge module can release a named TAP NIO (None = unknown)
|
# binary identity -> whether the bridge module can release a named TAP NIO (None = unknown)
|
||||||
_bridge_tap_cache = {}
|
_bridge_tap_cache: dict[tuple[str, int, int], Optional[bool]] = {}
|
||||||
_lock = asyncio.Lock()
|
_lock = asyncio.Lock()
|
||||||
|
|
||||||
|
|
||||||
@ -252,9 +254,7 @@ async def _probe_iol_tap(path, config, timeout):
|
|||||||
await hypervisor.send(f"iol_bridge create {bridge} {_IOL_PROBE_BRIDGE_ID}")
|
await hypervisor.send(f"iol_bridge create {bridge} {_IOL_PROBE_BRIDGE_ID}")
|
||||||
try:
|
try:
|
||||||
await hypervisor.send(f"tap create {tap}")
|
await hypervisor.send(f"tap create {tap}")
|
||||||
await hypervisor.send(
|
await hypervisor.send(f"iol_bridge add_nio_tap {bridge} {_IOL_PROBE_BRIDGE_ID - 1} 0 0 {tap}")
|
||||||
f"iol_bridge add_nio_tap {bridge} {_IOL_PROBE_BRIDGE_ID - 1} 0 0 {tap}"
|
|
||||||
)
|
|
||||||
await hypervisor.send(f"iol_bridge delete_nio_tap {bridge} 0 0")
|
await hypervisor.send(f"iol_bridge delete_nio_tap {bridge} 0 0")
|
||||||
finally:
|
finally:
|
||||||
# iol_bridge delete releases every port NIO (the TAP fd),
|
# iol_bridge delete releases every port NIO (the TAP fd),
|
||||||
|
|||||||
@ -23,7 +23,6 @@ from gns3server.config import Config
|
|||||||
from gns3server.utils.application_id import is_iol_runner_environment
|
from gns3server.utils.application_id import is_iol_runner_environment
|
||||||
from gns3server.utils.kernel_anchor import kernel_anchor_name, kernel_anchor_type, kernel_cascade_names
|
from gns3server.utils.kernel_anchor import kernel_anchor_name, kernel_anchor_type, kernel_cascade_names
|
||||||
from gns3server.utils.packet_filter_validation import (
|
from gns3server.utils.packet_filter_validation import (
|
||||||
KERNEL_UNSUPPORTED_FILTERS,
|
|
||||||
split_kernel_only_features,
|
split_kernel_only_features,
|
||||||
validate_bpf_syntax,
|
validate_bpf_syntax,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -101,7 +101,17 @@ from .compute.iou_nodes import IOU, IOUCreate, IOUStart, IOUUpdate
|
|||||||
from .compute.nat_nodes import NAT, NATCreate, NATUpdate
|
from .compute.nat_nodes import NAT, NATCreate, NATUpdate
|
||||||
|
|
||||||
# Compute schemas
|
# Compute schemas
|
||||||
from .compute.nios import AnchorNIO, TAPNIO, UDPNIO, BridgeNIO, BatchNIOCreate, BatchNIOEntry, EthernetNIO, MarkerRebuild, MarkerToggle
|
from .compute.nios import (
|
||||||
|
TAPNIO,
|
||||||
|
UDPNIO,
|
||||||
|
AnchorNIO,
|
||||||
|
BatchNIOCreate,
|
||||||
|
BatchNIOEntry,
|
||||||
|
BridgeNIO,
|
||||||
|
EthernetNIO,
|
||||||
|
MarkerRebuild,
|
||||||
|
MarkerToggle,
|
||||||
|
)
|
||||||
from .compute.qemu_nodes import Qemu, QemuCreate, QemuUpdate
|
from .compute.qemu_nodes import Qemu, QemuCreate, QemuUpdate
|
||||||
from .compute.virtualbox_nodes import VirtualBox, VirtualBoxCreate, VirtualBoxUpdate
|
from .compute.virtualbox_nodes import VirtualBox, VirtualBoxCreate, VirtualBoxUpdate
|
||||||
from .compute.vmware_nodes import VMware, VMwareCreate, VMwareUpdate
|
from .compute.vmware_nodes import VMware, VMwareCreate, VMwareUpdate
|
||||||
@ -159,12 +169,12 @@ __all__ = [
|
|||||||
"ATMSwitch",
|
"ATMSwitch",
|
||||||
"ATMSwitchCreate",
|
"ATMSwitchCreate",
|
||||||
"ATMSwitchUpdate",
|
"ATMSwitchUpdate",
|
||||||
|
"AnchorNIO",
|
||||||
"ApiKeyCreate",
|
"ApiKeyCreate",
|
||||||
"Appliance",
|
"Appliance",
|
||||||
"ApplianceVersion",
|
"ApplianceVersion",
|
||||||
"ApplianceVersionV8",
|
"ApplianceVersionV8",
|
||||||
"AutoIdlePC",
|
"AutoIdlePC",
|
||||||
"AnchorNIO",
|
|
||||||
"BatchNIOCreate",
|
"BatchNIOCreate",
|
||||||
"BatchNIOEntry",
|
"BatchNIOEntry",
|
||||||
"BridgeNIO",
|
"BridgeNIO",
|
||||||
|
|||||||
@ -16,11 +16,12 @@
|
|||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Any, List, Optional, Union
|
from typing import Any, Optional, Union
|
||||||
|
|
||||||
from pydantic import BaseModel, ConfigDict, Field, SecretStr, model_validator
|
from pydantic import BaseModel, ConfigDict, Field, SecretStr, model_validator
|
||||||
|
|
||||||
from .base import DateTimeModelMixin
|
from .base import DateTimeModelMixin
|
||||||
|
|
||||||
# The capabilities payload has one definition (controller.capabilities): a
|
# The capabilities payload has one definition (controller.capabilities): a
|
||||||
# second, private copy here silently stripped every field it did not know
|
# second, private copy here silently stripped every field it did not know
|
||||||
# about from GET /computes/{id} — which is how ubridge_tap and
|
# about from GET /computes/{id} — which is how ubridge_tap and
|
||||||
|
|||||||
@ -5,7 +5,7 @@ Packet filter parameter validation utilities.
|
|||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
from typing import Any, Dict, List, Optional, Set, Tuple
|
from typing import Any, Dict, List, Set, Tuple
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -47,7 +47,7 @@ _RATE_UNITS = {
|
|||||||
_RATE_MAX_BITS = 100 * 1000**3
|
_RATE_MAX_BITS = 100 * 1000**3
|
||||||
|
|
||||||
|
|
||||||
def validate_bpf_syntax(bpf_expression: str) -> Dict[str, Optional[str]]:
|
def validate_bpf_syntax(bpf_expression: str) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Validate BPF filter expression syntax using tcpdump.
|
Validate BPF filter expression syntax using tcpdump.
|
||||||
|
|
||||||
@ -142,9 +142,7 @@ def _validate_int_parameter(filter_type: str, rules: dict, i: int, value: Any) -
|
|||||||
else:
|
else:
|
||||||
int_value = int(value)
|
int_value = int(value)
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
raise FilterValidationError(
|
raise FilterValidationError(f"{filter_type} parameter {rules['names'][i]} must be an integer, got: {value}")
|
||||||
f"{filter_type} parameter {rules['names'][i]} must be an integer, got: {value}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Range validation
|
# Range validation
|
||||||
min_val, max_val = rules["ranges"][i]
|
min_val, max_val = rules["ranges"][i]
|
||||||
@ -441,7 +439,9 @@ def validate_all_filters(filters: Dict[str, List[Any]]) -> None:
|
|||||||
if "reorder" in filters and "delay" not in filters:
|
if "reorder" in filters and "delay" not in filters:
|
||||||
raise FilterValidationError("reorder requires delay")
|
raise FilterValidationError("reorder requires delay")
|
||||||
if "gemodel" in filters and "packet_loss" in filters:
|
if "gemodel" in filters and "packet_loss" in filters:
|
||||||
raise FilterValidationError("gemodel and packet_loss are mutually exclusive (both map to the netem loss keyword)")
|
raise FilterValidationError(
|
||||||
|
"gemodel and packet_loss are mutually exclusive (both map to the netem loss keyword)"
|
||||||
|
)
|
||||||
window = filters.get("window_drop")
|
window = filters.get("window_drop")
|
||||||
if window and len(window) >= 4 and int(window[3]) < int(window[1]):
|
if window and len(window) >= 4 and int(window[3]) < int(window[1]):
|
||||||
raise FilterValidationError("window_drop period must be greater than or equal to the outage length")
|
raise FilterValidationError("window_drop period must be greater than or equal to the outage length")
|
||||||
|
|||||||
@ -254,6 +254,7 @@ extend-immutable-calls = [
|
|||||||
"tests/controller/test_project.py" = ["RUF015"]
|
"tests/controller/test_project.py" = ["RUF015"]
|
||||||
"tests/controller/test_snapshot.py" = ["RUF015"]
|
"tests/controller/test_snapshot.py" = ["RUF015"]
|
||||||
"tests/controller/test_udp_link.py" = ["B006"]
|
"tests/controller/test_udp_link.py" = ["B006"]
|
||||||
|
"tests/e2e/test_docker_link_local_frames.py" = ["S603"]
|
||||||
"tests/test_server.py" = ["E722"]
|
"tests/test_server.py" = ["E722"]
|
||||||
"tests/test_topologies.py" = ["B011"]
|
"tests/test_topologies.py" = ["B011"]
|
||||||
"tests/test_utils.py" = ["F403", "F405"]
|
"tests/test_utils.py" = ["F403", "F405"]
|
||||||
|
|||||||
@ -246,7 +246,9 @@ class TestDuplicateDeviceMerging:
|
|||||||
nornir = MagicMock()
|
nornir = MagicMock()
|
||||||
nornir.run.return_value = _aggregated_result({"R1": "merged output"})
|
nornir.run.return_value = _aggregated_result({"R1": "merged output"})
|
||||||
with (
|
with (
|
||||||
patch(f"{CONFIG_MOD}.get_device_ports_from_topology", return_value={"R1": {"port": 5000, "node_type": "iou"}}),
|
patch(
|
||||||
|
f"{CONFIG_MOD}.get_device_ports_from_topology", return_value={"R1": {"port": 5000, "node_type": "iou"}}
|
||||||
|
),
|
||||||
patch.object(ExecuteMultipleDeviceConfigCommands, "_initialize_nornir", return_value=nornir),
|
patch.object(ExecuteMultipleDeviceConfigCommands, "_initialize_nornir", return_value=nornir),
|
||||||
):
|
):
|
||||||
result = ExecuteMultipleDeviceConfigCommands()._run(
|
result = ExecuteMultipleDeviceConfigCommands()._run(
|
||||||
@ -310,7 +312,9 @@ class TestDuplicateDeviceMerging:
|
|||||||
nornir = MagicMock()
|
nornir = MagicMock()
|
||||||
nornir.run.return_value = _aggregated_result({"R1": "ok"})
|
nornir.run.return_value = _aggregated_result({"R1": "ok"})
|
||||||
with (
|
with (
|
||||||
patch(f"{DISPLAY_MOD}.get_device_ports_from_topology", return_value={"R1": {"port": 5000, "node_type": "iou"}}),
|
patch(
|
||||||
|
f"{DISPLAY_MOD}.get_device_ports_from_topology", return_value={"R1": {"port": 5000, "node_type": "iou"}}
|
||||||
|
),
|
||||||
patch.object(ExecuteMultipleDeviceCommands, "_initialize_nornir", return_value=nornir),
|
patch.object(ExecuteMultipleDeviceCommands, "_initialize_nornir", return_value=nornir),
|
||||||
patch(f"{DISPLAY_MOD}.filter_forbidden_commands", side_effect=fake_filter),
|
patch(f"{DISPLAY_MOD}.filter_forbidden_commands", side_effect=fake_filter),
|
||||||
):
|
):
|
||||||
|
|||||||
@ -69,8 +69,12 @@ def test_uuid_params_pass_through_without_calls():
|
|||||||
def test_project_name_resolved():
|
def test_project_name_resolved():
|
||||||
"""A name-shaped project_id becomes the project's UUID."""
|
"""A name-shaped project_id becomes the project's UUID."""
|
||||||
|
|
||||||
run, calls = _run_factory(projects=[{"project_id": "11111111-1111-1111-1111-111111111111", "name": "other"},
|
run, calls = _run_factory(
|
||||||
{"project_id": PROJECT_ID, "name": "my-lab"}])
|
projects=[
|
||||||
|
{"project_id": "11111111-1111-1111-1111-111111111111", "name": "other"},
|
||||||
|
{"project_id": PROJECT_ID, "name": "my-lab"},
|
||||||
|
]
|
||||||
|
)
|
||||||
params, error = resolve_locators({"project_id": "my-lab"}, run)
|
params, error = resolve_locators({"project_id": "my-lab"}, run)
|
||||||
assert error is None
|
assert error is None
|
||||||
assert params["project_id"] == PROJECT_ID
|
assert params["project_id"] == PROJECT_ID
|
||||||
@ -88,8 +92,9 @@ def test_project_name_not_found_lists_known_names():
|
|||||||
|
|
||||||
def test_node_name_resolved_within_project():
|
def test_node_name_resolved_within_project():
|
||||||
|
|
||||||
run, calls = _run_factory(projects=[{"project_id": PROJECT_ID, "name": "my-lab"}],
|
run, calls = _run_factory(
|
||||||
nodes=[{"node_id": NODE_ID, "name": "FRRDocker-4"}])
|
projects=[{"project_id": PROJECT_ID, "name": "my-lab"}], nodes=[{"node_id": NODE_ID, "name": "FRRDocker-4"}]
|
||||||
|
)
|
||||||
params, error = resolve_locators({"project_id": "my-lab", "node_id": "FRRDocker-4"}, run)
|
params, error = resolve_locators({"project_id": "my-lab", "node_id": "FRRDocker-4"}, run)
|
||||||
assert error is None
|
assert error is None
|
||||||
assert params["project_id"] == PROJECT_ID
|
assert params["project_id"] == PROJECT_ID
|
||||||
@ -125,11 +130,10 @@ def test_handler_failure_during_resolution_is_reported():
|
|||||||
def test_did_you_mean_appended_for_transcription_slip():
|
def test_did_you_mean_appended_for_transcription_slip():
|
||||||
"""The 2c9e -> 3b9e class of typo earns exactly one hint."""
|
"""The 2c9e -> 3b9e class of typo earns exactly one hint."""
|
||||||
|
|
||||||
run, _calls = _run_factory(projects=[{"project_id": PROJECT_ID, "name": "my-lab"}],
|
run, _calls = _run_factory(
|
||||||
nodes=[{"node_id": NODE_ID, "name": "FRRDocker-4"}])
|
projects=[{"project_id": PROJECT_ID, "name": "my-lab"}], nodes=[{"node_id": NODE_ID, "name": "FRRDocker-4"}]
|
||||||
result = add_did_you_mean(
|
|
||||||
{"error": f"Node ID {NODE_ID_TYPO} doesn't exist"}, {"project_id": PROJECT_ID}, run
|
|
||||||
)
|
)
|
||||||
|
result = add_did_you_mean({"error": f"Node ID {NODE_ID_TYPO} doesn't exist"}, {"project_id": PROJECT_ID}, run)
|
||||||
assert f"Did you mean node 'FRRDocker-4' ({NODE_ID})" in result["error"]
|
assert f"Did you mean node 'FRRDocker-4' ({NODE_ID})" in result["error"]
|
||||||
# original message preserved
|
# original message preserved
|
||||||
assert NODE_ID_TYPO in result["error"]
|
assert NODE_ID_TYPO in result["error"]
|
||||||
@ -144,9 +148,7 @@ def test_no_hint_when_too_far_or_ambiguous():
|
|||||||
|
|
||||||
two = [NODE_ID, "b78a2630-2b9e-4b43-9397-c855c8053d1a"] # both within distance 2 of the typo
|
two = [NODE_ID, "b78a2630-2b9e-4b43-9397-c855c8053d1a"] # both within distance 2 of the typo
|
||||||
run2, _calls2 = _run_factory(nodes=[{"node_id": i, "name": f"n{i}"} for i in two])
|
run2, _calls2 = _run_factory(nodes=[{"node_id": i, "name": f"n{i}"} for i in two])
|
||||||
result2 = add_did_you_mean(
|
result2 = add_did_you_mean({"error": f"Node ID {NODE_ID_TYPO} doesn't exist"}, {"project_id": PROJECT_ID}, run2)
|
||||||
{"error": f"Node ID {NODE_ID_TYPO} doesn't exist"}, {"project_id": PROJECT_ID}, run2
|
|
||||||
)
|
|
||||||
assert "Did you mean" not in result2["error"]
|
assert "Did you mean" not in result2["error"]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -170,7 +170,9 @@ class TestCapabilitiesRoutes:
|
|||||||
async def fake_bridge_tap_probe():
|
async def fake_bridge_tap_probe():
|
||||||
return True
|
return True
|
||||||
|
|
||||||
monkeypatch.setattr("gns3server.api.routes.compute.capabilities.probe_bridge_tap_support", fake_bridge_tap_probe)
|
monkeypatch.setattr(
|
||||||
|
"gns3server.api.routes.compute.capabilities.probe_bridge_tap_support", fake_bridge_tap_probe
|
||||||
|
)
|
||||||
monkeypatch.setattr("gns3server.api.routes.compute.capabilities.probe_tap_support", _no_tap_probe)
|
monkeypatch.setattr("gns3server.api.routes.compute.capabilities.probe_tap_support", _no_tap_probe)
|
||||||
monkeypatch.setattr("gns3server.api.routes.compute.capabilities.probe_iol_tap_support", _no_tap_probe)
|
monkeypatch.setattr("gns3server.api.routes.compute.capabilities.probe_iol_tap_support", _no_tap_probe)
|
||||||
response = await compute_client.get(app.url_path_for("compute:get_capabilities"))
|
response = await compute_client.get(app.url_path_for("compute:get_capabilities"))
|
||||||
|
|||||||
@ -23,23 +23,18 @@ fallback and cleanup.
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import uuid
|
import uuid
|
||||||
from unittest.mock import MagicMock, call
|
from unittest.mock import MagicMock, call, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import pytest_asyncio
|
import pytest_asyncio
|
||||||
|
|
||||||
from unittest.mock import patch
|
|
||||||
|
|
||||||
from tests.utils import AsyncioMagicMock
|
|
||||||
|
|
||||||
from gns3server.compute.docker import Docker
|
|
||||||
from gns3server.compute.docker.docker_vm import DockerVM
|
|
||||||
from gns3server.compute.docker.docker_error import DockerError, DockerHttp404Error
|
|
||||||
from gns3server.compute.compute_error import ComputeError
|
from gns3server.compute.compute_error import ComputeError
|
||||||
from gns3server.compute.nios.nio_udp import NIOUDP
|
from gns3server.compute.docker import Docker
|
||||||
|
from gns3server.compute.docker.docker_error import DockerError, DockerHttp404Error
|
||||||
|
from gns3server.compute.docker.docker_vm import DockerVM
|
||||||
from gns3server.compute.nios.nio_bridge import NIOBridge
|
from gns3server.compute.nios.nio_bridge import NIOBridge
|
||||||
from gns3server.compute.ubridge.ubridge_error import UbridgeError
|
from gns3server.compute.ubridge.ubridge_error import UbridgeError
|
||||||
|
from tests.utils import AsyncioMagicMock
|
||||||
|
|
||||||
BRIDGE = "gns3a1b2c3d4e5f"
|
BRIDGE = "gns3a1b2c3d4e5f"
|
||||||
|
|
||||||
@ -1373,7 +1368,13 @@ def test_create_nio_udp_rejects_kernel_only_filters(vm):
|
|||||||
|
|
||||||
with pytest.raises(ComputeError, match="kernel-datapath"):
|
with pytest.raises(ComputeError, match="kernel-datapath"):
|
||||||
vm.manager.create_nio(
|
vm.manager.create_nio(
|
||||||
{"type": "nio_udp", "lport": 4242, "rport": 4343, "rhost": "127.0.0.1", "filters": {"gemodel": [100, 0, 30]}}
|
{
|
||||||
|
"type": "nio_udp",
|
||||||
|
"lport": 4242,
|
||||||
|
"rport": 4343,
|
||||||
|
"rhost": "127.0.0.1",
|
||||||
|
"filters": {"gemodel": [100, 0, 30]},
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -14,19 +14,15 @@ Image-free: everything is asserted against the uBridge command stream and
|
|||||||
the anchor naming contract.
|
the anchor naming contract.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import uuid
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import pytest_asyncio
|
import pytest_asyncio
|
||||||
|
|
||||||
from unittest.mock import MagicMock, patch
|
|
||||||
|
|
||||||
from tests.utils import AsyncioMagicMock
|
|
||||||
|
|
||||||
from gns3server.compute.docker import Docker
|
from gns3server.compute.docker import Docker
|
||||||
from gns3server.compute.docker.docker_error import DockerError
|
from gns3server.compute.docker.docker_error import DockerError
|
||||||
from gns3server.compute.docker.iol_docker_vm import IOLDockerVM
|
from gns3server.compute.docker.iol_docker_vm import IOLDockerVM
|
||||||
|
from tests.utils import AsyncioMagicMock
|
||||||
|
|
||||||
NODE_ID = "00010203-0405-0607-0809-0a0b0c0d0e0f"
|
NODE_ID = "00010203-0405-0607-0809-0a0b0c0d0e0f"
|
||||||
TAP00 = "gx00010203e0p0" # the vm fixture's node id, bay 0 unit 0
|
TAP00 = "gx00010203e0p0" # the vm fixture's node id, bay 0 unit 0
|
||||||
@ -296,7 +292,9 @@ async def test_remove_nio_binding_kernel_releases_the_port_leg_first(vm):
|
|||||||
commands = _commands(vm)
|
commands = _commands(vm)
|
||||||
assert f"bridge stop {PORT_BRIDGE}" in commands
|
assert f"bridge stop {PORT_BRIDGE}" in commands
|
||||||
assert f'bridge delete_nio_tap {PORT_BRIDGE} "{TAP00}"' in commands
|
assert f'bridge delete_nio_tap {PORT_BRIDGE} "{TAP00}"' in commands
|
||||||
assert commands.index(f"bridge stop {PORT_BRIDGE}") < commands.index(f'bridge delete_nio_tap {PORT_BRIDGE} "{TAP00}"')
|
assert commands.index(f"bridge stop {PORT_BRIDGE}") < commands.index(
|
||||||
|
f'bridge delete_nio_tap {PORT_BRIDGE} "{TAP00}"'
|
||||||
|
)
|
||||||
assert f'tc reset "{TAP00}"' in commands
|
assert f'tc reset "{TAP00}"' in commands
|
||||||
assert f'brctl delif "{BRIDGE}" "{TAP00}"' in commands
|
assert f'brctl delif "{BRIDGE}" "{TAP00}"' in commands
|
||||||
assert f'brctl delete "{BRIDGE}"' in commands
|
assert f'brctl delete "{BRIDGE}"' in commands
|
||||||
|
|||||||
@ -908,6 +908,7 @@ async def test_create_reparse_refreshes_env_knobs(compute_project, manager):
|
|||||||
# Kernel-datapath (NIOBridge) rejection
|
# Kernel-datapath (NIOBridge) rejection
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_unix_socket_nio_rejects_kernel_datapath_link(compute_project, manager):
|
async def test_unix_socket_nio_rejects_kernel_datapath_link(compute_project, manager):
|
||||||
|
|
||||||
|
|||||||
@ -25,27 +25,24 @@ hypervisor's fds released before the TAPs are unpersisted — QEMU's order,
|
|||||||
since here it is Dynamips that holds them).
|
since here it is Dynamips that holds them).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import contextlib
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import contextlib
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import pytest_asyncio
|
import pytest_asyncio
|
||||||
|
|
||||||
from unittest.mock import MagicMock, patch
|
|
||||||
|
|
||||||
from tests.utils import AsyncioMagicMock
|
|
||||||
|
|
||||||
from gns3server.compute.dynamips import Dynamips
|
from gns3server.compute.dynamips import Dynamips
|
||||||
from gns3server.compute.dynamips.nodes.router import Router
|
|
||||||
from gns3server.compute.dynamips.dynamips_error import DynamipsError
|
|
||||||
from gns3server.compute.dynamips.adapters.gt96100_fe import GT96100_FE
|
from gns3server.compute.dynamips.adapters.gt96100_fe import GT96100_FE
|
||||||
from gns3server.compute.dynamips.adapters.pa_4t import PA_4T
|
from gns3server.compute.dynamips.adapters.pa_4t import PA_4T
|
||||||
from gns3server.compute.dynamips.adapters.pa_fe_tx import PA_FE_TX
|
from gns3server.compute.dynamips.adapters.pa_fe_tx import PA_FE_TX
|
||||||
from gns3server.compute.dynamips.adapters.wic_1enet import WIC_1ENET
|
from gns3server.compute.dynamips.adapters.wic_1enet import WIC_1ENET
|
||||||
from gns3server.compute.ubridge.ubridge_error import UbridgeError
|
from gns3server.compute.dynamips.dynamips_error import DynamipsError
|
||||||
|
from gns3server.compute.dynamips.nodes.router import Router
|
||||||
from gns3server.compute.nios.nio_bridge import NIOBridge
|
from gns3server.compute.nios.nio_bridge import NIOBridge
|
||||||
|
from gns3server.compute.ubridge.ubridge_error import UbridgeError
|
||||||
|
from tests.utils import AsyncioMagicMock
|
||||||
|
|
||||||
NODE_ID = "00010203-0405-0607-0809-0a0b0c0d0e0f"
|
NODE_ID = "00010203-0405-0607-0809-0a0b0c0d0e0f"
|
||||||
BRIDGE = "gns3a1b2c3d4e5f"
|
BRIDGE = "gns3a1b2c3d4e5f"
|
||||||
@ -237,7 +234,7 @@ async def test_prepare_tap_datapath_wires_nios_bound_while_stopped(router):
|
|||||||
|
|
||||||
hypervisor = _hypervisor_commands(router)
|
hypervisor = _hypervisor_commands(router)
|
||||||
assert any(c.startswith("nio create_tap tap-") and c.endswith(f"{TAP00}") for c in hypervisor)
|
assert any(c.startswith("nio create_tap tap-") and c.endswith(f"{TAP00}") for c in hypervisor)
|
||||||
assert any(c.startswith(f"vm slot_add_nio_binding \"R1\" 0 0 tap-") for c in hypervisor)
|
assert any(c.startswith('vm slot_add_nio_binding "R1" 0 0 tap-') for c in hypervisor)
|
||||||
router._ubridge_send.assert_any_call(f'brctl addif "{BRIDGE}" "{TAP00}"')
|
router._ubridge_send.assert_any_call(f'brctl addif "{BRIDGE}" "{TAP00}"')
|
||||||
assert (0, 0) in router._tap_nios
|
assert (0, 0) in router._tap_nios
|
||||||
|
|
||||||
@ -489,9 +486,7 @@ async def test_stop_ubridge_releases_hypervisor_fds_before_deleting_taps(router)
|
|||||||
router._ubridge_send.reset_mock()
|
router._ubridge_send.reset_mock()
|
||||||
router._hypervisor.commands.clear()
|
router._hypervisor.commands.clear()
|
||||||
|
|
||||||
with patch(
|
with patch("gns3server.compute.base_node.BaseNode._stop_ubridge", new=AsyncioMagicMock()) as stop:
|
||||||
"gns3server.compute.base_node.BaseNode._stop_ubridge", new=AsyncioMagicMock()
|
|
||||||
) as stop:
|
|
||||||
await router._stop_ubridge()
|
await router._stop_ubridge()
|
||||||
|
|
||||||
hypervisor = _hypervisor_commands(router)
|
hypervisor = _hypervisor_commands(router)
|
||||||
|
|||||||
@ -22,19 +22,16 @@ it, kernel-link enslavement, the relay fallback for a uBridge without the
|
|||||||
command, and the reverse-of-QEMU stop order (uBridge holds the anchor fds).
|
command, and the reverse-of-QEMU stop order (uBridge holds the anchor fds).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import pytest_asyncio
|
import pytest_asyncio
|
||||||
|
|
||||||
from unittest.mock import MagicMock, patch
|
|
||||||
|
|
||||||
from tests.utils import AsyncioMagicMock
|
|
||||||
|
|
||||||
from gns3server.compute.iou import IOU
|
from gns3server.compute.iou import IOU
|
||||||
from gns3server.compute.iou.iou_vm import IOUVM
|
|
||||||
from gns3server.compute.iou.iou_error import IOUError
|
from gns3server.compute.iou.iou_error import IOUError
|
||||||
from gns3server.compute.nios.nio_bridge import NIOBridge
|
from gns3server.compute.iou.iou_vm import IOUVM
|
||||||
from gns3server.compute.ubridge.ubridge_error import UbridgeError
|
from gns3server.compute.ubridge.ubridge_error import UbridgeError
|
||||||
|
from tests.utils import AsyncioMagicMock
|
||||||
|
|
||||||
NODE_ID = "00010203-0405-0607-0809-0a0b0c0d0e0f"
|
NODE_ID = "00010203-0405-0607-0809-0a0b0c0d0e0f"
|
||||||
BRIDGE = "gns3a1b2c3d4e5f"
|
BRIDGE = "gns3a1b2c3d4e5f"
|
||||||
|
|||||||
@ -24,21 +24,16 @@ legacy socket-netdev fallback for a uBridge without the tap module.
|
|||||||
import asyncio
|
import asyncio
|
||||||
import os
|
import os
|
||||||
import stat
|
import stat
|
||||||
import sys
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import pytest_asyncio
|
import pytest_asyncio
|
||||||
|
|
||||||
from unittest.mock import MagicMock, patch
|
|
||||||
|
|
||||||
from tests.utils import asyncio_patch, AsyncioMagicMock
|
|
||||||
|
|
||||||
from gns3server.compute.qemu import Qemu
|
from gns3server.compute.qemu import Qemu
|
||||||
from gns3server.compute.qemu.qemu_vm import QemuVM
|
|
||||||
from gns3server.compute.qemu.qemu_error import QemuError
|
from gns3server.compute.qemu.qemu_error import QemuError
|
||||||
from gns3server.compute.nios.nio_bridge import NIOBridge
|
from gns3server.compute.qemu.qemu_vm import QemuVM
|
||||||
from gns3server.compute.ubridge.ubridge_error import UbridgeError
|
from gns3server.compute.ubridge.ubridge_error import UbridgeError
|
||||||
|
from tests.utils import AsyncioMagicMock, asyncio_patch
|
||||||
|
|
||||||
BRIDGE = "gns3a1b2c3d4e5f"
|
BRIDGE = "gns3a1b2c3d4e5f"
|
||||||
TAP0 = "gq00010203e0p0" # the vm fixture's node id, adapter 0
|
TAP0 = "gq00010203e0p0" # the vm fixture's node id, adapter 0
|
||||||
@ -272,7 +267,7 @@ async def test_update_nio_binding_reapplies_filters_on_the_anchor(vm):
|
|||||||
|
|
||||||
vm._ubridge_send.assert_any_call(f'tc reset "{TAP0}"')
|
vm._ubridge_send.assert_any_call(f'tc reset "{TAP0}"')
|
||||||
vm._ubridge_send.assert_any_call(f'tc netem set "{TAP0}" delay 10')
|
vm._ubridge_send.assert_any_call(f'tc netem set "{TAP0}" delay 10')
|
||||||
assert not any(f"brctl addif" in str(c) for c in vm._ubridge_send.call_args_list)
|
assert not any("brctl addif" in str(c) for c in vm._ubridge_send.call_args_list)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
|||||||
@ -25,8 +25,8 @@ from gns3server.compute.ubridge import tc_probe
|
|||||||
from gns3server.compute.ubridge.tc_probe import (
|
from gns3server.compute.ubridge.tc_probe import (
|
||||||
probe_bridge_tap_support,
|
probe_bridge_tap_support,
|
||||||
probe_iol_tap_support,
|
probe_iol_tap_support,
|
||||||
probe_tc_capabilities,
|
|
||||||
probe_tap_support,
|
probe_tap_support,
|
||||||
|
probe_tc_capabilities,
|
||||||
)
|
)
|
||||||
from gns3server.compute.ubridge.ubridge_error import UbridgeError
|
from gns3server.compute.ubridge.ubridge_error import UbridgeError
|
||||||
|
|
||||||
@ -65,7 +65,7 @@ class FakeHypervisor:
|
|||||||
if self.error:
|
if self.error:
|
||||||
raise UbridgeError(self.error)
|
raise UbridgeError(self.error)
|
||||||
if self.error_prefix and command.startswith(self.error_prefix):
|
if self.error_prefix and command.startswith(self.error_prefix):
|
||||||
raise UbridgeError(f"202-Unknown command")
|
raise UbridgeError("202-Unknown command")
|
||||||
if command.startswith("bridge delete_nio_tap "):
|
if command.startswith("bridge delete_nio_tap "):
|
||||||
if FakeHypervisor.bridge_tap_error:
|
if FakeHypervisor.bridge_tap_error:
|
||||||
raise UbridgeError(FakeHypervisor.bridge_tap_error)
|
raise UbridgeError(FakeHypervisor.bridge_tap_error)
|
||||||
|
|||||||
@ -381,7 +381,18 @@ async def test_json(compute):
|
|||||||
"disk_usage_percent": 0,
|
"disk_usage_percent": 0,
|
||||||
"connected": True,
|
"connected": True,
|
||||||
"last_error": None,
|
"last_error": None,
|
||||||
"capabilities": {"version": "", "platform": "", "cpus": 0, "memory": 0, "disk_size": 0, "node_types": [], "ubridge_tc": None, "ubridge_tap": None, "ubridge_iol_tap": None, "ubridge_bridge_tap": None},
|
"capabilities": {
|
||||||
|
"version": "",
|
||||||
|
"platform": "",
|
||||||
|
"cpus": 0,
|
||||||
|
"memory": 0,
|
||||||
|
"disk_size": 0,
|
||||||
|
"node_types": [],
|
||||||
|
"ubridge_tc": None,
|
||||||
|
"ubridge_tap": None,
|
||||||
|
"ubridge_iol_tap": None,
|
||||||
|
"ubridge_bridge_tap": None,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
assert compute.asdict(topology_dump=True) == {
|
assert compute.asdict(topology_dump=True) == {
|
||||||
"compute_id": "my_compute_id",
|
"compute_id": "my_compute_id",
|
||||||
|
|||||||
@ -20,14 +20,15 @@ Controller-side kernel-datapath link selection: eligibility of Docker-to-
|
|||||||
Docker links, NIO emission and the filters/markers guards.
|
Docker links, NIO emission and the filters/markers guards.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import pytest
|
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
from gns3server.config import Config
|
from gns3server.config import Config
|
||||||
from gns3server.controller.udp_link import UDPLink
|
|
||||||
from gns3server.controller.ports.ethernet_port import EthernetPort
|
|
||||||
from gns3server.controller.node import Node
|
|
||||||
from gns3server.controller.controller_error import ControllerError
|
from gns3server.controller.controller_error import ControllerError
|
||||||
|
from gns3server.controller.node import Node
|
||||||
|
from gns3server.controller.ports.ethernet_port import EthernetPort
|
||||||
|
from gns3server.controller.udp_link import UDPLink
|
||||||
|
|
||||||
|
|
||||||
def _node(project, compute, name, node_type="docker", status="stopped", environment=None):
|
def _node(project, compute, name, node_type="docker", status="stopped", environment=None):
|
||||||
|
|||||||
@ -903,7 +903,9 @@ class DockerDaemon:
|
|||||||
)
|
)
|
||||||
response = conn.getresponse()
|
response = conn.getresponse()
|
||||||
payload = response.read()
|
payload = response.read()
|
||||||
assert response.status == 200, f"docker cp into {container_id[:12]} -> HTTP {response.status}: {payload[:300]!r}"
|
assert response.status == 200, (
|
||||||
|
f"docker cp into {container_id[:12]} -> HTTP {response.status}: {payload[:300]!r}"
|
||||||
|
)
|
||||||
except (OSError, http.client.HTTPException) as e:
|
except (OSError, http.client.HTTPException) as e:
|
||||||
raise AssertionError(f"docker cp into {container_id[:12]} failed: {e}") from None
|
raise AssertionError(f"docker cp into {container_id[:12]} failed: {e}") from None
|
||||||
finally:
|
finally:
|
||||||
|
|||||||
@ -47,6 +47,7 @@ The negative control for the old behaviour is the uBridge suite's
|
|||||||
import functools
|
import functools
|
||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import tarfile
|
import tarfile
|
||||||
import tempfile
|
import tempfile
|
||||||
@ -246,9 +247,12 @@ def _llprobe():
|
|||||||
binary = os.path.join(directory, "llprobe")
|
binary = os.path.join(directory, "llprobe")
|
||||||
with open(source, "w") as f:
|
with open(source, "w") as f:
|
||||||
f.write(_LLPROBE_C)
|
f.write(_LLPROBE_C)
|
||||||
|
cc = shutil.which("cc")
|
||||||
|
if cc is None:
|
||||||
|
pytest.skip("cannot build the raw-frame probe (no C compiler found)")
|
||||||
try:
|
try:
|
||||||
subprocess.run(["cc", "-static", "-O2", "-o", binary, source], check=True, capture_output=True)
|
subprocess.run([cc, "-static", "-O2", "-o", binary, source], check=True, capture_output=True)
|
||||||
except (FileNotFoundError, subprocess.CalledProcessError) as e:
|
except subprocess.CalledProcessError as e:
|
||||||
pytest.skip(f"cannot build the raw-frame probe (cc -static): {e}")
|
pytest.skip(f"cannot build the raw-frame probe (cc -static): {e}")
|
||||||
return binary
|
return binary
|
||||||
|
|
||||||
@ -270,9 +274,7 @@ def _upload_probe(daemon, *container_ids):
|
|||||||
|
|
||||||
def _recv_counts(daemon, container_id, window, macs):
|
def _recv_counts(daemon, container_id, window, macs):
|
||||||
"""Run the receiver to completion and parse its "<mac>=<count>" lines."""
|
"""Run the receiver to completion and parse its "<mac>=<count>" lines."""
|
||||||
code, output = daemon.exec(
|
code, output = daemon.exec(container_id, ["/tmp/llprobe", "recv", "eth0", str(window), *macs], timeout=window + 30)
|
||||||
container_id, ["/tmp/llprobe", "recv", "eth0", str(window), *macs], timeout=window + 30
|
|
||||||
)
|
|
||||||
assert code == 0, output
|
assert code == 0, output
|
||||||
return {line.split("=")[0]: int(line.split("=")[1]) for line in output.strip().splitlines() if "=" in line}
|
return {line.split("=")[0]: int(line.split("=")[1]) for line in output.strip().splitlines() if "=" in line}
|
||||||
|
|
||||||
@ -316,7 +318,7 @@ def test_docker_link_local_frames():
|
|||||||
print(".. starting containers")
|
print(".. starting containers")
|
||||||
for node in nodes:
|
for node in nodes:
|
||||||
compute.call("POST", f"/projects/{pid}/nodes/{node['node_id']}/start")
|
compute.call("POST", f"/projects/{pid}/nodes/{node['node_id']}/start")
|
||||||
for node, address in zip(nodes, (R1_IP, R2_IP)):
|
for node, address in zip(nodes, (R1_IP, R2_IP), strict=True):
|
||||||
harness.docker_wait_address(daemon, node["container_id"], address)
|
harness.docker_wait_address(daemon, node["container_id"], address)
|
||||||
|
|
||||||
a1 = harness.docker_anchor_name(n1["node_id"], *ETH)
|
a1 = harness.docker_anchor_name(n1["node_id"], *ETH)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user