refactor(gns3server,tests): StrEnum, ASYNC109 and others

This commit is contained in:
Mark Paronyan 2026-10-17 12:02:53 +03:00
parent b7e6c02647
commit 4c311baabf
No known key found for this signature in database
GPG Key ID: 8DC7338A87FE22F8
54 changed files with 195 additions and 165 deletions

View File

@ -224,7 +224,7 @@ class DockerHTTPClient:
return None
raise
async def stop_container(self, container_id: str, timeout: int = 0):
async def stop_container(self, container_id: str, timeout: int = 0): # noqa: ASYNC109 - operation-specific timeout is intentional
"""Stop container immediately (force kill, no graceful shutdown)."""
await self._request("POST", f"containers/{container_id}/stop", params={"t": timeout})

View File

@ -85,7 +85,7 @@ class WebWiresharkManager:
logger.warning(f"Container {container_id[:12]} health check failed: {e}")
return False
async def _exec_in_container(self, container_id: str, command: str, timeout: Optional[int] = None) -> tuple:
async def _exec_in_container(self, container_id: str, command: str, timeout: Optional[int] = None) -> tuple: # noqa: ASYNC109 - operation-specific timeout is intentional
"""Execute command in container with timeout.
Args:

View File

@ -418,7 +418,12 @@ LIFECYCLE_RESPONSES: dict[int | str, dict[str, Any]] = {
async def _run_lifecycle(
node: Node, action: Callable, target: str, wait: bool, timeout: int, tolerate_unsupported: bool = True
node: Node,
action: Callable,
target: str,
wait: bool,
timeout: int, # noqa: ASYNC109 - operation-specific timeout is intentional
tolerate_unsupported: bool = True,
) -> dict:
async def _run():
try:
@ -449,7 +454,7 @@ async def _run_lifecycle(
async def start_node(
start_data: Optional[dict] = None,
wait: bool = Query(False, description="Return only when the node status is 'started'"),
timeout: int = Query(240, ge=1, le=3600, description="Seconds to wait when 'wait' is true"),
timeout: int = Query(240, ge=1, le=3600, description="Seconds to wait when 'wait' is true"), # noqa: ASYNC109 - operation-specific timeout is intentional
node: Node = Depends(dep_node),
) -> dict:
"""
@ -469,7 +474,7 @@ async def start_node(
)
async def stop_node(
wait: bool = Query(False, description="Return only when the node status is 'stopped'"),
timeout: int = Query(240, ge=1, le=3600, description="Seconds to wait when 'wait' is true"),
timeout: int = Query(240, ge=1, le=3600, description="Seconds to wait when 'wait' is true"), # noqa: ASYNC109 - operation-specific timeout is intentional
node: Node = Depends(dep_node),
) -> dict:
"""
@ -491,7 +496,7 @@ async def stop_node(
)
async def suspend_node(
wait: bool = Query(False, description="Return only when the node status is 'suspended'"),
timeout: int = Query(240, ge=1, le=3600, description="Seconds to wait when 'wait' is true"),
timeout: int = Query(240, ge=1, le=3600, description="Seconds to wait when 'wait' is true"), # noqa: ASYNC109 - operation-specific timeout is intentional
node: Node = Depends(dep_node),
) -> dict:
"""
@ -514,7 +519,7 @@ async def suspend_node(
)
async def reload_node(
wait: bool = Query(False, description="Return only when the node status is 'started'"),
timeout: int = Query(240, ge=1, le=3600, description="Seconds to wait when 'wait' is true"),
timeout: int = Query(240, ge=1, le=3600, description="Seconds to wait when 'wait' is true"), # noqa: ASYNC109 - operation-specific timeout is intentional
node: Node = Depends(dep_node),
) -> dict:
"""

View File

@ -283,7 +283,7 @@ class Docker(BaseManager):
log.debug("Query Docker %s %s params=%s data=%s Response: %s", method, path, params, data, body)
return body
async def http_query(self, method, path, data={}, params={}, timeout=300):
async def http_query(self, method, path, data={}, params={}, timeout=300): # noqa: ASYNC109 - operation-specific timeout is intentional
"""
Makes a query to the docker daemon

View File

@ -1711,7 +1711,15 @@ class DockerVM(DockerKernelDatapathMixin, BaseNode):
status_line = headers.split(b"\r\n", 1)[0]
if b" 101 " not in status_line and b" 200 " not in status_line:
raise DockerError(f"Docker interface monitor was rejected: {status_line.decode(errors='ignore')}")
except (TimeoutError, DockerError, OSError, KeyError, TypeError, RuntimeError, asyncio.IncompleteReadError) as e:
except (
TimeoutError,
DockerError,
OSError,
KeyError,
TypeError,
RuntimeError,
asyncio.IncompleteReadError,
) as e:
if "writer" in locals():
await self._close_interface_monitor_writer(writer)
log.warning("Could not monitor interfaces for Docker container '%s': %s", self.name, e)

View File

@ -57,7 +57,7 @@ class DynamipsHypervisor:
self._writer = None
self._io_lock = asyncio.Lock()
async def connect(self, timeout=10):
async def connect(self, timeout=10): # noqa: ASYNC109 - operation-specific timeout is intentional
"""
Connects to the hypervisor.
"""

View File

@ -1307,7 +1307,7 @@ class QemuVM(KernelDatapathMixin, BaseNode):
# already gone and sweeps nothing.
await self._stop_ubridge()
async def _open_qemu_monitor_connection_vm(self, timeout=10):
async def _open_qemu_monitor_connection_vm(self, timeout=10): # noqa: ASYNC109 - operation-specific timeout is intentional
"""
Opens a connection to the QEMU monitor.

View File

@ -62,7 +62,7 @@ _bridge_tap_cache: dict[tuple[str, int, int, int], Optional[bool]] = {}
_lock = asyncio.Lock()
async def probe_tc_capabilities(timeout: float = 15.0):
async def probe_tc_capabilities(timeout: float = 15.0): # noqa: ASYNC109 - operation-specific timeout is intentional
"""
Probe the configured uBridge's tc module once per binary identity.
@ -88,7 +88,7 @@ async def probe_tc_capabilities(timeout: float = 15.0):
return caps
async def _probe(path, config, timeout):
async def _probe(path, config, timeout): # noqa: ASYNC109 - operation-specific timeout is intentional
"""
Spawn a throwaway uBridge, ask ``tc capabilities``, tear it down.
"""
@ -124,7 +124,7 @@ async def _probe(path, config, timeout):
shutil.rmtree(working_dir, ignore_errors=True)
async def probe_tap_support(timeout: float = 15.0):
async def probe_tap_support(timeout: float = 15.0): # noqa: ASYNC109 - operation-specific timeout is intentional
"""
Whether this host's uBridge can create persistent TAPs (the ``tap``
module), probed the same way as the tc report — a throwaway uBridge, the
@ -151,7 +151,7 @@ async def probe_tap_support(timeout: float = 15.0):
return supported
async def _probe_tap(path, config, timeout):
async def _probe_tap(path, config, timeout): # noqa: ASYNC109 - operation-specific timeout is intentional
"""
Spawn a throwaway uBridge and try to create (then delete) one persistent
TAP. The probe device is named after a uuid so a concurrent probe on the
@ -200,7 +200,7 @@ async def _probe_tap(path, config, timeout):
_IOL_PROBE_BRIDGE_ID = 1050
async def probe_iol_tap_support(timeout: float = 15.0):
async def probe_iol_tap_support(timeout: float = 15.0): # noqa: ASYNC109 - operation-specific timeout is intentional
"""
Whether this host's uBridge can terminate an IOL port on a persistent
TAP (``iol_bridge add_nio_tap``) — the anchor IOU's Ethernet ports need
@ -229,7 +229,7 @@ async def probe_iol_tap_support(timeout: float = 15.0):
return supported
async def _probe_iol_tap(path, config, timeout):
async def _probe_iol_tap(path, config, timeout): # noqa: ASYNC109 - operation-specific timeout is intentional
"""
Spawn a throwaway uBridge and run one add_nio_tap/delete_nio_tap cycle on
a scratch bridge and scratch TAP — attaching works on a stopped bridge,
@ -280,7 +280,7 @@ async def _probe_iol_tap(path, config, timeout):
shutil.rmtree(working_dir, ignore_errors=True)
async def probe_bridge_tap_support(timeout: float = 15.0):
async def probe_bridge_tap_support(timeout: float = 15.0): # noqa: ASYNC109 - operation-specific timeout is intentional
"""
Whether this host's uBridge has ``bridge delete_nio_tap`` — the command
that makes the bridge module's TAP leg swappable (stop -> delete -> add ->
@ -311,7 +311,7 @@ async def probe_bridge_tap_support(timeout: float = 15.0):
return supported
async def _probe_bridge_tap(path, config, timeout):
async def _probe_bridge_tap(path, config, timeout): # noqa: ASYNC109 - operation-specific timeout is intentional
"""
Spawn a throwaway uBridge and issue the probe command against a bridge
name that cannot exist (uuid). The reply splits the builds: "bridge ...

View File

@ -58,7 +58,7 @@ class UBridgeHypervisor:
self._reader = None
self._writer = None
async def connect(self, timeout=10):
async def connect(self, timeout=10): # noqa: ASYNC109 - operation-specific timeout is intentional
"""
Connects to the hypervisor.
"""

View File

@ -85,7 +85,7 @@ class VirtualBox(BaseManager):
self._vboxmanage_path = vboxmanage_path
return vboxmanage_path
async def execute(self, subcommand, args, timeout=60):
async def execute(self, subcommand, args, timeout=60): # noqa: ASYNC109 - operation-specific timeout is intentional
# We use a lock prevent parallel execution due to strange errors
# reported by a user and reproduced by us.

View File

@ -344,7 +344,7 @@ class VMware(BaseManager):
return self._host_type
async def execute(self, subcommand, args, timeout=120, log_level=logging.INFO):
async def execute(self, subcommand, args, timeout=120, log_level=logging.INFO): # noqa: ASYNC109 - operation-specific timeout is intentional
trial = 2
while True:
@ -361,7 +361,7 @@ class VMware(BaseManager):
trial -= 1
await asyncio.sleep(0.5)
async def _execute(self, subcommand, args, timeout=120, log_level=logging.INFO):
async def _execute(self, subcommand, args, timeout=120, log_level=logging.INFO): # noqa: ASYNC109 - operation-specific timeout is intentional
if self.host_type is None:
await self.check_vmware_version()

View File

@ -57,7 +57,7 @@ _lock = asyncio.Lock()
_NO_HOSTCONF_NEEDLE = "do not auto-configure"
async def probe_vpcs_tap(timeout: float = 5.0) -> bool | None:
async def probe_vpcs_tap(timeout: float = 5.0) -> bool | None: # noqa: ASYNC109 - operation-specific timeout is intentional
"""
Whether this host's VPCS binary can anchor a kernel link (tap mode with
``-n``), probed once per binary identity.

View File

@ -596,7 +596,7 @@ class Compute:
"""Returns URL for specific path at Compute"""
return self._getUrl(path)
async def _run_http_query(self, method, path, data=None, timeout=120, raw=False, stream=False, params=None):
async def _run_http_query(self, method, path, data=None, timeout=120, raw=False, stream=False, params=None): # noqa: ASYNC109 - operation-specific timeout is intentional
async with asynctimeout(delay=timeout):
url = self._getUrl(path)
headers = {"content-type": "application/json"}

View File

@ -43,7 +43,7 @@ class VirtualBoxGNS3VM(BaseGNS3VM):
self._system_properties = {}
self._virtualbox_manager = VirtualBox()
async def _execute(self, subcommand, args, timeout=60):
async def _execute(self, subcommand, args, timeout=60): # noqa: ASYNC109 - operation-specific timeout is intentional
try:
result = await self._virtualbox_manager.execute(subcommand, args, timeout)

View File

@ -41,7 +41,7 @@ class VMwareGNS3VM(BaseGNS3VM):
def vmx_path(self):
return self._vmx_path
async def _execute(self, subcommand, args, timeout=60, log_level=logging.INFO):
async def _execute(self, subcommand, args, timeout=60, log_level=logging.INFO): # noqa: ASYNC109 - operation-specific timeout is intentional
try:
result = await self._vmware_manager.execute(subcommand, args, timeout, log_level=log_level)

View File

@ -331,7 +331,7 @@ class RbacRepository(BaseRepository):
Retrieve all user ACEs matching the user_id and privilege name.
"""
query: Select[Any, Any, Any, Any] = (
query: Select[tuple[Any, Any, Any, Any]] = (
select(models.ACE.path, models.ACE.propagate, models.ACE.allowed, models.Privilege.name)
.join(models.Privilege.roles)
.join(models.Role.acl_entries)
@ -349,7 +349,7 @@ class RbacRepository(BaseRepository):
Retrieve all group ACEs matching the user_id and privilege name.
"""
query: Select[Any, Any, Any, Any] = (
query: Select[tuple[Any, Any, Any, Any]] = (
select(models.ACE.path, models.ACE.propagate, models.ACE.allowed, models.Privilege.name)
.join(models.Privilege.roles)
.join(models.Role.acl_entries)

View File

@ -14,7 +14,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from enum import Enum
from enum import StrEnum
from typing import Any, Optional
from pydantic import BaseModel, Field, field_validator
@ -33,7 +33,7 @@ class ErrorMessage(BaseModel):
details: Optional[dict[str, Any]] = Field(None, description="Optional structured data about the error")
class NodeStatus(str, Enum):
class NodeStatus(StrEnum):
"""
Supported node statuses.
"""
@ -85,7 +85,7 @@ class ExtraConfig(BaseModel):
return v
class ConsoleType(str, Enum):
class ConsoleType(StrEnum):
"""
Supported console types.
"""
@ -101,7 +101,7 @@ class ConsoleType(str, Enum):
docker_exec = "docker_exec"
class AuxType(str, Enum):
class AuxType(StrEnum):
"""
Supported auxiliary console types.
"""

View File

@ -14,7 +14,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from enum import Enum
from enum import StrEnum
from typing import List, Optional, Union
from uuid import UUID
@ -24,17 +24,17 @@ from ..common import NodeStatus
from ..update import PartialUpdateModel
class HostInterfaceType(str, Enum):
class HostInterfaceType(StrEnum):
ethernet = "ethernet"
tap = "tap"
class IPAddressFamily(str, Enum):
class IPAddressFamily(StrEnum):
ipv4 = "ipv4"
ipv6 = "ipv6"
class InterfaceStatus(str, Enum):
class InterfaceStatus(StrEnum):
up = "up"
down = "down"
@ -66,7 +66,7 @@ class HostInterface(BaseModel):
flags: List[str] = Field(default_factory=list, description="Interface flags")
class EthernetType(str, Enum):
class EthernetType(StrEnum):
ethernet = "ethernet"
@ -81,7 +81,7 @@ class EthernetPort(BaseModel):
interface: str
class TAPType(str, Enum):
class TAPType(StrEnum):
tap = "tap"
@ -96,7 +96,7 @@ class TAPPort(BaseModel):
interface: str
class UDPType(str, Enum):
class UDPType(StrEnum):
udp = "udp"
@ -113,7 +113,7 @@ class UDPPort(BaseModel):
rport: int = Field(..., gt=0, le=65535, description="Remote port")
class CloudConsoleType(str, Enum):
class CloudConsoleType(StrEnum):
telnet = "telnet"
ssh = "ssh"
vnc = "vnc"

View File

@ -15,7 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from enum import Enum
from enum import StrEnum
from typing import List, Optional
from uuid import UUID
@ -25,7 +25,7 @@ from ..common import NodeStatus
from ..update import PartialUpdateModel
class DynamipsPlatform(str, Enum):
class DynamipsPlatform(StrEnum):
"""
Supported Dynamips Platforms.
"""
@ -39,7 +39,7 @@ class DynamipsPlatform(str, Enum):
c1700 = "c1700"
class DynamipsAdapters(str, Enum):
class DynamipsAdapters(StrEnum):
"""
Supported Dynamips Network Modules.
"""
@ -72,7 +72,7 @@ class DynamipsAdapters(str, Enum):
_ = ""
class DynamipsWics(str, Enum):
class DynamipsWics(StrEnum):
"""
Supported Dynamips WICs.
"""
@ -83,7 +83,7 @@ class DynamipsWics(str, Enum):
_ = ""
class DynamipsConsoleType(str, Enum):
class DynamipsConsoleType(StrEnum):
"""
Supported Dynamips console types.
"""
@ -93,7 +93,7 @@ class DynamipsConsoleType(str, Enum):
none = "none"
class DynamipsNPE(str, Enum):
class DynamipsNPE(StrEnum):
"""
Supported Dynamips NPE models.
"""
@ -108,7 +108,7 @@ class DynamipsNPE(str, Enum):
npe_g2 = "npe-g2"
class DynamipsMidplane(str, Enum):
class DynamipsMidplane(StrEnum):
"""
Supported Dynamips Midplane models.
"""

View File

@ -14,7 +14,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from enum import Enum
from enum import StrEnum
from typing import List, Optional
from uuid import UUID
@ -24,13 +24,13 @@ from ..common import NodeStatus
from ..update import PartialUpdateModel
class EthernetSwitchPortType(str, Enum):
class EthernetSwitchPortType(StrEnum):
access = "access"
dot1q = "dot1q"
qinq = "qinq"
class EthernetSwitchEtherType(str, Enum):
class EthernetSwitchEtherType(StrEnum):
ethertype_8021q = "0x8100"
ethertype_qinq = "0x88A8"
ethertype_8021q9100 = "0x9100"
@ -54,7 +54,7 @@ class EthernetSwitchPort(BaseModel):
return self
class TelnetConsoleType(str, Enum):
class TelnetConsoleType(StrEnum):
"""
Supported console types.
"""

View File

@ -14,7 +14,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from enum import Enum
from enum import StrEnum
from typing import List, Optional, Union
from uuid import UUID
@ -24,7 +24,7 @@ from ..common import NodeStatus
from ..update import PartialUpdateModel
class HostInterfaceType(str, Enum):
class HostInterfaceType(StrEnum):
ethernet = "ethernet"
tap = "tap"
@ -39,7 +39,7 @@ class HostInterface(BaseModel):
special: bool = Field(..., description="Whether the interface is non standard")
class EthernetType(str, Enum):
class EthernetType(StrEnum):
ethernet = "ethernet"
@ -54,7 +54,7 @@ class EthernetPort(BaseModel):
interface: str
class TAPType(str, Enum):
class TAPType(StrEnum):
tap = "tap"
@ -69,7 +69,7 @@ class TAPPort(BaseModel):
interface: str
class UDPType(str, Enum):
class UDPType(StrEnum):
udp = "udp"

View File

@ -15,13 +15,13 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from enum import Enum
from enum import StrEnum
from typing import List, Optional, Union
from pydantic import BaseModel, Field
class UDPNIOType(str, Enum):
class UDPNIOType(StrEnum):
udp = "nio_udp"
@ -43,7 +43,7 @@ class UDPNIO(BaseModel):
markers: Optional[dict] = Field(None, description="Traffic-insight markers")
class EthernetNIOType(str, Enum):
class EthernetNIOType(StrEnum):
ethernet = "nio_ethernet"
@ -56,7 +56,7 @@ class EthernetNIO(BaseModel):
ethernet_device: str = Field(..., description="Ethernet device name e.g. eth0")
class TAPNIOType(str, Enum):
class TAPNIOType(StrEnum):
tap = "nio_tap"
@ -69,7 +69,7 @@ class TAPNIO(BaseModel):
tap_device: str = Field(..., description="TAP device name e.g. tap0")
class BridgeNIOType(str, Enum):
class BridgeNIOType(StrEnum):
bridge = "nio_bridge"
@ -108,7 +108,7 @@ class BridgeNIO(BaseModel):
markers: Optional[dict] = Field(None, description="Traffic-insight markers (attached to the veth host end)")
class AnchorNIOType(str, Enum):
class AnchorNIOType(StrEnum):
anchor = "nio_anchor"

View File

@ -14,7 +14,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from enum import Enum
from enum import StrEnum
from typing import List, Optional
from uuid import UUID
@ -24,7 +24,7 @@ from ..common import CustomAdapter, NodeStatus
from ..update import PartialUpdateModel
class QemuPlatform(str, Enum):
class QemuPlatform(StrEnum):
aarch64 = "aarch64"
alpha = "alpha"
arm = "arm"
@ -55,7 +55,7 @@ class QemuPlatform(str, Enum):
xtensaeb = "xtensaeb"
class QemuConsoleType(str, Enum):
class QemuConsoleType(StrEnum):
"""
Supported console types.
"""
@ -68,7 +68,7 @@ class QemuConsoleType(str, Enum):
none = "none"
class QemuBootPriority(str, Enum):
class QemuBootPriority(StrEnum):
"""
Supported boot priority types.
"""
@ -84,7 +84,7 @@ class QemuBootPriority(str, Enum):
nd = "nd"
class QemuOnCloseAction(str, Enum):
class QemuOnCloseAction(StrEnum):
"""
Supported actions when closing Qemu VM.
"""
@ -94,7 +94,7 @@ class QemuOnCloseAction(str, Enum):
save_vm_state = "save_vm_state"
class QemuProcessPriority(str, Enum):
class QemuProcessPriority(StrEnum):
realtime = "realtime"
very_high = "very high"
high = "high"
@ -103,7 +103,7 @@ class QemuProcessPriority(str, Enum):
very_low = "very low"
class QemuAdapterType(str, Enum):
class QemuAdapterType(StrEnum):
"""
Supported Qemu VM adapter types.
"""
@ -135,7 +135,7 @@ class QemuAdapterType(str, Enum):
vmxnet3 = "vmxnet3"
class QemuDiskInterfaceType(str, Enum):
class QemuDiskInterfaceType(StrEnum):
"""
Supported Qemu VM disk interface types.
"""

View File

@ -14,7 +14,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from enum import Enum
from enum import StrEnum
from typing import List, Optional
from uuid import UUID
@ -24,7 +24,7 @@ from ..common import CustomAdapter, NodeStatus
from ..update import PartialUpdateModel
class VirtualBoxConsoleType(str, Enum):
class VirtualBoxConsoleType(StrEnum):
"""
Supported console types.
"""
@ -34,7 +34,7 @@ class VirtualBoxConsoleType(str, Enum):
none = "none"
class VirtualBoxOnCloseAction(str, Enum):
class VirtualBoxOnCloseAction(StrEnum):
"""
Supported actions when closing VirtualBox VM.
"""
@ -44,7 +44,7 @@ class VirtualBoxOnCloseAction(str, Enum):
save_vm_state = "save_vm_state"
class VirtualBoxAdapterType(str, Enum):
class VirtualBoxAdapterType(StrEnum):
pcnet_pci_ii = ("PCnet-PCI II (Am79C970A)",)
pcnet_fast_iii = ("PCNet-FAST III (Am79C973)",)
intel_pro_1000_mt_desktop = ("Intel PRO/1000 MT Desktop (82540EM)",)

View File

@ -14,7 +14,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from enum import Enum
from enum import StrEnum
from typing import List, Optional
from uuid import UUID
@ -24,7 +24,7 @@ from ..common import CustomAdapter, NodeStatus
from ..update import PartialUpdateModel
class VMwareConsoleType(str, Enum):
class VMwareConsoleType(StrEnum):
"""
Supported console types.
"""
@ -34,7 +34,7 @@ class VMwareConsoleType(str, Enum):
none = "none"
class VMwareOnCloseAction(str, Enum):
class VMwareOnCloseAction(StrEnum):
"""
Supported actions when closing VMware VM.
"""
@ -44,7 +44,7 @@ class VMwareOnCloseAction(str, Enum):
save_vm_state = "save_vm_state"
class VMwareAdapterType(str, Enum):
class VMwareAdapterType(StrEnum):
"""
Supported VMware VM adapter types.
"""

View File

@ -14,7 +14,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from enum import Enum
from enum import StrEnum
from typing import Optional
from uuid import UUID
@ -24,7 +24,7 @@ from ..common import NodeStatus
from ..update import PartialUpdateModel
class ConsoleType(str, Enum):
class ConsoleType(StrEnum):
"""
Supported console types.
"""

View File

@ -15,7 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import socket
from enum import Enum
from enum import StrEnum
from typing import List, Optional
from pydantic import BaseModel, ConfigDict, DirectoryPath, Field, FilePath, SecretStr, field_validator, model_validator
@ -126,12 +126,12 @@ class WebWiresharkSettings(BaseModel):
model_config = ConfigDict(validate_assignment=True, str_strip_whitespace=True)
class ServerProtocol(str, Enum):
class ServerProtocol(StrEnum):
http = "http"
https = "https"
class UbridgeControlTransport(str, Enum):
class UbridgeControlTransport(StrEnum):
# TCP control channel: -H host:port. ubridge now binds loopback by default,
# so this is reachable only locally. Retained for backward compatibility.
tcp = "tcp"
@ -140,7 +140,7 @@ class UbridgeControlTransport(str, Enum):
unix = "unix"
class BuiltinSymbolTheme(str, Enum):
class BuiltinSymbolTheme(StrEnum):
classic = "Classic"
affinity_square_blue = "Affinity-square-blue"
affinity_square_red = "Affinity-square-red"

View File

@ -16,7 +16,7 @@
# Unified Pydantic model supporting both appliance registry versions using discriminated unions
from enum import Enum
from enum import StrEnum
from typing import Annotated, List, Literal, Optional, Union
from uuid import UUID
@ -29,7 +29,7 @@ from ..common import ExtraConfig
# ============================================================================
class Category(str, Enum):
class Category(StrEnum):
"""Appliance category enum"""
router = "router"
@ -39,7 +39,7 @@ class Category(str, Enum):
guest = "guest"
class Status(str, Enum):
class Status(StrEnum):
"""Appliance status enum"""
stable = "stable"
@ -47,7 +47,7 @@ class Status(str, Enum):
broken = "broken"
class Availability(str, Enum):
class Availability(StrEnum):
"""Image availability enum"""
free = "free"
@ -56,7 +56,7 @@ class Availability(str, Enum):
service_contract = "service-contract"
class Compression(str, Enum):
class Compression(StrEnum):
"""Compression type enum"""
bzip2 = "bzip2"
@ -68,7 +68,7 @@ class Compression(str, Enum):
field_7z = "7z"
class DynamipsPlatform(str, Enum):
class DynamipsPlatform(StrEnum):
"""Dynamips platform enum"""
c1700 = "c1700"
@ -80,7 +80,7 @@ class DynamipsPlatform(str, Enum):
c7200 = "c7200"
class DynamipsChassis(str, Enum):
class DynamipsChassis(StrEnum):
"""Dynamips chassis enum"""
chassis_1720 = "1720"
@ -103,7 +103,7 @@ class DynamipsChassis(str, Enum):
_ = ""
class DynamipsSlot(str, Enum):
class DynamipsSlot(StrEnum):
"""Dynamips slot enum"""
C7200_IO_2FE = "C7200-IO-2FE"
@ -134,7 +134,7 @@ class DynamipsSlot(str, Enum):
_ = ""
class DynamipsWic(str, Enum):
class DynamipsWic(StrEnum):
"""Dynamips WIC enum"""
WIC_1ENET = "WIC-1ENET"
@ -143,14 +143,14 @@ class DynamipsWic(str, Enum):
_ = ""
class DynamipsMidplane(str, Enum):
class DynamipsMidplane(StrEnum):
"""Dynamips midplane enum"""
std = "std"
vxr = "vxr"
class DynamipsNpe(str, Enum):
class DynamipsNpe(StrEnum):
"""Dynamips NPE enum"""
npe_100 = "npe-100"
@ -163,7 +163,7 @@ class DynamipsNpe(str, Enum):
npe_g2 = "npe-g2"
class QemuConsoleType(str, Enum):
class QemuConsoleType(StrEnum):
"""Qemu console type enum"""
telnet = "telnet"
@ -174,7 +174,7 @@ class QemuConsoleType(str, Enum):
none = "none"
class QemuAdapterType(str, Enum):
class QemuAdapterType(StrEnum):
"""Qemu adapter type enum"""
e1000 = "e1000"
@ -200,7 +200,7 @@ class QemuAdapterType(str, Enum):
vmxnet3 = "vmxnet3"
class QemuPlatform(str, Enum):
class QemuPlatform(StrEnum):
"""Qemu platform enum"""
aarch64 = "aarch64"
@ -233,7 +233,7 @@ class QemuPlatform(str, Enum):
xtensaeb = "xtensaeb"
class QemuDiskInterface(str, Enum):
class QemuDiskInterface(StrEnum):
"""Disk interface enum"""
ide = "ide"
@ -248,7 +248,7 @@ class QemuDiskInterface(str, Enum):
none = "none"
class QemuBootPriority(str, Enum):
class QemuBootPriority(StrEnum):
"""Boot priority enum"""
c = "c"
@ -262,7 +262,7 @@ class QemuBootPriority(str, Enum):
nd = "nd"
class QemuOnClose(str, Enum):
class QemuOnClose(StrEnum):
"""Qemu on_close action enum"""
power_off = "power_off"
@ -270,7 +270,7 @@ class QemuOnClose(str, Enum):
save_vm_state = "save_vm_state"
class QemuProcessPriority(str, Enum):
class QemuProcessPriority(StrEnum):
"""Qemu process priority enum"""
realtime = "realtime"
@ -281,7 +281,7 @@ class QemuProcessPriority(str, Enum):
very_low = "very low"
class DockerConsoleType(str, Enum):
class DockerConsoleType(StrEnum):
"""Docker console type enum"""
telnet = "telnet"
@ -293,13 +293,13 @@ class DockerConsoleType(str, Enum):
docker_exec = "docker_exec"
class ChecksumType(str, Enum):
class ChecksumType(StrEnum):
"""Checksum type enum"""
md5 = "md5"
class TemplateType(str, Enum):
class TemplateType(StrEnum):
"""Template type enum"""
docker = "docker"
@ -313,7 +313,7 @@ class TemplateType(str, Enum):
# ============================================================================
class Kvm(str, Enum):
class Kvm(StrEnum):
"""KVM requirements enum"""
require = "require"

View File

@ -15,7 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import uuid
from enum import Enum
from enum import StrEnum
from typing import Any, Optional, Union
from pydantic import BaseModel, ConfigDict, Field, SecretStr, model_validator
@ -29,7 +29,7 @@ from .base import DateTimeModelMixin
from .capabilities import Capabilities
class Protocol(str, Enum):
class Protocol(StrEnum):
"""
Protocol supported to communicate with a compute.
"""

View File

@ -14,13 +14,13 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from enum import Enum
from enum import StrEnum
from typing import Optional
from pydantic import BaseModel, Field
class WhenExit(str, Enum):
class WhenExit(StrEnum):
"""
What to do with the VM when GNS3 VM exits.
"""
@ -30,7 +30,7 @@ class WhenExit(str, Enum):
keep = "keep"
class Engine(str, Enum):
class Engine(StrEnum):
"""
"The engine to use for the GNS3 VM.
"""

View File

@ -15,7 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from datetime import datetime
from enum import Enum
from enum import StrEnum
from typing import Annotated, List, Literal, Optional
from uuid import UUID
@ -24,7 +24,7 @@ from pydantic import BaseModel, ConfigDict, Field
from .base import DateTimeModelMixin
class ImageType(str, Enum):
class ImageType(StrEnum):
qemu = "qemu"
ios = "ios"
iou = "iou"

View File

@ -14,7 +14,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from enum import Enum
from enum import StrEnum
from typing import Annotated, List, Literal, Optional, Tuple, Union
from uuid import UUID, uuid4
@ -62,7 +62,7 @@ class LinkNodeCreate(BaseModel):
return self
class LinkType(str, Enum):
class LinkType(StrEnum):
"""
Link type.
"""
@ -81,7 +81,7 @@ class LinkStyle(BaseModel):
control_offset: Optional[Tuple[float, float]] = None
class LinkFilterType(str, Enum):
class LinkFilterType(StrEnum):
"""
Packet filter type. The first five run on relay and kernel-datapath links
alike; the rest are kernel-datapath only (tc netem / cls_bpf / the eBPF

View File

@ -14,7 +14,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from enum import Enum
from enum import StrEnum
from typing import List, Optional, Union
from uuid import UUID, uuid4
@ -25,7 +25,7 @@ from ..update import PartialUpdateModel
from .labels import Label
class NodeType(str, Enum):
class NodeType(StrEnum):
"""
Supported node types.
"""
@ -56,7 +56,7 @@ class Image(BaseModel):
filesize: Optional[int] = None
class LinkType(str, Enum):
class LinkType(StrEnum):
"""
Supported link types.
"""
@ -65,7 +65,7 @@ class LinkType(str, Enum):
serial = "serial"
class DataLinkType(str, Enum):
class DataLinkType(StrEnum):
"""
Supported data link types.
"""

View File

@ -14,7 +14,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from enum import Enum
from enum import StrEnum
from typing import Any, Dict, List, Optional, Union
from uuid import UUID
@ -28,7 +28,7 @@ from .projects import Project
from .snapshots import Snapshot
class NotificationAction(str, Enum):
class NotificationAction(StrEnum):
"""
Values of the "action" field of a notification.
"""

View File

@ -14,7 +14,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from enum import Enum
from enum import StrEnum
from typing import Optional
from uuid import UUID
@ -23,7 +23,7 @@ from pydantic import BaseModel, ConfigDict, Field
from .base import DateTimeModelMixin
class ResourceType(str, Enum):
class ResourceType(StrEnum):
project = "project"

View File

@ -16,7 +16,7 @@
from datetime import datetime
from enum import Enum
from enum import StrEnum
from typing import List, Optional
from uuid import UUID
@ -25,7 +25,7 @@ from pydantic import BaseModel, Field, HttpUrl
from ..update import PartialUpdateModel
class ProjectStatus(str, Enum):
class ProjectStatus(StrEnum):
"""
Supported project statuses.
"""
@ -117,7 +117,7 @@ class NodeFile(BaseModel):
file_type: str = Field(..., description="File type determined by the file command")
class ProjectCompression(str, Enum):
class ProjectCompression(StrEnum):
"""
Supported project compression.
"""

View File

@ -14,7 +14,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from enum import Enum
from enum import StrEnum
from typing import List, Optional
from uuid import UUID
@ -37,7 +37,7 @@ class Privilege(DateTimeModelMixin, PrivilegeBase):
model_config = ConfigDict(from_attributes=True)
class ACEType(str, Enum):
class ACEType(StrEnum):
user = "user"
group = "group"

View File

@ -14,7 +14,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from enum import Enum
from enum import StrEnum
from typing import List, Optional
from uuid import UUID
@ -25,7 +25,7 @@ from ..base import DateTimeModelMixin
from ..nodes import NodeType
class Category(str, Enum):
class Category(StrEnum):
"""
Supported categories
"""

View File

@ -14,7 +14,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from enum import Enum
from enum import StrEnum
from typing import Optional
from pydantic import Field
@ -123,7 +123,7 @@ class C3745DynamipsTemplate(C3745DynamipsTemplateBase):
image: str = Field(..., description="Path to the IOS image")
class C3600ChassisType(str, Enum):
class C3600ChassisType(StrEnum):
chassis_3620 = "3620"
chassis_3640 = "3640"
chassis_3660 = "3660"
@ -162,7 +162,7 @@ class C2691DynamipsTemplate(C2691DynamipsTemplateBase):
image: str = Field(..., description="Path to the IOS image")
class C2600ChassisType(str, Enum):
class C2600ChassisType(StrEnum):
chassis_2610 = "2610"
chassis_2620 = "2620"
chassis_2610xm = "2610XM"
@ -191,7 +191,7 @@ class C2600DynamipsTemplate(C2600DynamipsTemplateBase):
image: str = Field(..., description="Path to the IOS image")
class C1700ChassisType(str, Enum):
class C1700ChassisType(StrEnum):
chassis_1720 = "1720"
chassis_1721 = "1721"
chassis_1750 = "1750"

View File

@ -15,7 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from enum import Enum
from enum import StrEnum
from typing import List, Optional
from pydantic import Field
@ -37,7 +37,7 @@ DEFAULT_PORTS = [
]
class ConsoleType(str, Enum):
class ConsoleType(StrEnum):
"""
Supported console types for Ethernet switch nodes.
"""

View File

@ -19,7 +19,7 @@
# This file contains the validation for checking a .gns3 file
#
from enum import Enum
from enum import StrEnum
from typing import List, Optional
from uuid import UUID
@ -37,7 +37,7 @@ class TopologyLink(Link):
filters: Optional[dict] = None
class TopologyType(str, Enum):
class TopologyType(StrEnum):
topology = "topology"

View File

@ -14,13 +14,13 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from enum import Enum
from enum import StrEnum
from typing import Optional
from pydantic import BaseModel, Field
class QemuDiskImageFormat(str, Enum):
class QemuDiskImageFormat(StrEnum):
"""
Supported Qemu disk image formats.
"""
@ -33,7 +33,7 @@ class QemuDiskImageFormat(str, Enum):
raw = "raw"
class QemuDiskImagePreallocation(str, Enum):
class QemuDiskImagePreallocation(StrEnum):
"""
Supported Qemu disk image pre-allocation options.
"""
@ -44,7 +44,7 @@ class QemuDiskImagePreallocation(str, Enum):
full = "full"
class QemuDiskImageOnOff(str, Enum):
class QemuDiskImageOnOff(StrEnum):
"""
Supported Qemu image on/off options.
"""
@ -53,7 +53,7 @@ class QemuDiskImageOnOff(str, Enum):
off = "off"
class QemuDiskImageSubformat(str, Enum):
class QemuDiskImageSubformat(StrEnum):
"""
Supported Qemu disk image sub-format options.
"""
@ -67,7 +67,7 @@ class QemuDiskImageSubformat(str, Enum):
monolithic_flat = "monolithicFlat"
class QemuDiskImageAdapterType(str, Enum):
class QemuDiskImageAdapterType(StrEnum):
"""
Supported Qemu disk image on/off options.
"""

View File

@ -85,7 +85,7 @@ async def subprocess_check_output(*args, cwd=None, env=None, stderr=False):
return output.decode("utf-8", errors="ignore")
async def wait_for_process_termination(process, timeout=10):
async def wait_for_process_termination(process, timeout=10): # noqa: ASYNC109 - operation-specific timeout is intentional
"""
Wait for a process terminate, and raise asyncio.TimeoutError in case of
timeout.
@ -118,7 +118,7 @@ def monitor_process(process, termination_callback):
asyncio.ensure_future(_check_process(process, termination_callback))
async def wait_for_file_creation(path, timeout=60):
async def wait_for_file_creation(path, timeout=60): # noqa: ASYNC109 - operation-specific timeout is intentional
while timeout > 0:
if os.path.exists(path):

View File

@ -38,7 +38,7 @@ class NotificationQueue(asyncio.Queue):
self._first = True
self._last_ping = None
async def get(self, timeout):
async def get(self, timeout): # noqa: ASYNC109 - operation-specific timeout is intentional
"""
Return a notification, or a ping notification with server information
at least every `timeout` seconds. The ping used to be generated only
@ -84,7 +84,7 @@ class NotificationQueue(asyncio.Queue):
log.warning(f"Could not get CPU and memory usage from psutil: {e}")
return msg
async def get_json(self, timeout):
async def get_json(self, timeout): # noqa: ASYNC109 - operation-specific timeout is intentional
"""
Get a message as a JSON
"""

View File

@ -40,7 +40,7 @@ async def websocket_proxy(
target_url: str,
requested_protocols: Optional[list] = None,
buffer_size: int = 65536,
timeout: Optional[float] = None,
timeout: Optional[float] = None, # noqa: ASYNC109 - operation-specific timeout is intentional
) -> None:
"""
Proxy binary WebSocket data between client and target WebSocket server.
@ -142,7 +142,7 @@ async def websocket_proxy_with_manual_accept(
target_url: str,
requested_protocols: Optional[list] = None,
buffer_size: int = 65536,
timeout: Optional[float] = None,
timeout: Optional[float] = None, # noqa: ASYNC109 - operation-specific timeout is intentional
) -> None:
"""
Proxy binary WebSocket data between client and target WebSocket server.

View File

@ -64,7 +64,7 @@ async def vm(compute_project, manager):
def _capable(vm, monkeypatch):
"""Make both anchor-lifecycle probes answer True."""
async def probe(timeout=15.0):
async def probe(timeout=15.0): # noqa: ASYNC109 - mock signature must match patched API
return True
monkeypatch.setattr("gns3server.compute.docker.iol_docker_vm.probe_bridge_tap_support", probe)
@ -110,7 +110,7 @@ async def test_prepare_tap_datapath_falls_back_without_either_probe(vm, monkeypa
cannot carry kernel links.
"""
async def probe(timeout=15.0):
async def probe(timeout=15.0): # noqa: ASYNC109 - mock signature must match patched API
return None
monkeypatch.setattr("gns3server.compute.docker.iol_docker_vm.probe_bridge_tap_support", probe)

View File

@ -452,7 +452,7 @@ async def test_add_ubridge_connection_timeout_is_actionable(compute_project, man
_mock_wiring(vm)
vm._stop_ubridge = AsyncioMagicMock()
async def raise_timeout(path, timeout=60):
async def raise_timeout(path, timeout=60): # noqa: ASYNC109 - mock signature must match patched API
raise TimeoutError()
with patch("gns3server.compute.docker.vendor_docker_vm.wait_for_file_creation", side_effect=raise_timeout):

View File

@ -60,7 +60,7 @@ async def vm(compute_project, manager):
def _capable(vm, monkeypatch):
"""Make the iol-tap capability probe answer True."""
async def probe(timeout=15.0):
async def probe(timeout=15.0): # noqa: ASYNC109 - mock signature must match patched API
return True
monkeypatch.setattr("gns3server.compute.iou.iou_vm.probe_iol_tap_support", probe)
@ -99,7 +99,7 @@ async def test_prepare_tap_datapath_falls_back_without_the_command(vm, monkeypat
datapath — it still starts, it just cannot carry kernel links.
"""
async def probe(timeout=15.0):
async def probe(timeout=15.0): # noqa: ASYNC109 - mock signature must match patched API
return None
monkeypatch.setattr("gns3server.compute.iou.iou_vm.probe_iol_tap_support", probe)

View File

@ -358,7 +358,7 @@ async def test_stop_stops_the_process_before_deleting_the_taps(vm):
_running(vm)
order = []
async def fake_termination(process, timeout=None):
async def fake_termination(process, timeout=None): # noqa: ASYNC109 - mock signature must match patched API
order.append("process")
async def fake_stop_ubridge():
@ -476,7 +476,7 @@ async def test_concurrent_stops_sweep_every_tap_exactly_once(vm):
vm._ubridge_send = AsyncioMagicMock(side_effect=send)
async def fake_termination(process, timeout=None):
async def fake_termination(process, timeout=None): # noqa: ASYNC109 - mock signature must match patched API
pass
with (

View File

@ -19,6 +19,7 @@ The standalone `tc capabilities` probe behind the compute /capabilities
payload: spawn a throwaway uBridge, ask once, cache by binary identity.
"""
import asyncio
import os
import pytest
@ -140,10 +141,14 @@ async def test_probe_missing_binary_means_unknown(monkeypatch):
async def test_probe_reprobes_when_the_binary_attributes_change(probe_env):
# Ensure following chmod changes mode even under restrictive test-process umasks.
os.chmod(probe_env, 0o700)
assert await probe_tc_capabilities() is not None
# chmod touches ctime but neither mtime nor size: a setcap/chmod remedy
# after a permission-related probe failure must be able to flip the
# cached verdict without a server restart.
# cached verdict without a server restart. Wait for this filesystem's
# timestamp clock to advance before changing mode.
await asyncio.sleep(0.02)
os.chmod(probe_env, 0o600)
assert await probe_tc_capabilities() is not None
assert FakeHypervisor.spawned == 2

View File

@ -61,7 +61,7 @@ class TelnetClient:
log.warning(f"Client {self.client_id}: Send failed: {e}")
return False
async def receive_response(self, timeout: float = 1.0) -> Optional[str]:
async def receive_response(self, timeout: float = 1.0) -> Optional[str]: # noqa: ASYNC109 - mock signature must match patched API
"""Receive response from server (optional)."""
if not self.reader or not self.connected:
return None

View File

@ -40,7 +40,7 @@ async def _connect(port):
)
async def _wait_for_session_count(server, expected_count, timeout=2):
async def _wait_for_session_count(server, expected_count, timeout=2): # noqa: ASYNC109 - mock signature must match patched API
deadline = asyncio.get_running_loop().time() + timeout
last_count = 0
while asyncio.get_running_loop().time() < deadline:

View File

@ -24,7 +24,7 @@ def _get_listen_port(server):
return server.sockets[0].getsockname()[1]
async def _wait_for_connection_count(server, expected_count, timeout=2):
async def _wait_for_connection_count(server, expected_count, timeout=2): # noqa: ASYNC109 - mock signature must match patched API
deadline = asyncio.get_running_loop().time() + timeout
last_count = 0
while asyncio.get_running_loop().time() < deadline:

View File

@ -4,6 +4,7 @@ Unit tests for packet filter validation.
import pytest
from gns3server.utils import packet_filter_validation
from gns3server.utils.packet_filter_validation import (
FilterValidationError,
filter_inactive_filters,
@ -105,15 +106,26 @@ class TestPacketFilterValidation:
validate_filter_parameters("bpf", ["tcp port 80\nnot arp"])
validate_filter_parameters("bpf", ["tcp and not port 22\nhost 192.168.1.1\nicmp"])
def test_bpf_multi_line_invalid(self):
def test_bpf_multi_line_invalid(self, monkeypatch):
"""Test multi-line BPF with invalid line."""
monkeypatch.setattr(
packet_filter_validation,
"validate_bpf_syntax",
lambda expression: {"valid": expression != "invalid!!!", "error": "syntax error"},
)
with pytest.raises(FilterValidationError) as excinfo:
validate_filter_parameters("bpf", ["tcp port 80\ninvalid!!!"])
err = str(excinfo.value).lower()
assert "syntax error" in err
def test_bpf_invalid(self):
def test_bpf_invalid(self, monkeypatch):
"""Test invalid BPF parameters."""
monkeypatch.setattr(
packet_filter_validation,
"validate_bpf_syntax",
lambda expression: {"valid": expression != "tcp port", "error": "syntax error"},
)
# Wrong type
with pytest.raises(FilterValidationError, match="must be a string"):
validate_filter_parameters("bpf", [123])