mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-03 05:51:15 +02: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:
35 files changed
+165
-136
No files matched your search
@@ -47,6 +47,7 @@ from nornir.core.task import AggregatedResult, Result, Task
|
||||
from nornir_netmiko.tasks import netmiko_send_config
|
||||
|
||||
from gns3server.agent.gns3_copilot.gns3_client import get_gns3_server_host
|
||||
|
||||
# Import custom Netmiko device types for GNS3 emulation
|
||||
# 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
|
||||
|
||||
@@ -47,6 +47,7 @@ from nornir.core.task import AggregatedResult, Result, Task
|
||||
from nornir_netmiko.tasks import netmiko_multiline
|
||||
|
||||
from gns3server.agent.gns3_copilot.gns3_client import get_gns3_server_host
|
||||
|
||||
# Import custom Netmiko device types for GNS3 emulation
|
||||
# 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
|
||||
|
||||
@@ -47,6 +47,7 @@ from nornir.core.task import AggregatedResult, Result, Task
|
||||
from nornir_netmiko.tasks import netmiko_multiline
|
||||
|
||||
from gns3server.agent.gns3_copilot.gns3_client import get_gns3_server_host
|
||||
|
||||
# Import custom Netmiko device types for GNS3 emulation
|
||||
# This registers gns3_vpcs_telnet and other custom device types
|
||||
# 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
|
||||
# runs; a name that matches nothing fails fast with the known names.
|
||||
params, resolution_error = resolve_locators(params, run)
|
||||
if resolution_error is not None:
|
||||
resolved_params, resolution_error = resolve_locators(params, run)
|
||||
if resolved_params is None:
|
||||
result = resolution_error
|
||||
else:
|
||||
result = handler(params, ctx)
|
||||
result = handler(resolved_params, ctx)
|
||||
# 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)}]
|
||||
|
||||
|
||||
|
||||
@@ -37,9 +37,10 @@ distance 2 earns a "Did you mean ..." hint.
|
||||
import re
|
||||
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 .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}$")
|
||||
_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
|
||||
|
||||
|
||||
def add_did_you_mean(
|
||||
result: Any, params: dict[str, Any], run: Callable[[Callable, dict], Any]
|
||||
) -> Any:
|
||||
def add_did_you_mean(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
|
||||
non-existent UUID, when exactly one in-scope id is within edit
|
||||
@@ -118,7 +117,11 @@ def add_did_you_mean(
|
||||
|
||||
hints = []
|
||||
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:
|
||||
cid, label = close[0]
|
||||
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.
|
||||
"""
|
||||
|
||||
candidates = []
|
||||
candidates: list[tuple[str, str]] = []
|
||||
try:
|
||||
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"))
|
||||
|
||||
@@ -28,8 +28,8 @@ from gns3server.compute import MODULES
|
||||
from gns3server.compute.ubridge.tc_probe import (
|
||||
probe_bridge_tap_support,
|
||||
probe_iol_tap_support,
|
||||
probe_tc_capabilities,
|
||||
probe_tap_support,
|
||||
probe_tc_capabilities,
|
||||
)
|
||||
from gns3server.utils.path import get_default_project_directory
|
||||
from gns3server.utils.tc_capabilities import usable_ebpf_modes
|
||||
|
||||
@@ -264,8 +264,10 @@ async def duplicate_docker_node(
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def create_docker_node_nio(
|
||||
adapter_number: int, port_number: int, nio_data: Union[schemas.UDPNIO, schemas.BridgeNIO],
|
||||
node: DockerVM = Depends(dep_node)
|
||||
adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: Union[schemas.UDPNIO, schemas.BridgeNIO],
|
||||
node: DockerVM = Depends(dep_node),
|
||||
) -> Union[schemas.UDPNIO, schemas.BridgeNIO]:
|
||||
"""
|
||||
Add a NIO (Network Input/Output) to the node.
|
||||
@@ -284,8 +286,10 @@ async def create_docker_node_nio(
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def update_docker_node_nio(
|
||||
adapter_number: int, port_number: int, nio_data: Union[schemas.UDPNIO, schemas.BridgeNIO],
|
||||
node: DockerVM = Depends(dep_node)
|
||||
adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: Union[schemas.UDPNIO, schemas.BridgeNIO],
|
||||
node: DockerVM = Depends(dep_node),
|
||||
) -> Union[schemas.UDPNIO, schemas.BridgeNIO]:
|
||||
"""
|
||||
Update a NIO (Network Input/Output) on the node.
|
||||
|
||||
@@ -103,20 +103,20 @@ async def update_ethernet_switch(
|
||||
Update an Ethernet switch.
|
||||
"""
|
||||
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
if "name" in node_data and node.name != node_data["name"]:
|
||||
node.name = node_data["name"]
|
||||
if "usage" in node_data:
|
||||
node.usage = node_data["usage"]
|
||||
if "ports_mapping" in node_data:
|
||||
data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
if "name" in data and node.name != data["name"]:
|
||||
node.name = data["name"]
|
||||
if "usage" in data:
|
||||
node.usage = data["usage"]
|
||||
if "ports_mapping" in data:
|
||||
# capture the mapping before the setter replaces it: the VLAN
|
||||
# reconcile diffs old against new to touch only what changed, in
|
||||
# place (the setter itself keeps the port-count guard)
|
||||
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)
|
||||
if "console_type" in node_data:
|
||||
node.console_type = node_data["console_type"]
|
||||
if "console_type" in data:
|
||||
node.console_type = data["console_type"]
|
||||
node.updated()
|
||||
return node.asdict()
|
||||
|
||||
|
||||
@@ -24,14 +24,13 @@ markers, attach/detach, adapter carrier — lives in
|
||||
"""
|
||||
|
||||
import contextlib
|
||||
import logging
|
||||
|
||||
from gns3server.compute.kernel_datapath import KernelDatapathMixin
|
||||
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
|
||||
|
||||
import logging
|
||||
from .docker_error import DockerError
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -33,14 +33,13 @@ import aiohttp
|
||||
import psutil
|
||||
|
||||
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.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.ssh_server import AsyncioSSHServer
|
||||
from gns3server.utils.asyncio.telnet_server import AsyncioTelnetServer
|
||||
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 ..base_node import BaseNode
|
||||
|
||||
@@ -672,4 +672,3 @@ class IOLDockerVM(VendorDockerVM):
|
||||
|
||||
if self._kernel_host_ifc(adapter_number, port_number) is not None:
|
||||
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 ...kernel_datapath import KernelDatapathMixin
|
||||
from ...nios.nio_bridge import NIOBridge
|
||||
from ...nios.nio_tap import NIOTAP
|
||||
from ...ubridge.ubridge_error import UbridgeError
|
||||
from ..adapters.adapter import ETHERNET_ADAPTERS, ETHERNET_WICS
|
||||
from ..dynamips_error import DynamipsError
|
||||
from ..nios.nio_tap import NIOTAP
|
||||
|
||||
|
||||
class Router(KernelDatapathMixin, BaseNode):
|
||||
|
||||
@@ -22,6 +22,7 @@ order to run an IOU VM.
|
||||
import asyncio
|
||||
import binascii
|
||||
import configparser
|
||||
import contextlib
|
||||
import functools
|
||||
import glob
|
||||
import hashlib
|
||||
@@ -951,9 +952,7 @@ class IOUVM(KernelDatapathMixin, BaseNode):
|
||||
raise self._kernel_error(
|
||||
"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 "
|
||||
"iol_bridge add_nio_tap runs relay-only)".format(
|
||||
bay=adapter_number, unit=port_number, name=self._name
|
||||
)
|
||||
"iol_bridge add_nio_tap runs relay-only)".format(bay=adapter_number, unit=port_number, name=self._name)
|
||||
)
|
||||
await self._ubridge_send(
|
||||
'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``.
|
||||
"""
|
||||
|
||||
import os
|
||||
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.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
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ order to run a QEMU VM.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import contextlib
|
||||
import json
|
||||
import logging
|
||||
import math
|
||||
@@ -53,6 +54,7 @@ from ..kernel_datapath import KernelDatapathMixin
|
||||
from ..nios.nio_bridge import NIOBridge
|
||||
from ..nios.nio_tap import NIOTAP
|
||||
from ..nios.nio_udp import NIOUDP
|
||||
from ..ubridge.ubridge_error import UbridgeError
|
||||
from .qemu_error import QemuError
|
||||
from .utils.qcow2 import Qcow2, Qcow2Error
|
||||
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.
|
||||
"""
|
||||
|
||||
import os
|
||||
import uuid
|
||||
import shutil
|
||||
import asyncio
|
||||
import tempfile
|
||||
import logging
|
||||
import contextlib
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
import uuid
|
||||
from typing import Optional
|
||||
|
||||
from gns3server.config import Config
|
||||
from gns3server.utils.tc_capabilities import parse_tc_capabilities
|
||||
|
||||
from .hypervisor import Hypervisor
|
||||
from .ubridge_error import UbridgeError
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
# 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)
|
||||
_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)
|
||||
_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)
|
||||
_bridge_tap_cache = {}
|
||||
_bridge_tap_cache: dict[tuple[str, int, int], Optional[bool]] = {}
|
||||
_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}")
|
||||
try:
|
||||
await hypervisor.send(f"tap create {tap}")
|
||||
await hypervisor.send(
|
||||
f"iol_bridge add_nio_tap {bridge} {_IOL_PROBE_BRIDGE_ID - 1} 0 0 {tap}"
|
||||
)
|
||||
await hypervisor.send(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")
|
||||
finally:
|
||||
# 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.kernel_anchor import kernel_anchor_name, kernel_anchor_type, kernel_cascade_names
|
||||
from gns3server.utils.packet_filter_validation import (
|
||||
KERNEL_UNSUPPORTED_FILTERS,
|
||||
split_kernel_only_features,
|
||||
validate_bpf_syntax,
|
||||
)
|
||||
|
||||
@@ -101,7 +101,17 @@ from .compute.iou_nodes import IOU, IOUCreate, IOUStart, IOUUpdate
|
||||
from .compute.nat_nodes import NAT, NATCreate, NATUpdate
|
||||
|
||||
# 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.virtualbox_nodes import VirtualBox, VirtualBoxCreate, VirtualBoxUpdate
|
||||
from .compute.vmware_nodes import VMware, VMwareCreate, VMwareUpdate
|
||||
@@ -159,12 +169,12 @@ __all__ = [
|
||||
"ATMSwitch",
|
||||
"ATMSwitchCreate",
|
||||
"ATMSwitchUpdate",
|
||||
"AnchorNIO",
|
||||
"ApiKeyCreate",
|
||||
"Appliance",
|
||||
"ApplianceVersion",
|
||||
"ApplianceVersionV8",
|
||||
"AutoIdlePC",
|
||||
"AnchorNIO",
|
||||
"BatchNIOCreate",
|
||||
"BatchNIOEntry",
|
||||
"BridgeNIO",
|
||||
|
||||
@@ -16,11 +16,12 @@
|
||||
|
||||
import uuid
|
||||
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 .base import DateTimeModelMixin
|
||||
|
||||
# The capabilities payload has one definition (controller.capabilities): a
|
||||
# second, private copy here silently stripped every field it did not know
|
||||
# about from GET /computes/{id} — which is how ubridge_tap and
|
||||
|
||||
@@ -5,7 +5,7 @@ Packet filter parameter validation utilities.
|
||||
import logging
|
||||
import re
|
||||
import subprocess
|
||||
from typing import Any, Dict, List, Optional, Set, Tuple
|
||||
from typing import Any, Dict, List, Set, Tuple
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -47,7 +47,7 @@ _RATE_UNITS = {
|
||||
_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.
|
||||
|
||||
@@ -142,9 +142,7 @@ def _validate_int_parameter(filter_type: str, rules: dict, i: int, value: Any) -
|
||||
else:
|
||||
int_value = int(value)
|
||||
except (ValueError, TypeError):
|
||||
raise FilterValidationError(
|
||||
f"{filter_type} parameter {rules['names'][i]} must be an integer, got: {value}"
|
||||
)
|
||||
raise FilterValidationError(f"{filter_type} parameter {rules['names'][i]} must be an integer, got: {value}")
|
||||
|
||||
# Range validation
|
||||
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:
|
||||
raise FilterValidationError("reorder requires delay")
|
||||
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")
|
||||
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")
|
||||
Reference in new issue
Block a user