From 9c850e0f2bb1f328d7477dd5cf3d723bf10a8615 Mon Sep 17 00:00:00 2001 From: grossmj Date: Thu, 15 Apr 2021 18:12:08 +0930 Subject: [PATCH 01/10] Move schemas between compute and controller subpackages --- gns3server/api/routes/compute/__init__.py | 104 ++++++++++++--- .../api/routes/compute/virtualbox_nodes.py | 5 - gns3server/api/routes/compute/vmware_nodes.py | 5 - gns3server/api/routes/controller/nodes.py | 2 +- gns3server/compute/project_manager.py | 15 --- gns3server/compute/qemu/qemu_vm.py | 2 +- gns3server/controller/__init__.py | 1 + gns3server/controller/drawing.py | 1 + gns3server/controller/topology.py | 4 +- gns3server/core/tasks.py | 8 ++ .../handlers/api/controller/symbol_handler.py | 0 gns3server/schemas/__init__.py | 78 ++++++----- gns3server/schemas/common.py | 48 +++++-- .../compute/__init__.py} | 0 .../schemas/{ => compute}/atm_switch_nodes.py | 2 +- .../schemas/{ => compute}/cloud_nodes.py | 2 +- .../schemas/{ => compute}/docker_nodes.py | 2 +- .../schemas/{ => compute}/dynamips_nodes.py | 2 +- .../{ => compute}/ethernet_hub_nodes.py | 2 +- .../{ => compute}/ethernet_switch_nodes.py | 2 +- .../{ => compute}/frame_relay_switch_nodes.py | 2 +- gns3server/schemas/{ => compute}/iou_nodes.py | 2 +- gns3server/schemas/{ => compute}/nat_nodes.py | 2 +- gns3server/schemas/{ => compute}/nios.py | 3 +- .../schemas/{ => compute}/qemu_nodes.py | 2 +- .../schemas/{ => compute}/virtualbox_nodes.py | 2 +- .../schemas/{ => compute}/vmware_nodes.py | 2 +- .../schemas/{ => compute}/vpcs_nodes.py | 2 +- gns3server/schemas/config.py | 6 +- .../controller/__init__.py} | 0 gns3server/schemas/{ => controller}/base.py | 0 .../schemas/{ => controller}/capabilities.py | 0 .../schemas/{ => controller}/computes.py | 0 .../schemas/{ => controller}/drawings.py | 0 gns3server/schemas/{ => controller}/gns3vm.py | 0 .../schemas/{ => controller}/iou_license.py | 0 .../{filters.py => controller/labels.py} | 18 ++- gns3server/schemas/{ => controller}/links.py | 2 +- gns3server/schemas/{ => controller}/nodes.py | 126 ++++++++---------- .../schemas/{ => controller}/projects.py | 0 .../schemas/{ => controller}/snapshots.py | 0 .../templates/__init__.py} | 6 +- .../templates}/cloud_templates.py | 9 +- .../templates}/docker_templates.py | 5 +- .../templates}/dynamips_templates.py | 5 +- .../templates}/ethernet_hub_templates.py | 5 +- .../templates}/ethernet_switch_templates.py | 4 +- .../templates}/iou_templates.py | 4 +- .../templates}/qemu_templates.py | 4 +- .../templates}/virtualbox_templates.py | 9 +- .../templates}/vmware_templates.py | 9 +- .../templates}/vpcs_templates.py | 4 +- gns3server/schemas/{ => controller}/tokens.py | 0 .../schemas/{ => controller}/topology.py | 0 gns3server/schemas/{ => controller}/users.py | 0 gns3server/services/authentication.py | 2 +- tests/conftest.py | 26 +++- 57 files changed, 332 insertions(+), 214 deletions(-) delete mode 100644 gns3server/handlers/api/controller/symbol_handler.py rename gns3server/{handlers/api/compute/ethernet_switch_handler.py => schemas/compute/__init__.py} (100%) rename gns3server/schemas/{ => compute}/atm_switch_nodes.py (97%) rename gns3server/schemas/{ => compute}/cloud_nodes.py (99%) rename gns3server/schemas/{ => compute}/docker_nodes.py (97%) rename gns3server/schemas/{ => compute}/dynamips_nodes.py (99%) rename gns3server/schemas/{ => compute}/ethernet_hub_nodes.py (97%) rename gns3server/schemas/{ => compute}/ethernet_switch_nodes.py (98%) rename gns3server/schemas/{ => compute}/frame_relay_switch_nodes.py (97%) rename gns3server/schemas/{ => compute}/iou_nodes.py (98%) rename gns3server/schemas/{ => compute}/nat_nodes.py (98%) rename gns3server/schemas/{ => compute}/nios.py (96%) rename gns3server/schemas/{ => compute}/qemu_nodes.py (99%) rename gns3server/schemas/{ => compute}/virtualbox_nodes.py (98%) rename gns3server/schemas/{ => compute}/vmware_nodes.py (98%) rename gns3server/schemas/{ => compute}/vpcs_nodes.py (97%) rename gns3server/{handlers/api/controller/link_handler.py => schemas/controller/__init__.py} (100%) rename gns3server/schemas/{ => controller}/base.py (100%) rename gns3server/schemas/{ => controller}/capabilities.py (100%) rename gns3server/schemas/{ => controller}/computes.py (100%) rename gns3server/schemas/{ => controller}/drawings.py (100%) rename gns3server/schemas/{ => controller}/gns3vm.py (100%) rename gns3server/schemas/{ => controller}/iou_license.py (100%) rename gns3server/schemas/{filters.py => controller/labels.py} (51%) rename gns3server/schemas/{ => controller}/links.py (98%) rename gns3server/schemas/{ => controller}/nodes.py (70%) rename gns3server/schemas/{ => controller}/projects.py (100%) rename gns3server/schemas/{ => controller}/snapshots.py (100%) rename gns3server/schemas/{templates.py => controller/templates/__init__.py} (94%) rename gns3server/schemas/{ => controller/templates}/cloud_templates.py (90%) rename gns3server/schemas/{ => controller/templates}/docker_templates.py (95%) rename gns3server/schemas/{ => controller/templates}/dynamips_templates.py (98%) rename gns3server/schemas/{ => controller/templates}/ethernet_hub_templates.py (92%) rename gns3server/schemas/{ => controller/templates}/ethernet_switch_templates.py (94%) rename gns3server/schemas/{ => controller/templates}/iou_templates.py (95%) rename gns3server/schemas/{ => controller/templates}/qemu_templates.py (98%) rename gns3server/schemas/{ => controller/templates}/virtualbox_templates.py (93%) rename gns3server/schemas/{ => controller/templates}/vmware_templates.py (93%) rename gns3server/schemas/{ => controller/templates}/vpcs_templates.py (92%) rename gns3server/schemas/{ => controller}/tokens.py (100%) rename gns3server/schemas/{ => controller}/topology.py (100%) rename gns3server/schemas/{ => controller}/users.py (100%) diff --git a/gns3server/api/routes/compute/__init__.py b/gns3server/api/routes/compute/__init__.py index b60dfb7c..57b7225d 100644 --- a/gns3server/api/routes/compute/__init__.py +++ b/gns3server/api/routes/compute/__init__.py @@ -138,35 +138,103 @@ async def http_exception_handler(request: Request, exc: StarletteHTTPException): ) -compute_api.include_router(capabilities.router, tags=["Capabilities"]) -compute_api.include_router(compute.router, tags=["Compute"]) -compute_api.include_router(notifications.router, tags=["Notifications"]) -compute_api.include_router(projects.router, tags=["Projects"]) -compute_api.include_router(images.router, tags=["Images"]) compute_api.include_router( - atm_switch_nodes.router, prefix="/projects/{project_id}/atm_switch/nodes", tags=["ATM switch"] + capabilities.router, + tags=["Capabilities"] ) -compute_api.include_router(cloud_nodes.router, prefix="/projects/{project_id}/cloud/nodes", tags=["Cloud nodes"]) -compute_api.include_router(docker_nodes.router, prefix="/projects/{project_id}/docker/nodes", tags=["Docker nodes"]) + compute_api.include_router( - dynamips_nodes.router, prefix="/projects/{project_id}/dynamips/nodes", tags=["Dynamips nodes"] + compute.router, + tags=["Compute"] +) + +compute_api.include_router( + notifications.router, + tags=["Notifications"] +) + +compute_api.include_router( + projects.router, + tags=["Projects"] +) + +compute_api.include_router( + images.router, + tags=["Images"] +) + +compute_api.include_router( + atm_switch_nodes.router, + prefix="/projects/{project_id}/atm_switch/nodes", + tags=["ATM switch"] ) compute_api.include_router( - ethernet_hub_nodes.router, prefix="/projects/{project_id}/ethernet_hub/nodes", tags=["Ethernet hub nodes"] + cloud_nodes.router, + prefix="/projects/{project_id}/cloud/nodes", + tags=["Cloud nodes"] ) + compute_api.include_router( - ethernet_switch_nodes.router, prefix="/projects/{project_id}/ethernet_switch/nodes", tags=["Ethernet switch nodes"] + docker_nodes.router, + prefix="/projects/{project_id}/docker/nodes", + tags=["Docker nodes"] ) + +compute_api.include_router( + dynamips_nodes.router, + prefix="/projects/{project_id}/dynamips/nodes", + tags=["Dynamips nodes"] +) + +compute_api.include_router( + ethernet_hub_nodes.router, + prefix="/projects/{project_id}/ethernet_hub/nodes", + tags=["Ethernet hub nodes"] +) + +compute_api.include_router( + ethernet_switch_nodes.router, + prefix="/projects/{project_id}/ethernet_switch/nodes", + tags=["Ethernet switch nodes"] +) + compute_api.include_router( frame_relay_switch_nodes.router, prefix="/projects/{project_id}/frame_relay_switch/nodes", - tags=["Frame Relay switch nodes"], + tags=["Frame Relay switch nodes"] ) -compute_api.include_router(iou_nodes.router, prefix="/projects/{project_id}/iou/nodes", tags=["IOU nodes"]) -compute_api.include_router(nat_nodes.router, prefix="/projects/{project_id}/nat/nodes", tags=["NAT nodes"]) -compute_api.include_router(qemu_nodes.router, prefix="/projects/{project_id}/qemu/nodes", tags=["Qemu nodes"]) + compute_api.include_router( - virtualbox_nodes.router, prefix="/projects/{project_id}/virtualbox/nodes", tags=["VirtualBox nodes"] + iou_nodes.router, + prefix="/projects/{project_id}/iou/nodes", + tags=["IOU nodes"]) + +compute_api.include_router( + nat_nodes.router, + prefix="/projects/{project_id}/nat/nodes", + tags=["NAT nodes"] +) + +compute_api.include_router( + qemu_nodes.router, + prefix="/projects/{project_id}/qemu/nodes", + tags=["Qemu nodes"] +) + +compute_api.include_router( + virtualbox_nodes.router, + prefix="/projects/{project_id}/virtualbox/nodes", + tags=["VirtualBox nodes"] +) + +compute_api.include_router( + vmware_nodes.router, + prefix="/projects/{project_id}/vmware/nodes", + tags=["VMware nodes"] +) + +compute_api.include_router( + vpcs_nodes.router, + prefix="/projects/{project_id}/vpcs/nodes", + tags=["VPCS nodes"] ) -compute_api.include_router(vmware_nodes.router, prefix="/projects/{project_id}/vmware/nodes", tags=["VMware nodes"]) -compute_api.include_router(vpcs_nodes.router, prefix="/projects/{project_id}/vpcs/nodes", tags=["VPCS nodes"]) diff --git a/gns3server/api/routes/compute/virtualbox_nodes.py b/gns3server/api/routes/compute/virtualbox_nodes.py index f558a02e..7fa51d9e 100644 --- a/gns3server/api/routes/compute/virtualbox_nodes.py +++ b/gns3server/api/routes/compute/virtualbox_nodes.py @@ -149,11 +149,6 @@ async def start_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)): Start a VirtualBox node. """ - if await node.check_hw_virtualization(): - pm = ProjectManager.instance() - if pm.check_hardware_virtualization(node) is False: - pass # FIXME: check this - # raise ComputeError("Cannot start VM with hardware acceleration (KVM/HAX) enabled because hardware virtualization (VT-x/AMD-V) is already used by another software like VMware or VirtualBox") await node.start() diff --git a/gns3server/api/routes/compute/vmware_nodes.py b/gns3server/api/routes/compute/vmware_nodes.py index 1d857f1e..2617f791 100644 --- a/gns3server/api/routes/compute/vmware_nodes.py +++ b/gns3server/api/routes/compute/vmware_nodes.py @@ -117,11 +117,6 @@ async def start_vmware_node(node: VMwareVM = Depends(dep_node)): Start a VMware node. """ - if node.check_hw_virtualization(): - pm = ProjectManager.instance() - if pm.check_hardware_virtualization(node) is False: - pass # FIXME: check this - # raise ComputeError("Cannot start VM with hardware acceleration (KVM/HAX) enabled because hardware virtualization (VT-x/AMD-V) is already used by another software like VMware or VirtualBox") await node.start() diff --git a/gns3server/api/routes/controller/nodes.py b/gns3server/api/routes/controller/nodes.py index e14dd334..93e2a28b 100644 --- a/gns3server/api/routes/controller/nodes.py +++ b/gns3server/api/routes/controller/nodes.py @@ -108,7 +108,7 @@ async def dep_node(node_id: UUID, project: Project = Depends(dep_project)): 409: {"model": schemas.ErrorMessage, "description": "Could not create node"}, }, ) -async def create_node(node_data: schemas.Node, project: Project = Depends(dep_project)): +async def create_node(node_data: schemas.NodeCreate, project: Project = Depends(dep_project)): """ Create a new node. """ diff --git a/gns3server/compute/project_manager.py b/gns3server/compute/project_manager.py index b657007c..d1f77cb8 100644 --- a/gns3server/compute/project_manager.py +++ b/gns3server/compute/project_manager.py @@ -120,18 +120,3 @@ class ProjectManager: if project_id not in self._projects: raise ComputeNotFoundError(f"Project ID {project_id} doesn't exist") del self._projects[project_id] - - def check_hardware_virtualization(self, source_node): - """ - Checks if hardware virtualization can be used. - - :returns: boolean - """ - - for project in self._projects.values(): - for node in project.nodes: - if node == source_node: - continue - if node.hw_virtualization and node.__class__.__name__ != source_node.__class__.__name__: - return False - return True diff --git a/gns3server/compute/qemu/qemu_vm.py b/gns3server/compute/qemu/qemu_vm.py index 7e618210..87f825bb 100644 --- a/gns3server/compute/qemu/qemu_vm.py +++ b/gns3server/compute/qemu/qemu_vm.py @@ -47,7 +47,7 @@ from ...utils.asyncio import monitor_process from ...utils.images import md5sum from ...utils import macaddress_to_int, int_to_macaddress -from gns3server.schemas.qemu_nodes import Qemu, QemuPlatform +from gns3server.schemas.compute.qemu_nodes import Qemu, QemuPlatform import logging diff --git a/gns3server/controller/__init__.py b/gns3server/controller/__init__.py index 46b3c966..f7976b90 100644 --- a/gns3server/controller/__init__.py +++ b/gns3server/controller/__init__.py @@ -353,6 +353,7 @@ class Controller: self._computes[compute.id] = compute # self.save() if connect: + # call compute.connect() later to give time to the controller to be fully started asyncio.get_event_loop().call_later(1, lambda: asyncio.ensure_future(compute.connect())) self.notification.controller_emit("compute.created", compute.__json__()) return compute diff --git a/gns3server/controller/drawing.py b/gns3server/controller/drawing.py index a1a89809..83e174da 100644 --- a/gns3server/controller/drawing.py +++ b/gns3server/controller/drawing.py @@ -203,6 +203,7 @@ class Drawing: """ :param topology_dump: Filter to keep only properties require for saving on disk """ + if topology_dump: return { "drawing_id": self._id, diff --git a/gns3server/controller/topology.py b/gns3server/controller/topology.py index 687f2b29..fa2a54d6 100644 --- a/gns3server/controller/topology.py +++ b/gns3server/controller/topology.py @@ -31,8 +31,8 @@ from ..utils.qt import qt_font_to_style from ..compute.dynamips import PLATFORMS_DEFAULT_RAM from .controller_error import ControllerError -from gns3server.schemas.topology import Topology -from gns3server.schemas.dynamips_nodes import DynamipsCreate +from gns3server.schemas.controller.topology import Topology +from gns3server.schemas.compute.dynamips_nodes import DynamipsCreate import logging diff --git a/gns3server/core/tasks.py b/gns3server/core/tasks.py index 4623c487..5080c7d9 100644 --- a/gns3server/core/tasks.py +++ b/gns3server/core/tasks.py @@ -33,6 +33,10 @@ log = logging.getLogger(__name__) def create_startup_handler(app: FastAPI) -> Callable: + """ + Tasks to be performed when the server is starting. + """ + async def start_app() -> None: loop = asyncio.get_event_loop() logger = logging.getLogger("asyncio") @@ -77,6 +81,10 @@ def create_startup_handler(app: FastAPI) -> Callable: def create_shutdown_handler(app: FastAPI) -> Callable: + """ + Tasks to be performed when the server is shutdown. + """ + async def shutdown_handler() -> None: await HTTPClient.close_session() await Controller.instance().stop() diff --git a/gns3server/handlers/api/controller/symbol_handler.py b/gns3server/handlers/api/controller/symbol_handler.py deleted file mode 100644 index e69de29b..00000000 diff --git a/gns3server/schemas/__init__.py b/gns3server/schemas/__init__.py index 022d428e..8ff9d6f0 100644 --- a/gns3server/schemas/__init__.py +++ b/gns3server/schemas/__init__.py @@ -14,46 +14,36 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +# General schemas from .config import ServerConfig -from .iou_license import IOULicense -from .links import Link from .common import ErrorMessage from .version import Version -from .computes import ComputeCreate, ComputeUpdate, AutoIdlePC, Compute -from .templates import TemplateCreate, TemplateUpdate, TemplateUsage, Template -from .drawings import Drawing -from .gns3vm import GNS3VM -from .nodes import NodeUpdate, NodeDuplicate, NodeCapture, Node -from .projects import ProjectCreate, ProjectUpdate, ProjectDuplicate, Project, ProjectFile -from .users import UserCreate, UserUpdate, User -from .tokens import Token -from .snapshots import SnapshotCreate, Snapshot -from .capabilities import Capabilities -from .nios import UDPNIO, TAPNIO, EthernetNIO -from .atm_switch_nodes import ATMSwitchCreate, ATMSwitchUpdate, ATMSwitch -from .cloud_nodes import CloudCreate, CloudUpdate, Cloud -from .docker_nodes import DockerCreate, DockerUpdate, Docker -from .dynamips_nodes import DynamipsCreate, DynamipsUpdate, Dynamips -from .ethernet_hub_nodes import EthernetHubCreate, EthernetHubUpdate, EthernetHub -from .ethernet_switch_nodes import EthernetSwitchCreate, EthernetSwitchUpdate, EthernetSwitch -from .frame_relay_switch_nodes import FrameRelaySwitchCreate, FrameRelaySwitchUpdate, FrameRelaySwitch -from .qemu_nodes import QemuCreate, QemuUpdate, QemuImageCreate, QemuImageUpdate, QemuDiskResize, Qemu -from .iou_nodes import IOUCreate, IOUUpdate, IOUStart, IOU -from .nat_nodes import NATCreate, NATUpdate, NAT -from .vpcs_nodes import VPCSCreate, VPCSUpdate, VPCS -from .vmware_nodes import VMwareCreate, VMwareUpdate, VMware -from .virtualbox_nodes import VirtualBoxCreate, VirtualBoxUpdate, VirtualBox -from .vpcs_templates import VPCSTemplate -from .cloud_templates import CloudTemplate -from .iou_templates import IOUTemplate -from .docker_templates import DockerTemplate -from .ethernet_hub_templates import EthernetHubTemplate -from .ethernet_switch_templates import EthernetSwitchTemplate -from .virtualbox_templates import VirtualBoxTemplate -from .vmware_templates import VMwareTemplate -from .qemu_templates import QemuTemplate -from .dynamips_templates import ( +# Controller schemas +from .controller.links import Link +from .controller.computes import ComputeCreate, ComputeUpdate, AutoIdlePC, Compute +from .controller.templates import TemplateCreate, TemplateUpdate, TemplateUsage, Template +from .controller.drawings import Drawing +from .controller.gns3vm import GNS3VM +from .controller.nodes import NodeCreate, NodeUpdate, NodeDuplicate, NodeCapture, Node +from .controller.projects import ProjectCreate, ProjectUpdate, ProjectDuplicate, Project, ProjectFile +from .controller.users import UserCreate, UserUpdate, User +from .controller.tokens import Token +from .controller.snapshots import SnapshotCreate, Snapshot +from .controller.iou_license import IOULicense +from .controller.capabilities import Capabilities + +# Controller template schemas +from .controller.templates.vpcs_templates import VPCSTemplate +from .controller.templates.cloud_templates import CloudTemplate +from .controller.templates.iou_templates import IOUTemplate +from .controller.templates.docker_templates import DockerTemplate +from .controller.templates.ethernet_hub_templates import EthernetHubTemplate +from .controller.templates.ethernet_switch_templates import EthernetSwitchTemplate +from .controller.templates.virtualbox_templates import VirtualBoxTemplate +from .controller.templates.vmware_templates import VMwareTemplate +from .controller.templates.qemu_templates import QemuTemplate +from .controller.templates.dynamips_templates import ( DynamipsTemplate, C1700DynamipsTemplate, C2600DynamipsTemplate, @@ -63,3 +53,19 @@ from .dynamips_templates import ( C3745DynamipsTemplate, C7200DynamipsTemplate, ) + +# Compute schemas +from .compute.nios import UDPNIO, TAPNIO, EthernetNIO +from .compute.atm_switch_nodes import ATMSwitchCreate, ATMSwitchUpdate, ATMSwitch +from .compute.cloud_nodes import CloudCreate, CloudUpdate, Cloud +from .compute.docker_nodes import DockerCreate, DockerUpdate, Docker +from .compute.dynamips_nodes import DynamipsCreate, DynamipsUpdate, Dynamips +from .compute.ethernet_hub_nodes import EthernetHubCreate, EthernetHubUpdate, EthernetHub +from .compute.ethernet_switch_nodes import EthernetSwitchCreate, EthernetSwitchUpdate, EthernetSwitch +from .compute.frame_relay_switch_nodes import FrameRelaySwitchCreate, FrameRelaySwitchUpdate, FrameRelaySwitch +from .compute.qemu_nodes import QemuCreate, QemuUpdate, QemuImageCreate, QemuImageUpdate, QemuDiskResize, Qemu +from .compute.iou_nodes import IOUCreate, IOUUpdate, IOUStart, IOU +from .compute.nat_nodes import NATCreate, NATUpdate, NAT +from .compute.vpcs_nodes import VPCSCreate, VPCSUpdate, VPCS +from .compute.vmware_nodes import VMwareCreate, VMwareUpdate, VMware +from .compute.virtualbox_nodes import VirtualBoxCreate, VirtualBoxUpdate, VirtualBox diff --git a/gns3server/schemas/common.py b/gns3server/schemas/common.py index 85ae01d3..e524384f 100644 --- a/gns3server/schemas/common.py +++ b/gns3server/schemas/common.py @@ -16,6 +16,7 @@ from pydantic import BaseModel, Field from typing import Optional, Union +from enum import Enum class ErrorMessage(BaseModel): @@ -26,14 +27,45 @@ class ErrorMessage(BaseModel): message: str -class Label(BaseModel): +class NodeStatus(str, Enum): """ - Label data. - + Supported node statuses. """ - text: str - style: Optional[Union[str, None]] = Field(None, description="SVG style attribute. Apply default style if null") - x: Optional[Union[int, None]] = Field(None, description="Relative X position of the label. Center it if null") - y: Optional[int] = Field(None, description="Relative Y position of the label") - rotation: Optional[int] = Field(None, ge=-359, le=360, description="Rotation of the label") + stopped = "stopped" + started = "started" + suspended = "suspended" + + +class CustomAdapter(BaseModel): + """ + Custom adapter data. + """ + + adapter_number: int + port_name: Optional[str] = None + adapter_type: Optional[str] = None + mac_address: Optional[str] = Field(None, regex="^([0-9a-fA-F]{2}[:]){5}([0-9a-fA-F]{2})$") + + +class ConsoleType(str, Enum): + """ + Supported console types. + """ + + vnc = "vnc" + telnet = "telnet" + http = "http" + https = "https" + spice = "spice" + spice_agent = "spice+agent" + none = "none" + + +class AuxType(str, Enum): + """ + Supported auxiliary console types. + """ + + telnet = "telnet" + none = "none" diff --git a/gns3server/handlers/api/compute/ethernet_switch_handler.py b/gns3server/schemas/compute/__init__.py similarity index 100% rename from gns3server/handlers/api/compute/ethernet_switch_handler.py rename to gns3server/schemas/compute/__init__.py diff --git a/gns3server/schemas/atm_switch_nodes.py b/gns3server/schemas/compute/atm_switch_nodes.py similarity index 97% rename from gns3server/schemas/atm_switch_nodes.py rename to gns3server/schemas/compute/atm_switch_nodes.py index 6f9e55b1..836fbfa3 100644 --- a/gns3server/schemas/atm_switch_nodes.py +++ b/gns3server/schemas/compute/atm_switch_nodes.py @@ -18,7 +18,7 @@ from pydantic import BaseModel from typing import Optional from uuid import UUID -from .nodes import NodeStatus +from ..common import NodeStatus class ATMSwitchBase(BaseModel): diff --git a/gns3server/schemas/cloud_nodes.py b/gns3server/schemas/compute/cloud_nodes.py similarity index 99% rename from gns3server/schemas/cloud_nodes.py rename to gns3server/schemas/compute/cloud_nodes.py index 7ef0fe02..92d8aadd 100644 --- a/gns3server/schemas/cloud_nodes.py +++ b/gns3server/schemas/compute/cloud_nodes.py @@ -19,7 +19,7 @@ from typing import Optional, Union, List from enum import Enum from uuid import UUID -from .nodes import NodeStatus +from ..common import NodeStatus class HostInterfaceType(Enum): diff --git a/gns3server/schemas/docker_nodes.py b/gns3server/schemas/compute/docker_nodes.py similarity index 97% rename from gns3server/schemas/docker_nodes.py rename to gns3server/schemas/compute/docker_nodes.py index 4f033c85..7129aaae 100644 --- a/gns3server/schemas/docker_nodes.py +++ b/gns3server/schemas/compute/docker_nodes.py @@ -18,7 +18,7 @@ from pydantic import BaseModel, Field from typing import Optional, List from uuid import UUID -from .nodes import CustomAdapter, ConsoleType, AuxType, NodeStatus +from ..common import NodeStatus, CustomAdapter, ConsoleType, AuxType class DockerBase(BaseModel): diff --git a/gns3server/schemas/dynamips_nodes.py b/gns3server/schemas/compute/dynamips_nodes.py similarity index 99% rename from gns3server/schemas/dynamips_nodes.py rename to gns3server/schemas/compute/dynamips_nodes.py index 1289c013..64bc07c2 100644 --- a/gns3server/schemas/dynamips_nodes.py +++ b/gns3server/schemas/compute/dynamips_nodes.py @@ -20,7 +20,7 @@ from typing import Optional, List from enum import Enum from uuid import UUID -from .nodes import NodeStatus +from ..common import NodeStatus class DynamipsPlatform(str, Enum): diff --git a/gns3server/schemas/ethernet_hub_nodes.py b/gns3server/schemas/compute/ethernet_hub_nodes.py similarity index 97% rename from gns3server/schemas/ethernet_hub_nodes.py rename to gns3server/schemas/compute/ethernet_hub_nodes.py index e559a6d4..803be8f3 100644 --- a/gns3server/schemas/ethernet_hub_nodes.py +++ b/gns3server/schemas/compute/ethernet_hub_nodes.py @@ -18,7 +18,7 @@ from pydantic import BaseModel from typing import Optional, List from uuid import UUID -from .nodes import NodeStatus +from ..common import NodeStatus class EthernetHubPort(BaseModel): diff --git a/gns3server/schemas/ethernet_switch_nodes.py b/gns3server/schemas/compute/ethernet_switch_nodes.py similarity index 98% rename from gns3server/schemas/ethernet_switch_nodes.py rename to gns3server/schemas/compute/ethernet_switch_nodes.py index 9b1050ec..0edd7f1b 100644 --- a/gns3server/schemas/ethernet_switch_nodes.py +++ b/gns3server/schemas/compute/ethernet_switch_nodes.py @@ -19,7 +19,7 @@ from typing import Optional, List from uuid import UUID from enum import Enum -from .nodes import NodeStatus +from ..common import NodeStatus class EthernetSwitchPortType(Enum): diff --git a/gns3server/schemas/frame_relay_switch_nodes.py b/gns3server/schemas/compute/frame_relay_switch_nodes.py similarity index 97% rename from gns3server/schemas/frame_relay_switch_nodes.py rename to gns3server/schemas/compute/frame_relay_switch_nodes.py index fb6e580c..6b759eb4 100644 --- a/gns3server/schemas/frame_relay_switch_nodes.py +++ b/gns3server/schemas/compute/frame_relay_switch_nodes.py @@ -18,7 +18,7 @@ from pydantic import BaseModel from typing import Optional from uuid import UUID -from .nodes import NodeStatus +from ..common import NodeStatus class FrameRelaySwitchBase(BaseModel): diff --git a/gns3server/schemas/iou_nodes.py b/gns3server/schemas/compute/iou_nodes.py similarity index 98% rename from gns3server/schemas/iou_nodes.py rename to gns3server/schemas/compute/iou_nodes.py index f237fdd9..aa09323b 100644 --- a/gns3server/schemas/iou_nodes.py +++ b/gns3server/schemas/compute/iou_nodes.py @@ -18,7 +18,7 @@ from pydantic import BaseModel, Field from typing import Optional from uuid import UUID -from .nodes import ConsoleType, NodeStatus +from ..common import NodeStatus, ConsoleType class IOUBase(BaseModel): diff --git a/gns3server/schemas/nat_nodes.py b/gns3server/schemas/compute/nat_nodes.py similarity index 98% rename from gns3server/schemas/nat_nodes.py rename to gns3server/schemas/compute/nat_nodes.py index d2edabf2..755ac664 100644 --- a/gns3server/schemas/nat_nodes.py +++ b/gns3server/schemas/compute/nat_nodes.py @@ -19,7 +19,7 @@ from typing import Optional, Union, List from enum import Enum from uuid import UUID -from .nodes import NodeStatus +from ..common import NodeStatus class HostInterfaceType(Enum): diff --git a/gns3server/schemas/nios.py b/gns3server/schemas/compute/nios.py similarity index 96% rename from gns3server/schemas/nios.py rename to gns3server/schemas/compute/nios.py index 10514a3b..75ab4b5b 100644 --- a/gns3server/schemas/nios.py +++ b/gns3server/schemas/compute/nios.py @@ -16,9 +16,8 @@ from pydantic import BaseModel, Field -from typing import Optional, Union, Generic +from typing import Optional from enum import Enum -from uuid import UUID class UDPNIOType(Enum): diff --git a/gns3server/schemas/qemu_nodes.py b/gns3server/schemas/compute/qemu_nodes.py similarity index 99% rename from gns3server/schemas/qemu_nodes.py rename to gns3server/schemas/compute/qemu_nodes.py index 6433f5d9..de464fef 100644 --- a/gns3server/schemas/qemu_nodes.py +++ b/gns3server/schemas/compute/qemu_nodes.py @@ -19,7 +19,7 @@ from typing import Optional, List from enum import Enum from uuid import UUID -from .nodes import CustomAdapter, NodeStatus +from ..common import NodeStatus, CustomAdapter class QemuPlatform(str, Enum): diff --git a/gns3server/schemas/virtualbox_nodes.py b/gns3server/schemas/compute/virtualbox_nodes.py similarity index 98% rename from gns3server/schemas/virtualbox_nodes.py rename to gns3server/schemas/compute/virtualbox_nodes.py index 72fc1144..53f93975 100644 --- a/gns3server/schemas/virtualbox_nodes.py +++ b/gns3server/schemas/compute/virtualbox_nodes.py @@ -19,7 +19,7 @@ from typing import Optional, List from enum import Enum from uuid import UUID -from .nodes import NodeStatus, CustomAdapter +from ..common import NodeStatus, CustomAdapter class VirtualBoxConsoleType(str, Enum): diff --git a/gns3server/schemas/vmware_nodes.py b/gns3server/schemas/compute/vmware_nodes.py similarity index 98% rename from gns3server/schemas/vmware_nodes.py rename to gns3server/schemas/compute/vmware_nodes.py index 4a3f2fc9..a2fb26f2 100644 --- a/gns3server/schemas/vmware_nodes.py +++ b/gns3server/schemas/compute/vmware_nodes.py @@ -19,7 +19,7 @@ from typing import Optional, List from enum import Enum from uuid import UUID -from .nodes import NodeStatus, CustomAdapter +from ..common import NodeStatus, CustomAdapter class VMwareConsoleType(str, Enum): diff --git a/gns3server/schemas/vpcs_nodes.py b/gns3server/schemas/compute/vpcs_nodes.py similarity index 97% rename from gns3server/schemas/vpcs_nodes.py rename to gns3server/schemas/compute/vpcs_nodes.py index 324be1a7..6373182c 100644 --- a/gns3server/schemas/vpcs_nodes.py +++ b/gns3server/schemas/compute/vpcs_nodes.py @@ -19,7 +19,7 @@ from typing import Optional from enum import Enum from uuid import UUID -from .nodes import NodeStatus, CustomAdapter +from ..common import NodeStatus class ConsoleType(str, Enum): diff --git a/gns3server/schemas/config.py b/gns3server/schemas/config.py index c30dada7..d692cf9e 100644 --- a/gns3server/schemas/config.py +++ b/gns3server/schemas/config.py @@ -15,7 +15,7 @@ # along with this program. If not, see . from enum import Enum -from pydantic import BaseModel, Field, SecretStr, FilePath, validator +from pydantic import BaseModel, Field, SecretStr, FilePath, DirectoryPath, validator from typing import List @@ -113,7 +113,7 @@ class ServerSettings(BaseModel): protocol: ServerProtocol = ServerProtocol.http host: str = "0.0.0.0" port: int = Field(3080, gt=0, le=65535) - secrets_dir: str = None + secrets_dir: DirectoryPath = None certfile: FilePath = None certkey: FilePath = None enable_ssl: bool = False @@ -167,7 +167,7 @@ class ServerSettings(BaseModel): if v is True: if "user" not in values or not values["user"]: - raise ValueError("HTTP authentication is enabled but no username is configured") + raise ValueError("HTTP authentication is enabled but user is not configured") return v @validator("enable_ssl") diff --git a/gns3server/handlers/api/controller/link_handler.py b/gns3server/schemas/controller/__init__.py similarity index 100% rename from gns3server/handlers/api/controller/link_handler.py rename to gns3server/schemas/controller/__init__.py diff --git a/gns3server/schemas/base.py b/gns3server/schemas/controller/base.py similarity index 100% rename from gns3server/schemas/base.py rename to gns3server/schemas/controller/base.py diff --git a/gns3server/schemas/capabilities.py b/gns3server/schemas/controller/capabilities.py similarity index 100% rename from gns3server/schemas/capabilities.py rename to gns3server/schemas/controller/capabilities.py diff --git a/gns3server/schemas/computes.py b/gns3server/schemas/controller/computes.py similarity index 100% rename from gns3server/schemas/computes.py rename to gns3server/schemas/controller/computes.py diff --git a/gns3server/schemas/drawings.py b/gns3server/schemas/controller/drawings.py similarity index 100% rename from gns3server/schemas/drawings.py rename to gns3server/schemas/controller/drawings.py diff --git a/gns3server/schemas/gns3vm.py b/gns3server/schemas/controller/gns3vm.py similarity index 100% rename from gns3server/schemas/gns3vm.py rename to gns3server/schemas/controller/gns3vm.py diff --git a/gns3server/schemas/iou_license.py b/gns3server/schemas/controller/iou_license.py similarity index 100% rename from gns3server/schemas/iou_license.py rename to gns3server/schemas/controller/iou_license.py diff --git a/gns3server/schemas/filters.py b/gns3server/schemas/controller/labels.py similarity index 51% rename from gns3server/schemas/filters.py rename to gns3server/schemas/controller/labels.py index 52bafdd0..2eb77026 100644 --- a/gns3server/schemas/filters.py +++ b/gns3server/schemas/controller/labels.py @@ -1,6 +1,5 @@ -#!/usr/bin/env python # -# Copyright (C) 2020 GNS3 Technologies Inc. +# Copyright (C) 2021 GNS3 Technologies Inc. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -14,3 +13,18 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . + +from pydantic import BaseModel, Field +from typing import Optional, Union + + +class Label(BaseModel): + """ + Label data. + """ + + text: str + style: Optional[Union[str, None]] = Field(None, description="SVG style attribute. Apply default style if null") + x: Optional[Union[int, None]] = Field(None, description="Relative X position of the label. Center it if null") + y: Optional[int] = Field(None, description="Relative Y position of the label") + rotation: Optional[int] = Field(None, ge=-359, le=360, description="Rotation of the label") diff --git a/gns3server/schemas/links.py b/gns3server/schemas/controller/links.py similarity index 98% rename from gns3server/schemas/links.py rename to gns3server/schemas/controller/links.py index 98efba28..3a37ccbc 100644 --- a/gns3server/schemas/links.py +++ b/gns3server/schemas/controller/links.py @@ -19,7 +19,7 @@ from typing import List, Optional from enum import Enum from uuid import UUID -from .common import Label +from .labels import Label class LinkNode(BaseModel): diff --git a/gns3server/schemas/nodes.py b/gns3server/schemas/controller/nodes.py similarity index 70% rename from gns3server/schemas/nodes.py rename to gns3server/schemas/controller/nodes.py index 3a6f2802..1e59fef1 100644 --- a/gns3server/schemas/nodes.py +++ b/gns3server/schemas/controller/nodes.py @@ -14,12 +14,13 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, validator from typing import List, Optional, Union from enum import Enum -from uuid import UUID +from uuid import UUID, uuid4 -from .common import Label +from .labels import Label +from ..common import ConsoleType, NodeStatus, CustomAdapter class NodeType(str, Enum): @@ -74,39 +75,6 @@ class DataLinkType(str, Enum): ppp = "DLT_PPP_SERIAL" -class ConsoleType(str, Enum): - """ - Supported console types. - """ - - vnc = "vnc" - telnet = "telnet" - http = "http" - https = "https" - spice = "spice" - spice_agent = "spice+agent" - none = "none" - - -class AuxType(str, Enum): - """ - Supported auxiliary console types. - """ - - telnet = "telnet" - none = "none" - - -class NodeStatus(str, Enum): - """ - Supported node statuses. - """ - - stopped = "stopped" - started = "started" - suspended = "suspended" - - class NodeCapture(BaseModel): """ Node capture data. @@ -116,17 +84,6 @@ class NodeCapture(BaseModel): data_link_type: Optional[DataLinkType] = None -class CustomAdapter(BaseModel): - """ - Custom adapter data. - """ - - adapter_number: int - port_name: Optional[str] = None - adapter_type: Optional[str] = None - mac_address: Optional[str] = Field(None, regex="^([0-9a-fA-F]{2}[:]){5}([0-9a-fA-F]{2})$") - - class NodePort(BaseModel): """ Node port data. @@ -142,7 +99,7 @@ class NodePort(BaseModel): mac_address: Union[str, None] = Field(None, regex="^([0-9a-fA-F]{2}[:]){5}([0-9a-fA-F]{2})$") -class Node(BaseModel): +class NodeBase(BaseModel): """ Node data. """ @@ -150,44 +107,56 @@ class Node(BaseModel): compute_id: Union[UUID, str] name: str node_type: NodeType - project_id: Optional[UUID] = None + node_id: Optional[UUID] = None - template_id: Optional[UUID] = Field( - None, description="Template UUID from which the node has been created. Read only" - ) - node_directory: Optional[str] = Field(None, description="Working directory of the node. Read only") - command_line: Optional[str] = Field(None, description="Command line use to start the node") + console: Optional[int] = Field(None, gt=0, le=65535, description="Console TCP port") - console_host: Optional[str] = Field( - None, - description="Console host. Warning if the host is 0.0.0.0 or :: (listen on all interfaces) you need to use the same address you use to connect to the controller", - ) + console_type: Optional[ConsoleType] = None console_auto_start: Optional[bool] = Field( - None, description="Automatically start the console when the node has started" + False, description="Automatically start the console when the node has started" ) aux: Optional[int] = Field(None, gt=0, le=65535, description="Auxiliary console TCP port") aux_type: Optional[ConsoleType] - properties: Optional[dict] = Field(None, description="Properties specific to an emulator") - status: Optional[NodeStatus] = None + properties: Optional[dict] = Field(default_factory=dict, description="Properties specific to an emulator") + label: Optional[Label] = None symbol: Optional[str] = None - width: Optional[int] = Field(None, description="Width of the node (Read only)") - height: Optional[int] = Field(None, description="Height of the node (Read only)") - x: Optional[int] = None - y: Optional[int] = None - z: Optional[int] = None - locked: Optional[bool] = Field(None, description="Whether the element locked or not") + + x: Optional[int] = 0 + y: Optional[int] = 0 + z: Optional[int] = 1 + locked: Optional[bool] = Field(False, description="Whether the element locked or not") port_name_format: Optional[str] = Field( - None, description="Formatting for port name {0} will be replace by port number" + None, descript_port_name_formation="Formatting for port name {0} will be replace by port number" ) port_segment_size: Optional[int] = Field(None, description="Size of the port segment") first_port_name: Optional[str] = Field(None, description="Name of the first port") custom_adapters: Optional[List[CustomAdapter]] = None - ports: Optional[List[NodePort]] = Field(None, description="List of node ports (read only)") + + @validator("port_name_format", pre=True, always=True) + def default_port_name_format(cls, v, values): + if v is None: + if "node_type" in values and values["node_type"] == NodeType.iou: + return "Ethernet{segment0}/{port0}" + return "Ethernet{0}" + return v + + @validator("port_segment_size", pre=True, always=True) + def default_port_segment_size(cls, v, values): + if v is None: + if "node_type" in values and values["node_type"] == NodeType.iou: + return 4 + return 0 + return v -class NodeUpdate(Node): +class NodeCreate(NodeBase): + + node_id: UUID = Field(default_factory=uuid4) + + +class NodeUpdate(NodeBase): """ Data to update a node. """ @@ -197,6 +166,23 @@ class NodeUpdate(Node): node_type: Optional[NodeType] = None +class Node(NodeBase): + + template_id: Optional[UUID] = Field(None, + description="Template UUID from which the node has been created. Read only") + project_id: Optional[UUID] = None + node_directory: Optional[str] = Field(None, description="Working directory of the node. Read only") + status: Optional[NodeStatus] = Field(None, description="Node status. Read only") + command_line: Optional[str] = Field(None, description="Command line use to start the node. Read only") + width: Optional[int] = Field(None, description="Width of the node. Read only") + height: Optional[int] = Field(None, description="Height of the node. Read only") + ports: Optional[List[NodePort]] = Field(None, description="List of node ports. Read only") + console_host: Optional[str] = Field( + None, + description="Console host. Warning if the host is 0.0.0.0 or :: (listen on all interfaces) you need to use the same address you use to connect to the controller", + ) + + class NodeDuplicate(BaseModel): """ Data to duplicate a node. diff --git a/gns3server/schemas/projects.py b/gns3server/schemas/controller/projects.py similarity index 100% rename from gns3server/schemas/projects.py rename to gns3server/schemas/controller/projects.py diff --git a/gns3server/schemas/snapshots.py b/gns3server/schemas/controller/snapshots.py similarity index 100% rename from gns3server/schemas/snapshots.py rename to gns3server/schemas/controller/snapshots.py diff --git a/gns3server/schemas/templates.py b/gns3server/schemas/controller/templates/__init__.py similarity index 94% rename from gns3server/schemas/templates.py rename to gns3server/schemas/controller/templates/__init__.py index f9de0b03..74866b2e 100644 --- a/gns3server/schemas/templates.py +++ b/gns3server/schemas/controller/templates/__init__.py @@ -1,5 +1,5 @@ # -# Copyright (C) 2020 GNS3 Technologies Inc. +# Copyright (C) 2021 GNS3 Technologies Inc. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -19,8 +19,8 @@ from typing import Optional, Union from enum import Enum from uuid import UUID -from .nodes import NodeType -from .base import DateTimeModelMixin +from ..nodes import NodeType +from ..base import DateTimeModelMixin class Category(str, Enum): diff --git a/gns3server/schemas/cloud_templates.py b/gns3server/schemas/controller/templates/cloud_templates.py similarity index 90% rename from gns3server/schemas/cloud_templates.py rename to gns3server/schemas/controller/templates/cloud_templates.py index 6ae199f1..37fd1942 100644 --- a/gns3server/schemas/cloud_templates.py +++ b/gns3server/schemas/controller/templates/cloud_templates.py @@ -15,8 +15,13 @@ # along with this program. If not, see . -from .templates import Category, TemplateBase -from .cloud_nodes import EthernetPort, TAPPort, UDPPort, CloudConsoleType +from . import Category, TemplateBase +from gns3server.schemas.compute.cloud_nodes import ( + EthernetPort, + TAPPort, + UDPPort, + CloudConsoleType +) from pydantic import Field from typing import Optional, Union, List diff --git a/gns3server/schemas/docker_templates.py b/gns3server/schemas/controller/templates/docker_templates.py similarity index 95% rename from gns3server/schemas/docker_templates.py rename to gns3server/schemas/controller/templates/docker_templates.py index 48f16e1a..ceb05040 100644 --- a/gns3server/schemas/docker_templates.py +++ b/gns3server/schemas/controller/templates/docker_templates.py @@ -15,9 +15,8 @@ # along with this program. If not, see . -from .templates import Category, TemplateBase -from .nodes import CustomAdapter -from .docker_nodes import ConsoleType, AuxType +from . import Category, TemplateBase +from ...common import ConsoleType, AuxType, CustomAdapter from pydantic import Field from typing import Optional, List diff --git a/gns3server/schemas/dynamips_templates.py b/gns3server/schemas/controller/templates/dynamips_templates.py similarity index 98% rename from gns3server/schemas/dynamips_templates.py rename to gns3server/schemas/controller/templates/dynamips_templates.py index 308a90f5..9598505e 100644 --- a/gns3server/schemas/dynamips_templates.py +++ b/gns3server/schemas/controller/templates/dynamips_templates.py @@ -14,8 +14,9 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from .templates import Category, TemplateBase -from .dynamips_nodes import ( +from . import Category, TemplateBase + +from gns3server.schemas.compute.dynamips_nodes import ( DynamipsConsoleType, DynamipsPlatform, DynamipsAdapters, diff --git a/gns3server/schemas/ethernet_hub_templates.py b/gns3server/schemas/controller/templates/ethernet_hub_templates.py similarity index 92% rename from gns3server/schemas/ethernet_hub_templates.py rename to gns3server/schemas/controller/templates/ethernet_hub_templates.py index 947d41db..7b61cd01 100644 --- a/gns3server/schemas/ethernet_hub_templates.py +++ b/gns3server/schemas/controller/templates/ethernet_hub_templates.py @@ -14,9 +14,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . - -from .templates import Category, TemplateBase -from .ethernet_hub_nodes import EthernetHubPort +from . import Category, TemplateBase +from gns3server.schemas.compute.ethernet_hub_nodes import EthernetHubPort from pydantic import Field from typing import Optional, List diff --git a/gns3server/schemas/ethernet_switch_templates.py b/gns3server/schemas/controller/templates/ethernet_switch_templates.py similarity index 94% rename from gns3server/schemas/ethernet_switch_templates.py rename to gns3server/schemas/controller/templates/ethernet_switch_templates.py index 51373e31..2e42aaa2 100644 --- a/gns3server/schemas/ethernet_switch_templates.py +++ b/gns3server/schemas/controller/templates/ethernet_switch_templates.py @@ -15,8 +15,8 @@ # along with this program. If not, see . -from .templates import Category, TemplateBase -from .ethernet_switch_nodes import EthernetSwitchPort +from . import Category, TemplateBase +from gns3server.schemas.compute.ethernet_switch_nodes import EthernetSwitchPort from pydantic import Field from typing import Optional, List diff --git a/gns3server/schemas/iou_templates.py b/gns3server/schemas/controller/templates/iou_templates.py similarity index 95% rename from gns3server/schemas/iou_templates.py rename to gns3server/schemas/controller/templates/iou_templates.py index 0c7100b9..f7bba8d4 100644 --- a/gns3server/schemas/iou_templates.py +++ b/gns3server/schemas/controller/templates/iou_templates.py @@ -15,8 +15,8 @@ # along with this program. If not, see . -from .templates import Category, TemplateBase -from .iou_nodes import ConsoleType +from . import Category, TemplateBase +from gns3server.schemas.compute.iou_nodes import ConsoleType from pydantic import Field from typing import Optional diff --git a/gns3server/schemas/qemu_templates.py b/gns3server/schemas/controller/templates/qemu_templates.py similarity index 98% rename from gns3server/schemas/qemu_templates.py rename to gns3server/schemas/controller/templates/qemu_templates.py index 8ab0d452..f69eb23a 100644 --- a/gns3server/schemas/qemu_templates.py +++ b/gns3server/schemas/controller/templates/qemu_templates.py @@ -15,8 +15,8 @@ # along with this program. If not, see . -from .templates import Category, TemplateBase -from .qemu_nodes import ( +from . import Category, TemplateBase +from gns3server.schemas.compute.qemu_nodes import ( QemuConsoleType, QemuPlatform, QemuAdapterType, diff --git a/gns3server/schemas/virtualbox_templates.py b/gns3server/schemas/controller/templates/virtualbox_templates.py similarity index 93% rename from gns3server/schemas/virtualbox_templates.py rename to gns3server/schemas/controller/templates/virtualbox_templates.py index 967bea3d..82cd63f4 100644 --- a/gns3server/schemas/virtualbox_templates.py +++ b/gns3server/schemas/controller/templates/virtualbox_templates.py @@ -14,8 +14,13 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from .templates import Category, TemplateBase -from .virtualbox_nodes import VirtualBoxConsoleType, VirtualBoxAdapterType, VirtualBoxOnCloseAction, CustomAdapter +from . import Category, TemplateBase +from gns3server.schemas.compute.virtualbox_nodes import ( + VirtualBoxConsoleType, + VirtualBoxAdapterType, + VirtualBoxOnCloseAction, + CustomAdapter +) from pydantic import Field from typing import Optional, List diff --git a/gns3server/schemas/vmware_templates.py b/gns3server/schemas/controller/templates/vmware_templates.py similarity index 93% rename from gns3server/schemas/vmware_templates.py rename to gns3server/schemas/controller/templates/vmware_templates.py index e0d6cef0..a8d604d8 100644 --- a/gns3server/schemas/vmware_templates.py +++ b/gns3server/schemas/controller/templates/vmware_templates.py @@ -15,8 +15,13 @@ # along with this program. If not, see . -from .templates import Category, TemplateBase -from .vmware_nodes import VMwareConsoleType, VMwareAdapterType, VMwareOnCloseAction, CustomAdapter +from . import Category, TemplateBase +from gns3server.schemas.compute.vmware_nodes import ( + VMwareConsoleType, + VMwareAdapterType, + VMwareOnCloseAction, + CustomAdapter +) from pydantic import Field from typing import Optional, List diff --git a/gns3server/schemas/vpcs_templates.py b/gns3server/schemas/controller/templates/vpcs_templates.py similarity index 92% rename from gns3server/schemas/vpcs_templates.py rename to gns3server/schemas/controller/templates/vpcs_templates.py index 18291c89..f75009ce 100644 --- a/gns3server/schemas/vpcs_templates.py +++ b/gns3server/schemas/controller/templates/vpcs_templates.py @@ -15,8 +15,8 @@ # along with this program. If not, see . -from .templates import Category, TemplateBase -from .vpcs_nodes import ConsoleType +from . import Category, TemplateBase +from gns3server.schemas.compute.vpcs_nodes import ConsoleType from pydantic import Field from typing import Optional diff --git a/gns3server/schemas/tokens.py b/gns3server/schemas/controller/tokens.py similarity index 100% rename from gns3server/schemas/tokens.py rename to gns3server/schemas/controller/tokens.py diff --git a/gns3server/schemas/topology.py b/gns3server/schemas/controller/topology.py similarity index 100% rename from gns3server/schemas/topology.py rename to gns3server/schemas/controller/topology.py diff --git a/gns3server/schemas/users.py b/gns3server/schemas/controller/users.py similarity index 100% rename from gns3server/schemas/users.py rename to gns3server/schemas/controller/users.py diff --git a/gns3server/services/authentication.py b/gns3server/services/authentication.py index e2a4b64f..021db36e 100644 --- a/gns3server/services/authentication.py +++ b/gns3server/services/authentication.py @@ -21,7 +21,7 @@ from passlib.context import CryptContext from typing import Optional from fastapi import HTTPException, status -from gns3server.schemas.tokens import TokenData +from gns3server.schemas.controller.tokens import TokenData from gns3server.config import Config from pydantic import ValidationError diff --git a/tests/conftest.py b/tests/conftest.py index 072c124e..7996955c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -344,13 +344,27 @@ def run_around_tests(monkeypatch, config, port_manager):#port_manager, controlle module._instance = None config.settings.Controller.jwt_secret_key = DEFAULT_JWT_SECRET_KEY - config.settings.Server.secrets_dir = os.path.join(tmppath, 'secrets') - os.makedirs(os.path.join(tmppath, 'projects')) - config.settings.Server.projects_path = os.path.join(tmppath, 'projects') - config.settings.Server.symbols_path = os.path.join(tmppath, 'symbols') - config.settings.Server.images_path = os.path.join(tmppath, 'images') - config.settings.Server.appliances_path = os.path.join(tmppath, 'appliances') + secrets_dir = os.path.join(tmppath, 'secrets') + os.makedirs(secrets_dir) + config.settings.Server.secrets_dir = secrets_dir + + projects_dir = os.path.join(tmppath, 'projects') + os.makedirs(projects_dir) + config.settings.Server.projects_path = projects_dir + + symbols_dir = os.path.join(tmppath, 'symbols') + os.makedirs(symbols_dir) + config.settings.Server.symbols_path = symbols_dir + + images_dir = os.path.join(tmppath, 'images') + os.makedirs(images_dir) + config.settings.Server.images_path = images_dir + + appliances_dir = os.path.join(tmppath, 'appliances') + os.makedirs(appliances_dir) + config.settings.Server.appliances_path = appliances_dir + config.settings.Server.ubridge_path = os.path.join(tmppath, 'bin', 'ubridge') config.settings.Server.local = True config.settings.Server.enable_http_auth = False From 6b8ce8219c0420dacdbdd47762a42b584f22345d Mon Sep 17 00:00:00 2001 From: grossmj Date: Thu, 15 Apr 2021 18:30:22 +0930 Subject: [PATCH 02/10] Fix tests. --- tests/api/routes/compute/test_virtualbox_nodes.py | 13 ++++++------- tests/api/routes/compute/test_vmware_nodes.py | 14 ++++++-------- tests/api/routes/controller/test_computes.py | 2 +- tests/api/routes/controller/test_users.py | 2 +- tests/conftest.py | 2 +- 5 files changed, 15 insertions(+), 18 deletions(-) diff --git a/tests/api/routes/compute/test_virtualbox_nodes.py b/tests/api/routes/compute/test_virtualbox_nodes.py index bff6aa95..5ff06722 100644 --- a/tests/api/routes/compute/test_virtualbox_nodes.py +++ b/tests/api/routes/compute/test_virtualbox_nodes.py @@ -75,14 +75,13 @@ async def test_vbox_get(app: FastAPI, client: AsyncClient, compute_project: Proj async def test_vbox_start(app: FastAPI, client: AsyncClient, vm: dict) -> None: - with asyncio_patch("gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.check_hw_virtualization", return_value=True): - with asyncio_patch("gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.start", return_value=True) as mock: + with asyncio_patch("gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.start", return_value=True) as mock: - response = await client.post(app.url_path_for("start_virtualbox_node", - project_id=vm["project_id"], - node_id=vm["node_id"])) - assert mock.called - assert response.status_code == status.HTTP_204_NO_CONTENT + response = await client.post(app.url_path_for("start_virtualbox_node", + project_id=vm["project_id"], + node_id=vm["node_id"])) + assert mock.called + assert response.status_code == status.HTTP_204_NO_CONTENT async def test_vbox_stop(app: FastAPI, client: AsyncClient, vm: dict) -> None: diff --git a/tests/api/routes/compute/test_vmware_nodes.py b/tests/api/routes/compute/test_vmware_nodes.py index f3e8783a..a32d4374 100644 --- a/tests/api/routes/compute/test_vmware_nodes.py +++ b/tests/api/routes/compute/test_vmware_nodes.py @@ -82,14 +82,12 @@ async def test_vmware_get(app: FastAPI, client: AsyncClient, compute_project: Pr async def test_vmware_start(app: FastAPI, client: AsyncClient, vm: dict) -> None: - with asyncio_patch("gns3server.compute.vmware.vmware_vm.VMwareVM.check_hw_virtualization", return_value=True) as mock1: - with asyncio_patch("gns3server.compute.vmware.vmware_vm.VMwareVM.start", return_value=True) as mock2: - response = await client.post(app.url_path_for("start_vmware_node", - project_id=vm["project_id"], - node_id=vm["node_id"])) - assert mock1.called - assert mock2.called - assert response.status_code == status.HTTP_204_NO_CONTENT + with asyncio_patch("gns3server.compute.vmware.vmware_vm.VMwareVM.start", return_value=True) as mock: + response = await client.post(app.url_path_for("start_vmware_node", + project_id=vm["project_id"], + node_id=vm["node_id"])) + assert mock.called + assert response.status_code == status.HTTP_204_NO_CONTENT async def test_vmware_stop(app: FastAPI, client: AsyncClient, vm: dict) -> None: diff --git a/tests/api/routes/controller/test_computes.py b/tests/api/routes/controller/test_computes.py index 6cb7040b..0e2c0e69 100644 --- a/tests/api/routes/controller/test_computes.py +++ b/tests/api/routes/controller/test_computes.py @@ -21,7 +21,7 @@ import pytest from fastapi import FastAPI, status from httpx import AsyncClient -from gns3server.schemas.computes import Compute +from gns3server.schemas.controller.computes import Compute pytestmark = pytest.mark.asyncio diff --git a/tests/api/routes/controller/test_users.py b/tests/api/routes/controller/test_users.py index 32041cb0..f8ad92f0 100644 --- a/tests/api/routes/controller/test_users.py +++ b/tests/api/routes/controller/test_users.py @@ -27,7 +27,7 @@ from gns3server.db.repositories.users import UsersRepository from gns3server.services import auth_service from gns3server.services.authentication import DEFAULT_JWT_SECRET_KEY from gns3server.config import Config -from gns3server.schemas.users import User +from gns3server.schemas.controller.users import User pytestmark = pytest.mark.asyncio diff --git a/tests/conftest.py b/tests/conftest.py index 7996955c..f54c1422 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -23,7 +23,7 @@ from gns3server.db.repositories.users import UsersRepository from gns3server.db.repositories.computes import ComputesRepository from gns3server.api.routes.controller.dependencies.database import get_db_session from gns3server import schemas -from gns3server.schemas.computes import Protocol +from gns3server.schemas.controller.computes import Protocol from gns3server.services import auth_service from gns3server.services.authentication import DEFAULT_JWT_SECRET_KEY From bad3ef7003e1c5f4329840c25a6ed05b7d0b8de1 Mon Sep 17 00:00:00 2001 From: grossmj Date: Sat, 17 Apr 2021 18:33:20 +0930 Subject: [PATCH 03/10] Detect the app is exiting and avoid reconnecting to computes. --- gns3server/api/server.py | 14 +++++++++++++- gns3server/controller/compute.py | 5 +++-- gns3server/core/tasks.py | 2 +- gns3server/server.py | 11 ++--------- 4 files changed, 19 insertions(+), 13 deletions(-) diff --git a/gns3server/api/server.py b/gns3server/api/server.py index de2310eb..da948299 100644 --- a/gns3server/api/server.py +++ b/gns3server/api/server.py @@ -25,7 +25,7 @@ from fastapi import FastAPI, Request from starlette.exceptions import HTTPException as StarletteHTTPException from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse - +from uvicorn.main import Server as UvicornServer from gns3server.controller.controller_error import ( ControllerError, @@ -82,6 +82,18 @@ def get_application() -> FastAPI: app = get_application() +# Monkey Patch uvicorn signal handler to detect the application is shutting down +app.state.exiting = False +unicorn_exit_handler = UvicornServer.handle_exit + + +def handle_exit(*args, **kwargs): + app.state.exiting = True + unicorn_exit_handler(*args, **kwargs) + + +UvicornServer.handle_exit = handle_exit + @app.exception_handler(ControllerError) async def controller_error_handler(request: Request, exc: ControllerError): diff --git a/gns3server/controller/compute.py b/gns3server/controller/compute.py index 04c73950..4c5969f0 100644 --- a/gns3server/controller/compute.py +++ b/gns3server/controller/compute.py @@ -21,7 +21,6 @@ import asyncio import async_timeout import socket import json -import uuid import sys import io from operator import itemgetter @@ -295,6 +294,7 @@ class Compute: """ :param topology_dump: Filter to keep only properties require for saving on disk """ + if topology_dump: return { "compute_id": self._id, @@ -486,7 +486,8 @@ class Compute: log.info(f"Connection closed to compute '{self._id}' WebSocket '{ws_url}'") # Try to reconnect after 1 second if server unavailable only if not during tests (otherwise we create a ressources usage bomb) - if self.id != "local" and not hasattr(sys, "_called_from_test") or not sys._called_from_test: + from gns3server.api.server import app + if not app.state.exiting and not hasattr(sys, "_called_from_test") or not sys._called_from_test: log.info(f"Reconnecting to to compute '{self._id}' WebSocket '{ws_url}'") asyncio.get_event_loop().call_later(1, lambda: asyncio.ensure_future(self.connect())) diff --git a/gns3server/core/tasks.py b/gns3server/core/tasks.py index 5080c7d9..f1a95556 100644 --- a/gns3server/core/tasks.py +++ b/gns3server/core/tasks.py @@ -82,7 +82,7 @@ def create_startup_handler(app: FastAPI) -> Callable: def create_shutdown_handler(app: FastAPI) -> Callable: """ - Tasks to be performed when the server is shutdown. + Tasks to be performed when the server is exiting. """ async def shutdown_handler() -> None: diff --git a/gns3server/server.py b/gns3server/server.py index 409d35cb..3c7dedb1 100644 --- a/gns3server/server.py +++ b/gns3server/server.py @@ -38,7 +38,6 @@ from gns3server.version import __version__ from gns3server.config import Config from gns3server.crash_report import CrashReport from gns3server.api.server import app - from pydantic import ValidationError import logging @@ -170,22 +169,16 @@ class Server: config.Server.certkey = args.certkey config.Server.enable_ssl = args.ssl - async def reload_server(self): - """ - Reload the server. - """ - - await Controller.instance().reload() - def _signal_handling(self): def signal_handler(signame, *args): try: if signame == "SIGHUP": log.info(f"Server has got signal {signame}, reloading...") - asyncio.ensure_future(self.reload_server()) + asyncio.ensure_future(Controller.instance().reload()) else: log.info(f"Server has got signal {signame}, exiting...") + # send SIGTERM to the server PID so uvicorn can shutdown the process os.kill(os.getpid(), signal.SIGTERM) except asyncio.CancelledError: pass From 44074ff7c9439b879b5a937b7fb5a1760a31fd71 Mon Sep 17 00:00:00 2001 From: grossmj Date: Sat, 17 Apr 2021 18:36:32 +0930 Subject: [PATCH 04/10] Some cleaning. --- gns3server/api/routes/controller/links.py | 4 +- .../compute/builtin/nodes/ethernet_hub.py | 7 ++- .../compute/builtin/nodes/ethernet_switch.py | 7 ++- .../dynamips/nios/nio_generic_ethernet.py | 5 +- .../dynamips/nios/nio_linux_ethernet.py | 5 +- gns3server/compute/dynamips/nios/nio_tap.py | 5 +- gns3server/compute/dynamips/nios/nio_udp.py | 7 ++- gns3server/compute/dynamips/nios/nio_unix.py | 6 +- gns3server/compute/dynamips/nios/nio_vde.py | 6 +- gns3server/compute/nios/nio_ethernet.py | 5 +- gns3server/compute/nios/nio_tap.py | 5 +- gns3server/compute/nios/nio_udp.py | 7 ++- gns3server/compute/project.py | 6 +- gns3server/controller/appliance.py | 3 +- gns3server/controller/node.py | 55 ++++++------------- gns3server/controller/notification.py | 27 +-------- gns3server/controller/project.py | 5 +- gns3server/controller/snapshot.py | 8 +-- gns3server/controller/topology.py | 3 +- gns3server/schemas/__init__.py | 2 +- gns3server/schemas/controller/links.py | 41 ++++++++++---- gns3server/utils/cpu_percent.py | 2 +- 22 files changed, 122 insertions(+), 99 deletions(-) diff --git a/gns3server/api/routes/controller/links.py b/gns3server/api/routes/controller/links.py index 7b91460d..5ae0fe03 100644 --- a/gns3server/api/routes/controller/links.py +++ b/gns3server/api/routes/controller/links.py @@ -71,7 +71,7 @@ async def get_links(project_id: UUID): 409: {"model": schemas.ErrorMessage, "description": "Could not create link"}, }, ) -async def create_link(project_id: UUID, link_data: schemas.Link): +async def create_link(project_id: UUID, link_data: schemas.LinkCreate): """ Create a new link. """ @@ -116,7 +116,7 @@ async def get_link(link: Link = Depends(dep_link)): @router.put("/{link_id}", response_model=schemas.Link, response_model_exclude_unset=True) -async def update_link(link_data: schemas.Link, link: Link = Depends(dep_link)): +async def update_link(link_data: schemas.LinkUpdate, link: Link = Depends(dep_link)): """ Update a link. """ diff --git a/gns3server/compute/builtin/nodes/ethernet_hub.py b/gns3server/compute/builtin/nodes/ethernet_hub.py index ca6d316a..c2ca8f61 100644 --- a/gns3server/compute/builtin/nodes/ethernet_hub.py +++ b/gns3server/compute/builtin/nodes/ethernet_hub.py @@ -40,7 +40,12 @@ class EthernetHub(BaseNode): def __json__(self): - return {"name": self.name, "usage": self.usage, "node_id": self.id, "project_id": self.project.id} + return { + "name": self.name, + "usage": self.usage, + "node_id": self.id, + "project_id": self.project.id + } async def create(self): """ diff --git a/gns3server/compute/builtin/nodes/ethernet_switch.py b/gns3server/compute/builtin/nodes/ethernet_switch.py index a16c64b3..22aa8647 100644 --- a/gns3server/compute/builtin/nodes/ethernet_switch.py +++ b/gns3server/compute/builtin/nodes/ethernet_switch.py @@ -40,7 +40,12 @@ class EthernetSwitch(BaseNode): def __json__(self): - return {"name": self.name, "usage": self.usage, "node_id": self.id, "project_id": self.project.id} + return { + "name": self.name, + "usage": self.usage, + "node_id": self.id, + "project_id": self.project.id + } async def create(self): """ diff --git a/gns3server/compute/dynamips/nios/nio_generic_ethernet.py b/gns3server/compute/dynamips/nios/nio_generic_ethernet.py index 519ac7dc..94a1c0c7 100644 --- a/gns3server/compute/dynamips/nios/nio_generic_ethernet.py +++ b/gns3server/compute/dynamips/nios/nio_generic_ethernet.py @@ -67,4 +67,7 @@ class NIOGenericEthernet(NIO): def __json__(self): - return {"type": "nio_generic_ethernet", "ethernet_device": self._ethernet_device} + return { + "type": "nio_generic_ethernet", + "ethernet_device": self._ethernet_device + } diff --git a/gns3server/compute/dynamips/nios/nio_linux_ethernet.py b/gns3server/compute/dynamips/nios/nio_linux_ethernet.py index 2ced9d83..1d76eeee 100644 --- a/gns3server/compute/dynamips/nios/nio_linux_ethernet.py +++ b/gns3server/compute/dynamips/nios/nio_linux_ethernet.py @@ -66,4 +66,7 @@ class NIOLinuxEthernet(NIO): def __json__(self): - return {"type": "nio_linux_ethernet", "ethernet_device": self._ethernet_device} + return { + "type": "nio_linux_ethernet", + "ethernet_device": self._ethernet_device + } diff --git a/gns3server/compute/dynamips/nios/nio_tap.py b/gns3server/compute/dynamips/nios/nio_tap.py index 4e27dcce..cf93eeeb 100644 --- a/gns3server/compute/dynamips/nios/nio_tap.py +++ b/gns3server/compute/dynamips/nios/nio_tap.py @@ -60,4 +60,7 @@ class NIOTAP(NIO): def __json__(self): - return {"type": "nio_tap", "tap_device": self._tap_device} + return { + "type": "nio_tap", + "tap_device": self._tap_device + } diff --git a/gns3server/compute/dynamips/nios/nio_udp.py b/gns3server/compute/dynamips/nios/nio_udp.py index 2988e858..52a29d61 100644 --- a/gns3server/compute/dynamips/nios/nio_udp.py +++ b/gns3server/compute/dynamips/nios/nio_udp.py @@ -128,4 +128,9 @@ class NIOUDP(NIO): def __json__(self): - return {"type": "nio_udp", "lport": self._lport, "rport": self._rport, "rhost": self._rhost} + return { + "type": "nio_udp", + "lport": self._lport, + "rport": self._rport, + "rhost": self._rhost + } diff --git a/gns3server/compute/dynamips/nios/nio_unix.py b/gns3server/compute/dynamips/nios/nio_unix.py index dfc9065b..44010b27 100644 --- a/gns3server/compute/dynamips/nios/nio_unix.py +++ b/gns3server/compute/dynamips/nios/nio_unix.py @@ -81,4 +81,8 @@ class NIOUNIX(NIO): def __json__(self): - return {"type": "nio_unix", "local_file": self._local_file, "remote_file": self._remote_file} + return { + "type": "nio_unix", + "local_file": self._local_file, + "remote_file": self._remote_file + } diff --git a/gns3server/compute/dynamips/nios/nio_vde.py b/gns3server/compute/dynamips/nios/nio_vde.py index ef2d921f..5971b892 100644 --- a/gns3server/compute/dynamips/nios/nio_vde.py +++ b/gns3server/compute/dynamips/nios/nio_vde.py @@ -81,4 +81,8 @@ class NIOVDE(NIO): def __json__(self): - return {"type": "nio_vde", "local_file": self._local_file, "control_file": self._control_file} + return { + "type": "nio_vde", + "local_file": self._local_file, + "control_file": self._control_file + } diff --git a/gns3server/compute/nios/nio_ethernet.py b/gns3server/compute/nios/nio_ethernet.py index a80ab5e2..1f521a69 100644 --- a/gns3server/compute/nios/nio_ethernet.py +++ b/gns3server/compute/nios/nio_ethernet.py @@ -50,4 +50,7 @@ class NIOEthernet(NIO): def __json__(self): - return {"type": "nio_ethernet", "ethernet_device": self._ethernet_device} + return { + "type": "nio_ethernet", + "ethernet_device": self._ethernet_device + } diff --git a/gns3server/compute/nios/nio_tap.py b/gns3server/compute/nios/nio_tap.py index 51deefe2..fee2d3d4 100644 --- a/gns3server/compute/nios/nio_tap.py +++ b/gns3server/compute/nios/nio_tap.py @@ -50,4 +50,7 @@ class NIOTAP(NIO): def __json__(self): - return {"type": "nio_tap", "tap_device": self._tap_device} + return { + "type": "nio_tap", + "tap_device": self._tap_device + } diff --git a/gns3server/compute/nios/nio_udp.py b/gns3server/compute/nios/nio_udp.py index 68fd2a8a..b9a87caf 100644 --- a/gns3server/compute/nios/nio_udp.py +++ b/gns3server/compute/nios/nio_udp.py @@ -74,4 +74,9 @@ class NIOUDP(NIO): def __json__(self): - return {"type": "nio_udp", "lport": self._lport, "rport": self._rport, "rhost": self._rhost} + return { + "type": "nio_udp", + "lport": self._lport, + "rport": self._rport, + "rhost": self._rhost + } diff --git a/gns3server/compute/project.py b/gns3server/compute/project.py index c1a44225..4f224f79 100644 --- a/gns3server/compute/project.py +++ b/gns3server/compute/project.py @@ -79,7 +79,11 @@ class Project: def __json__(self): - return {"name": self._name, "project_id": self._id, "variables": self._variables} + return { + "name": self._name, + "project_id": self._id, + "variables": self._variables + } def is_local(self): diff --git a/gns3server/controller/appliance.py b/gns3server/controller/appliance.py index d96d436b..47ac24cf 100644 --- a/gns3server/controller/appliance.py +++ b/gns3server/controller/appliance.py @@ -17,14 +17,15 @@ import copy import uuid - import logging log = logging.getLogger(__name__) class Appliance: + def __init__(self, appliance_id, data, builtin=True): + if appliance_id is None: self._id = str(uuid.uuid4()) elif isinstance(appliance_id, uuid.UUID): diff --git a/gns3server/controller/node.py b/gns3server/controller/node.py index 80e8c5d6..0032b49a 100644 --- a/gns3server/controller/node.py +++ b/gns3server/controller/node.py @@ -390,7 +390,6 @@ class Node: data["node_id"] = self._id if self._node_type == "docker": timeout = None - else: timeout = 1200 trial = 0 @@ -543,7 +542,7 @@ class Node: if self.custom_adapters: data["custom_adapters"] = self.custom_adapters - # None properties are not be send. Because it can mean the emulator doesn't support it + # None properties are not be sent because it can mean the emulator doesn't support it for key in list(data.keys()): if data[key] is None or data[key] is {} or key in self.CONTROLLER_ONLY_PROPERTIES: del data[key] @@ -628,7 +627,7 @@ class Node: async def put(self, path, data=None, **kwargs): """ - HTTP post on the node + HTTP put on the node """ if path is None: path = f"/projects/{self._project.id}/{self._node_type}/nodes/{self._id}" @@ -780,11 +779,10 @@ class Node: def __json__(self, topology_dump=False): """ - :param topology_dump: Filter to keep only properties require for saving on disk + :param topology_dump: Filter to keep only properties required for saving on disk """ - if topology_dump: - return { + topology = { "compute_id": str(self._compute.id), "node_id": self._id, "node_type": self._node_type, @@ -808,35 +806,18 @@ class Node: "port_segment_size": self._port_segment_size, "first_port_name": self._first_port_name, "custom_adapters": self._custom_adapters, - } - return { - "compute_id": str(self._compute.id), - "project_id": self._project.id, - "node_id": self._id, - "template_id": self._template_id, - "node_type": self._node_type, - "node_directory": self._node_directory, - "name": self._name, - "console": self._console, - "console_host": str(self._compute.console_host), - "console_type": self._console_type, - "aux": self._aux, - "aux_type": self._aux_type, - "console_auto_start": self._console_auto_start, - "command_line": self._command_line, - "properties": self._properties, - "status": self._status, - "label": self._label, - "x": self._x, - "y": self._y, - "z": self._z, - "locked": self._locked, - "width": self._width, - "height": self._height, - "symbol": self._symbol, - "port_name_format": self._port_name_format, - "port_segment_size": self._port_segment_size, - "first_port_name": self._first_port_name, - "custom_adapters": self._custom_adapters, - "ports": [port.__json__() for port in self.ports], } + + if topology_dump: + return topology + + additional_data = { + "project_id": self._project.id, + "command_line": self._command_line, + "status": self._status, + "console_host": str(self._compute.console_host), + "node_directory": self._node_directory, + "ports": [port.__json__() for port in self.ports] + } + topology.update(additional_data) + return topology diff --git a/gns3server/controller/notification.py b/gns3server/controller/notification.py index 9b9c3c5a..c7aaf0b4 100644 --- a/gns3server/controller/notification.py +++ b/gns3server/controller/notification.py @@ -28,6 +28,7 @@ class Notification: """ def __init__(self, controller): + self._controller = controller self._project_listeners = {} self._controller_listeners = set() @@ -71,19 +72,6 @@ class Notification: :param event: Event to send """ - # If use in tests for documentation we save a sample - if os.environ.get("PYTEST_BUILD_DOCUMENTATION") == "1": - os.makedirs("docs/api/notifications", exist_ok=True) - try: - import json - - data = json.dumps(event, indent=4, sort_keys=True) - if "MagicMock" not in data: - with open(os.path.join("docs/api/notifications", action + ".json"), "w+") as f: - f.write(data) - except TypeError: # If we receive a mock as an event it will raise TypeError when using json dump - pass - for controller_listener in self._controller_listeners: controller_listener.put_nowait((action, event, {})) @@ -127,19 +115,6 @@ class Notification: :param event: Event to send """ - # If use in tests for documentation we save a sample - if os.environ.get("PYTEST_BUILD_DOCUMENTATION") == "1": - os.makedirs("docs/api/notifications", exist_ok=True) - try: - import json - - data = json.dumps(event, indent=4, sort_keys=True) - if "MagicMock" not in data: - with open(os.path.join("docs/api/notifications", action + ".json"), "w+") as f: - f.write(data) - except TypeError: # If we receive a mock as an event it will raise TypeError when using json dump - pass - if "project_id" in event or project_id: self._send_event_to_project(event.get("project_id", project_id), action, event) else: diff --git a/gns3server/controller/project.py b/gns3server/controller/project.py index 8a5c7d3c..38131609 100644 --- a/gns3server/controller/project.py +++ b/gns3server/controller/project.py @@ -633,7 +633,7 @@ class Project: def _get_closed_data(self, section, id_key): """ Get the data for a project from the .gns3 when - the project is close + the project is closed :param section: The section name in the .gns3 :param id_key: The key for the element unique id @@ -1117,7 +1117,7 @@ class Project: try: topo = project_to_topology(self) path = self._topology_file() - log.debug("Write %s", path) + log.debug(f"Write topology file '{path}'") with open(path + ".tmp", "w+", encoding="utf-8") as f: json.dump(topo, f, indent=4, sort_keys=True) shutil.move(path + ".tmp", path) @@ -1176,6 +1176,7 @@ class Project: :param z: Z position :returns: New node """ + if node.status != "stopped" and not node.is_always_running(): raise ControllerError("Cannot duplicate node data while the node is running") diff --git a/gns3server/controller/snapshot.py b/gns3server/controller/snapshot.py index e0af79af..35883f93 100644 --- a/gns3server/controller/snapshot.py +++ b/gns3server/controller/snapshot.py @@ -36,10 +36,6 @@ import logging log = logging.getLogger(__name__) -# The string use to extract the date from the filename -FILENAME_TIME_FORMAT = "%d%m%y_%H%M%S" - - class Snapshot: """ A snapshot object @@ -59,7 +55,7 @@ class Snapshot: filename = ( self._name + "_" - + datetime.utcfromtimestamp(self._created_at).replace(tzinfo=None).strftime(FILENAME_TIME_FORMAT) + + datetime.utcfromtimestamp(self._created_at).replace(tzinfo=None).strftime("%d%m%y_%H%M%S") + ".gns3project" ) else: @@ -67,7 +63,7 @@ class Snapshot: datestring = filename.replace(self._name + "_", "").split(".")[0] try: self._created_at = ( - datetime.strptime(datestring, FILENAME_TIME_FORMAT).replace(tzinfo=timezone.utc).timestamp() + datetime.strptime(datestring, "%d%m%y_%H%M%S").replace(tzinfo=timezone.utc).timestamp() ) except ValueError: self._created_at = datetime.utcnow().timestamp() diff --git a/gns3server/controller/topology.py b/gns3server/controller/topology.py index fa2a54d6..7b3134e8 100644 --- a/gns3server/controller/topology.py +++ b/gns3server/controller/topology.py @@ -121,7 +121,8 @@ def load_topology(path): """ Open a topology file, patch it for last GNS3 release and return it """ - log.debug("Read topology %s", path) + + log.debug(f"Read topology {path}") try: with open(path, encoding="utf-8") as f: topo = json.load(f) diff --git a/gns3server/schemas/__init__.py b/gns3server/schemas/__init__.py index 8ff9d6f0..46446f82 100644 --- a/gns3server/schemas/__init__.py +++ b/gns3server/schemas/__init__.py @@ -20,7 +20,7 @@ from .common import ErrorMessage from .version import Version # Controller schemas -from .controller.links import Link +from .controller.links import LinkCreate, LinkUpdate, Link from .controller.computes import ComputeCreate, ComputeUpdate, AutoIdlePC, Compute from .controller.templates import TemplateCreate, TemplateUpdate, TemplateUsage, Template from .controller.drawings import Drawing diff --git a/gns3server/schemas/controller/links.py b/gns3server/schemas/controller/links.py index 3a37ccbc..fbbf50a1 100644 --- a/gns3server/schemas/controller/links.py +++ b/gns3server/schemas/controller/links.py @@ -17,7 +17,7 @@ from pydantic import BaseModel, Field from typing import List, Optional from enum import Enum -from uuid import UUID +from uuid import UUID, uuid4 from .labels import Label @@ -42,24 +42,45 @@ class LinkType(str, Enum): serial = "serial" -class Link(BaseModel): +class LinkBase(BaseModel): """ Link data. """ - link_id: Optional[UUID] = None - project_id: Optional[UUID] = None - nodes: Optional[List[LinkNode]] = None + nodes: Optional[List[LinkNode]] = Field(None, min_items=0, max_items=2) suspend: Optional[bool] = None filters: Optional[dict] = None - capturing: Optional[bool] = Field(None, description="Read only property. True if a capture running on the link") + + +class LinkCreate(LinkBase): + + link_id: UUID = Field(default_factory=uuid4) + nodes: List[LinkNode] = Field(..., min_items=2, max_items=2) + + +class LinkUpdate(LinkBase): + + pass + + +class Link(LinkBase): + + link_id: UUID + project_id: Optional[UUID] = None + link_type: Optional[LinkType] = None + capturing: Optional[bool] = Field( + None, + description="Read only property. True if a capture running on the link" + ) capture_file_name: Optional[str] = Field( - None, description="Read only property. The name of the capture file if a capture is running" + None, + description="Read only property. The name of the capture file if a capture is running" ) capture_file_path: Optional[str] = Field( - None, description="Read only property. The full path of the capture file if a capture is running" + None, + description="Read only property. The full path of the capture file if a capture is running" ) capture_compute_id: Optional[str] = Field( - None, description="Read only property. The compute identifier where a capture is running" + None, + description="Read only property. The compute identifier where a capture is running" ) - link_type: Optional[LinkType] = None diff --git a/gns3server/utils/cpu_percent.py b/gns3server/utils/cpu_percent.py index 59463bb9..016c633b 100644 --- a/gns3server/utils/cpu_percent.py +++ b/gns3server/utils/cpu_percent.py @@ -20,7 +20,7 @@ import time class CpuPercent: """ - Ensures a minumum interval between two cpu_percent() calls + Ensures a minimum interval between two cpu_percent() calls """ _last_measurement = None # time of last measurement From cefab8d3626638a39a44b993dcb4fc73a4cf7887 Mon Sep 17 00:00:00 2001 From: grossmj Date: Sat, 17 Apr 2021 23:34:28 +0930 Subject: [PATCH 05/10] Rename __json__() to asdict() --- .../api/routes/compute/atm_switch_nodes.py | 10 +++--- gns3server/api/routes/compute/cloud_nodes.py | 10 +++--- gns3server/api/routes/compute/compute.py | 2 +- gns3server/api/routes/compute/docker_nodes.py | 12 +++---- .../api/routes/compute/dynamips_nodes.py | 12 +++---- .../api/routes/compute/ethernet_hub_nodes.py | 10 +++--- .../routes/compute/ethernet_switch_nodes.py | 10 +++--- .../compute/frame_relay_switch_nodes.py | 10 +++--- gns3server/api/routes/compute/iou_nodes.py | 14 ++++---- gns3server/api/routes/compute/nat_nodes.py | 10 +++--- gns3server/api/routes/compute/projects.py | 12 +++---- gns3server/api/routes/compute/qemu_nodes.py | 12 +++---- .../api/routes/compute/virtualbox_nodes.py | 10 +++--- gns3server/api/routes/compute/vmware_nodes.py | 10 +++--- gns3server/api/routes/compute/vpcs_nodes.py | 12 +++---- .../api/routes/controller/appliances.py | 2 +- gns3server/api/routes/controller/drawings.py | 8 ++--- gns3server/api/routes/controller/gns3vm.py | 4 +-- gns3server/api/routes/controller/links.py | 12 +++---- gns3server/api/routes/controller/nodes.py | 12 +++---- gns3server/api/routes/controller/projects.py | 16 ++++----- gns3server/api/routes/controller/snapshots.py | 6 ++-- gns3server/api/routes/controller/templates.py | 2 +- gns3server/compute/builtin/nodes/cloud.py | 2 +- .../compute/builtin/nodes/ethernet_hub.py | 2 +- .../compute/builtin/nodes/ethernet_switch.py | 2 +- gns3server/compute/builtin/nodes/nat.py | 2 +- gns3server/compute/docker/docker_vm.py | 2 +- .../dynamips/nios/nio_generic_ethernet.py | 2 +- .../dynamips/nios/nio_linux_ethernet.py | 2 +- gns3server/compute/dynamips/nios/nio_null.py | 2 +- gns3server/compute/dynamips/nios/nio_tap.py | 2 +- gns3server/compute/dynamips/nios/nio_udp.py | 2 +- gns3server/compute/dynamips/nios/nio_unix.py | 2 +- gns3server/compute/dynamips/nios/nio_vde.py | 2 +- .../compute/dynamips/nodes/atm_switch.py | 2 +- gns3server/compute/dynamips/nodes/c1700.py | 4 +-- gns3server/compute/dynamips/nodes/c2600.py | 4 +-- gns3server/compute/dynamips/nodes/c2691.py | 4 +-- gns3server/compute/dynamips/nodes/c3600.py | 4 +-- gns3server/compute/dynamips/nodes/c3725.py | 4 +-- gns3server/compute/dynamips/nodes/c3745.py | 4 +-- gns3server/compute/dynamips/nodes/c7200.py | 4 +-- .../compute/dynamips/nodes/ethernet_hub.py | 2 +- .../compute/dynamips/nodes/ethernet_switch.py | 2 +- .../dynamips/nodes/frame_relay_switch.py | 2 +- gns3server/compute/dynamips/nodes/router.py | 2 +- gns3server/compute/iou/iou_vm.py | 2 +- gns3server/compute/nios/nio_ethernet.py | 2 +- gns3server/compute/nios/nio_tap.py | 2 +- gns3server/compute/nios/nio_udp.py | 2 +- gns3server/compute/port_manager.py | 2 +- gns3server/compute/project.py | 2 +- gns3server/compute/qemu/qemu_vm.py | 4 +-- .../compute/virtualbox/virtualbox_vm.py | 2 +- gns3server/compute/vmware/vmware_vm.py | 2 +- gns3server/compute/vpcs/vpcs_vm.py | 2 +- gns3server/controller/__init__.py | 6 ++-- gns3server/controller/appliance.py | 2 +- gns3server/controller/appliance_manager.py | 2 +- gns3server/controller/compute.py | 14 ++++---- gns3server/controller/drawing.py | 4 +-- gns3server/controller/gns3vm/__init__.py | 2 +- gns3server/controller/link.py | 14 ++++---- gns3server/controller/node.py | 10 +++--- gns3server/controller/notification.py | 2 +- gns3server/controller/ports/port.py | 2 +- gns3server/controller/project.py | 22 ++++++------ gns3server/controller/snapshot.py | 4 +-- gns3server/controller/topology.py | 20 ++++++----- gns3server/utils/notification_queue.py | 4 +-- tests/compute/builtin/nodes/test_cloud.py | 4 +-- tests/compute/builtin/nodes/test_nat.py | 6 ++-- tests/compute/docker/test_docker_vm.py | 2 +- tests/compute/qemu/test_qemu_vm.py | 2 +- tests/compute/test_project.py | 4 +-- .../compute/virtualbox/test_virtualbox_vm.py | 4 +-- tests/compute/vmware/test_vmware_vm.py | 4 +-- tests/controller/test_compute.py | 12 +++---- tests/controller/test_controller.py | 16 ++++----- tests/controller/test_drawing.py | 4 +-- tests/controller/test_gns3vm.py | 2 +- tests/controller/test_link.py | 12 +++---- tests/controller/test_node.py | 10 +++--- tests/controller/test_node_port_name.py | 34 +++++++++---------- tests/controller/test_notification.py | 2 +- tests/controller/test_project.py | 30 ++++++++-------- tests/controller/test_snapshot.py | 2 +- tests/controller/test_topology.py | 8 ++--- 89 files changed, 286 insertions(+), 282 deletions(-) diff --git a/gns3server/api/routes/compute/atm_switch_nodes.py b/gns3server/api/routes/compute/atm_switch_nodes.py index 23a45ef1..6a84ab27 100644 --- a/gns3server/api/routes/compute/atm_switch_nodes.py +++ b/gns3server/api/routes/compute/atm_switch_nodes.py @@ -65,7 +65,7 @@ async def create_atm_switch(project_id: UUID, node_data: schemas.ATMSwitchCreate node_type="atm_switch", mappings=node_data.get("mappings"), ) - return node.__json__() + return node.asdict() @router.get("/{node_id}", response_model=schemas.ATMSwitch) @@ -74,7 +74,7 @@ def get_atm_switch(node: ATMSwitch = Depends(dep_node)): Return an ATM switch node. """ - return node.__json__() + return node.asdict() @router.post("/{node_id}/duplicate", response_model=schemas.ATMSwitch, status_code=status.HTTP_201_CREATED) @@ -84,7 +84,7 @@ async def duplicate_atm_switch(destination_node_id: UUID = Body(..., embed=True) """ new_node = await Dynamips.instance().duplicate_node(node.id, str(destination_node_id)) - return new_node.__json__() + return new_node.asdict() @router.put("/{node_id}", response_model=schemas.ATMSwitch) @@ -99,7 +99,7 @@ async def update_atm_switch(node_data: schemas.ATMSwitchUpdate, node: ATMSwitch if "mappings" in node_data: node.mappings = node_data["mappings"] node.updated() - return node.__json__() + return node.asdict() @router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT) @@ -156,7 +156,7 @@ async def create_nio( nio = await Dynamips.instance().create_nio(node, jsonable_encoder(nio_data, exclude_unset=True)) await node.add_nio(nio, port_number) - return nio.__json__() + return nio.asdict() @router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT) diff --git a/gns3server/api/routes/compute/cloud_nodes.py b/gns3server/api/routes/compute/cloud_nodes.py index 0c710a2b..34bda678 100644 --- a/gns3server/api/routes/compute/cloud_nodes.py +++ b/gns3server/api/routes/compute/cloud_nodes.py @@ -72,7 +72,7 @@ async def create_cloud(project_id: UUID, node_data: schemas.CloudCreate): node.remote_console_type = node_data.get("remote_console_type", node.remote_console_type) node.remote_console_http_path = node_data.get("remote_console_http_path", node.remote_console_http_path) node.usage = node_data.get("usage", "") - return node.__json__() + return node.asdict() @router.get("/{node_id}", response_model=schemas.Cloud) @@ -81,7 +81,7 @@ def get_cloud(node: Cloud = Depends(dep_node)): Return a cloud node. """ - return node.__json__() + return node.asdict() @router.put("/{node_id}", response_model=schemas.Cloud) @@ -95,7 +95,7 @@ def update_cloud(node_data: schemas.CloudUpdate, node: Cloud = Depends(dep_node) if hasattr(node, name) and getattr(node, name) != value: setattr(node, name, value) node.updated() - return node.__json__() + return node.asdict() @router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT) @@ -154,7 +154,7 @@ async def create_cloud_nio( nio = Builtin.instance().create_nio(jsonable_encoder(nio_data, exclude_unset=True)) await node.add_nio(nio, port_number) - return nio.__json__() + return nio.asdict() @router.put( @@ -177,7 +177,7 @@ async def update_cloud_nio( if nio_data.filters: nio.filters = nio_data.filters await node.update_nio(port_number, nio) - return nio.__json__() + return nio.asdict() @router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT) diff --git a/gns3server/api/routes/compute/compute.py b/gns3server/api/routes/compute/compute.py index 81d8e8ec..2ae6ec2f 100644 --- a/gns3server/api/routes/compute/compute.py +++ b/gns3server/api/routes/compute/compute.py @@ -72,7 +72,7 @@ def network_ports() -> dict: """ m = PortManager.instance() - return m.__json__() + return m.asdict() @router.get("/version") diff --git a/gns3server/api/routes/compute/docker_nodes.py b/gns3server/api/routes/compute/docker_nodes.py index 8663d82b..0f90e267 100644 --- a/gns3server/api/routes/compute/docker_nodes.py +++ b/gns3server/api/routes/compute/docker_nodes.py @@ -82,7 +82,7 @@ async def create_docker_node(project_id: UUID, node_data: schemas.DockerCreate): if hasattr(container, name) and getattr(container, name) != value: setattr(container, name, value) - return container.__json__() + return container.asdict() @router.get("/{node_id}", response_model=schemas.Docker) @@ -91,7 +91,7 @@ def get_docker_node(node: DockerVM = Depends(dep_node)): Return a Docker node. """ - return node.__json__() + return node.asdict() @router.put("/{node_id}", response_model=schemas.Docker) @@ -128,7 +128,7 @@ async def update_docker_node(node_data: schemas.DockerUpdate, node: DockerVM = D if changed: await node.update() node.updated() - return node.__json__() + return node.asdict() @router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT) @@ -201,7 +201,7 @@ async def duplicate_docker_node(destination_node_id: UUID = Body(..., embed=True """ new_node = await Docker.instance().duplicate_node(node.id, str(destination_node_id)) - return new_node.__json__() + return new_node.asdict() @router.post( @@ -219,7 +219,7 @@ async def create_docker_node_nio( nio = Docker.instance().create_nio(jsonable_encoder(nio_data, exclude_unset=True)) await node.adapter_add_nio_binding(adapter_number, nio) - return nio.__json__() + return nio.asdict() @router.put( @@ -239,7 +239,7 @@ async def update_docker_node_nio( if nio_data.filters: nio.filters = nio_data.filters await node.adapter_update_nio_binding(adapter_number, nio) - return nio.__json__() + return nio.asdict() @router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT) diff --git a/gns3server/api/routes/compute/dynamips_nodes.py b/gns3server/api/routes/compute/dynamips_nodes.py index 63e0bde9..bff59c6c 100644 --- a/gns3server/api/routes/compute/dynamips_nodes.py +++ b/gns3server/api/routes/compute/dynamips_nodes.py @@ -81,7 +81,7 @@ async def create_router(project_id: UUID, node_data: schemas.DynamipsCreate): node_type="dynamips", ) await dynamips_manager.update_vm_settings(vm, node_data) - return vm.__json__() + return vm.asdict() @router.get("/{node_id}", response_model=schemas.Dynamips) @@ -90,7 +90,7 @@ def get_router(node: Router = Depends(dep_node)): Return Dynamips router. """ - return node.__json__() + return node.asdict() @router.put("/{node_id}", response_model=schemas.Dynamips) @@ -101,7 +101,7 @@ async def update_router(node_data: schemas.DynamipsUpdate, node: Router = Depend await Dynamips.instance().update_vm_settings(node, jsonable_encoder(node_data, exclude_unset=True)) node.updated() - return node.__json__() + return node.asdict() @router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT) @@ -171,7 +171,7 @@ async def create_nio(adapter_number: int, port_number: int, nio_data: schemas.UD nio = await Dynamips.instance().create_nio(node, jsonable_encoder(nio_data, exclude_unset=True)) await node.slot_add_nio_binding(adapter_number, port_number, nio) - return nio.__json__() + return nio.asdict() @router.put( @@ -188,7 +188,7 @@ async def update_nio(adapter_number: int, port_number: int, nio_data: schemas.UD if nio_data.filters: nio.filters = nio_data.filters await node.slot_update_nio_binding(adapter_number, port_number, nio) - return nio.__json__() + return nio.asdict() @router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT) @@ -273,7 +273,7 @@ async def duplicate_router(destination_node_id: UUID, node: Router = Depends(dep """ new_node = await Dynamips.instance().duplicate_node(node.id, str(destination_node_id)) - return new_node.__json__() + return new_node.asdict() @router.websocket("/{node_id}/console/ws") diff --git a/gns3server/api/routes/compute/ethernet_hub_nodes.py b/gns3server/api/routes/compute/ethernet_hub_nodes.py index 316bf229..c14dce63 100644 --- a/gns3server/api/routes/compute/ethernet_hub_nodes.py +++ b/gns3server/api/routes/compute/ethernet_hub_nodes.py @@ -65,7 +65,7 @@ async def create_ethernet_hub(project_id: UUID, node_data: schemas.EthernetHubCr node_type="ethernet_hub", ports=node_data.get("ports_mapping"), ) - return node.__json__() + return node.asdict() @router.get("/{node_id}", response_model=schemas.EthernetHub) @@ -74,7 +74,7 @@ def get_ethernet_hub(node: EthernetHub = Depends(dep_node)): Return an Ethernet hub. """ - return node.__json__() + return node.asdict() @router.post("/{node_id}/duplicate", response_model=schemas.EthernetHub, status_code=status.HTTP_201_CREATED) @@ -86,7 +86,7 @@ async def duplicate_ethernet_hub( """ new_node = await Dynamips.instance().duplicate_node(node.id, str(destination_node_id)) - return new_node.__json__() + return new_node.asdict() @router.put("/{node_id}", response_model=schemas.EthernetHub) @@ -101,7 +101,7 @@ async def update_ethernet_hub(node_data: schemas.EthernetHubUpdate, node: Ethern if "ports_mapping" in node_data: node.ports_mapping = node_data["ports_mapping"] node.updated() - return node.__json__() + return node.asdict() @router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT) @@ -158,7 +158,7 @@ async def create_nio( nio = await Dynamips.instance().create_nio(node, jsonable_encoder(nio_data, exclude_unset=True)) await node.add_nio(nio, port_number) - return nio.__json__() + return nio.asdict() @router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT) diff --git a/gns3server/api/routes/compute/ethernet_switch_nodes.py b/gns3server/api/routes/compute/ethernet_switch_nodes.py index 277dfcbb..51628dfb 100644 --- a/gns3server/api/routes/compute/ethernet_switch_nodes.py +++ b/gns3server/api/routes/compute/ethernet_switch_nodes.py @@ -68,13 +68,13 @@ async def create_ethernet_switch(project_id: UUID, node_data: schemas.EthernetSw ports=node_data.get("ports_mapping"), ) - return node.__json__() + return node.asdict() @router.get("/{node_id}", response_model=schemas.EthernetSwitch) def get_ethernet_switch(node: EthernetSwitch = Depends(dep_node)): - return node.__json__() + return node.asdict() @router.post("/{node_id}/duplicate", response_model=schemas.EthernetSwitch, status_code=status.HTTP_201_CREATED) @@ -86,7 +86,7 @@ async def duplicate_ethernet_switch( """ new_node = await Dynamips.instance().duplicate_node(node.id, str(destination_node_id)) - return new_node.__json__() + return new_node.asdict() @router.put("/{node_id}", response_model=schemas.EthernetSwitch) @@ -104,7 +104,7 @@ async def update_ethernet_switch(node_data: schemas.EthernetSwitchUpdate, node: if "console_type" in node_data: node.console_type = node_data["console_type"] node.updated() - return node.__json__() + return node.asdict() @router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT) @@ -157,7 +157,7 @@ async def create_nio( nio = await Dynamips.instance().create_nio(node, jsonable_encoder(nio_data, exclude_unset=True)) await node.add_nio(nio, port_number) - return nio.__json__() + return nio.asdict() @router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT) diff --git a/gns3server/api/routes/compute/frame_relay_switch_nodes.py b/gns3server/api/routes/compute/frame_relay_switch_nodes.py index 14dd490b..7b9f5ecb 100644 --- a/gns3server/api/routes/compute/frame_relay_switch_nodes.py +++ b/gns3server/api/routes/compute/frame_relay_switch_nodes.py @@ -65,7 +65,7 @@ async def create_frame_relay_switch(project_id: UUID, node_data: schemas.FrameRe node_type="frame_relay_switch", mappings=node_data.get("mappings"), ) - return node.__json__() + return node.asdict() @router.get("/{node_id}", response_model=schemas.FrameRelaySwitch) @@ -74,7 +74,7 @@ def get_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)): Return a Frame Relay switch node. """ - return node.__json__() + return node.asdict() @router.post("/{node_id}/duplicate", response_model=schemas.FrameRelaySwitch, status_code=status.HTTP_201_CREATED) @@ -86,7 +86,7 @@ async def duplicate_frame_relay_switch( """ new_node = await Dynamips.instance().duplicate_node(node.id, str(destination_node_id)) - return new_node.__json__() + return new_node.asdict() @router.put("/{node_id}", response_model=schemas.FrameRelaySwitch) @@ -103,7 +103,7 @@ async def update_frame_relay_switch( if "mappings" in node_data: node.mappings = node_data["mappings"] node.updated() - return node.__json__() + return node.asdict() @router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT) @@ -160,7 +160,7 @@ async def create_nio( nio = await Dynamips.instance().create_nio(node, jsonable_encoder(nio_data, exclude_unset=True)) await node.add_nio(nio, port_number) - return nio.__json__() + return nio.asdict() @router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT) diff --git a/gns3server/api/routes/compute/iou_nodes.py b/gns3server/api/routes/compute/iou_nodes.py index 399bdc55..32e19fcf 100644 --- a/gns3server/api/routes/compute/iou_nodes.py +++ b/gns3server/api/routes/compute/iou_nodes.py @@ -79,7 +79,7 @@ async def create_iou_node(project_id: UUID, node_data: schemas.IOUCreate): if node_data.get("use_default_iou_values") and (name == "ram" or name == "nvram"): continue setattr(vm, name, value) - return vm.__json__() + return vm.asdict() @router.get("/{node_id}", response_model=schemas.IOU) @@ -88,7 +88,7 @@ def get_iou_node(node: IOUVM = Depends(dep_node)): Return an IOU node. """ - return node.__json__() + return node.asdict() @router.put("/{node_id}", response_model=schemas.IOU) @@ -109,7 +109,7 @@ async def update_iou_node(node_data: schemas.IOUUpdate, node: IOUVM = Depends(de # this is important to have the correct NVRAM amount in order to correctly push the configs to the NVRAM await node.update_default_iou_values() node.updated() - return node.__json__() + return node.asdict() @router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT) @@ -128,7 +128,7 @@ async def duplicate_iou_node(destination_node_id: UUID = Body(..., embed=True), """ new_node = await IOU.instance().duplicate_node(node.id, str(destination_node_id)) - return new_node.__json__() + return new_node.asdict() @router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT) @@ -143,7 +143,7 @@ async def start_iou_node(start_data: schemas.IOUStart, node: IOUVM = Depends(dep setattr(node, name, value) await node.start() - return node.__json__() + return node.asdict() @router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT) @@ -191,7 +191,7 @@ async def create_iou_node_nio( nio = IOU.instance().create_nio(jsonable_encoder(nio_data, exclude_unset=True)) await node.adapter_add_nio_binding(adapter_number, port_number, nio) - return nio.__json__() + return nio.asdict() @router.put( @@ -213,7 +213,7 @@ async def update_iou_node_nio( if nio_data.filters: nio.filters = nio_data.filters await node.adapter_update_nio_binding(adapter_number, port_number, nio) - return nio.__json__() + return nio.asdict() @router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT) diff --git a/gns3server/api/routes/compute/nat_nodes.py b/gns3server/api/routes/compute/nat_nodes.py index 3af08309..f4f92f6a 100644 --- a/gns3server/api/routes/compute/nat_nodes.py +++ b/gns3server/api/routes/compute/nat_nodes.py @@ -67,7 +67,7 @@ async def create_nat_node(project_id: UUID, node_data: schemas.NATCreate): ) node.usage = node_data.get("usage", "") - return node.__json__() + return node.asdict() @router.get("/{node_id}", response_model=schemas.NAT) @@ -76,7 +76,7 @@ def get_nat_node(node: Nat = Depends(dep_node)): Return a NAT node. """ - return node.__json__() + return node.asdict() @router.put("/{node_id}", response_model=schemas.NAT) @@ -90,7 +90,7 @@ def update_nat_node(node_data: schemas.NATUpdate, node: Nat = Depends(dep_node)) if hasattr(node, name) and getattr(node, name) != value: setattr(node, name, value) node.updated() - return node.__json__() + return node.asdict() @router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT) @@ -149,7 +149,7 @@ async def create_nat_node_nio( nio = Builtin.instance().create_nio(jsonable_encoder(nio_data, exclude_unset=True)) await node.add_nio(nio, port_number) - return nio.__json__() + return nio.asdict() @router.put( @@ -172,7 +172,7 @@ async def update_nat_node_nio( if nio_data.filters: nio.filters = nio_data.filters await node.update_nio(port_number, nio) - return nio.__json__() + return nio.asdict() @router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT) diff --git a/gns3server/api/routes/compute/projects.py b/gns3server/api/routes/compute/projects.py index 4637f5e1..fd446db9 100644 --- a/gns3server/api/routes/compute/projects.py +++ b/gns3server/api/routes/compute/projects.py @@ -58,7 +58,7 @@ def get_compute_projects(): """ pm = ProjectManager.instance() - return [p.__json__() for p in pm.projects] + return [p.asdict() for p in pm.projects] @router.post("/projects", status_code=status.HTTP_201_CREATED, response_model=schemas.Project) @@ -75,7 +75,7 @@ def create_compute_project(project_data: schemas.ProjectCreate): project_id=project_data.get("project_id"), variables=project_data.get("variables", None), ) - return project.__json__() + return project.asdict() @router.put("/projects/{project_id}", response_model=schemas.Project) @@ -85,7 +85,7 @@ async def update_compute_project(project_data: schemas.ProjectUpdate, project: P """ await project.update(variables=project_data.variables) - return project.__json__() + return project.asdict() @router.get("/projects/{project_id}", response_model=schemas.Project) @@ -94,7 +94,7 @@ def get_compute_project(project: Project = Depends(dep_project)): Return a project from the compute. """ - return project.__json__() + return project.asdict() @router.post("/projects/{project_id}/close", status_code=status.HTTP_204_NO_CONTENT) @@ -152,8 +152,8 @@ async def delete_compute_project(project: Project = Depends(dep_project)): # while True: # try: # (action, msg) = await asyncio.wait_for(queue.get(), 5) -# if hasattr(msg, "__json__"): -# msg = json.dumps({"action": action, "event": msg.__json__()}, sort_keys=True) +# if hasattr(msg, "asdict"): +# msg = json.dumps({"action": action, "event": msg.asdict()}, sort_keys=True) # else: # msg = json.dumps({"action": action, "event": msg}, sort_keys=True) # log.debug("Send notification: %s", msg) diff --git a/gns3server/api/routes/compute/qemu_nodes.py b/gns3server/api/routes/compute/qemu_nodes.py index ed8d4aec..97645af6 100644 --- a/gns3server/api/routes/compute/qemu_nodes.py +++ b/gns3server/api/routes/compute/qemu_nodes.py @@ -76,7 +76,7 @@ async def create_qemu_node(project_id: UUID, node_data: schemas.QemuCreate): if hasattr(vm, name) and getattr(vm, name) != value: setattr(vm, name, value) - return vm.__json__() + return vm.asdict() @router.get("/{node_id}", response_model=schemas.Qemu) @@ -85,7 +85,7 @@ def get_qemu_node(node: QemuVM = Depends(dep_node)): Return a Qemu node. """ - return node.__json__() + return node.asdict() @router.put("/{node_id}", response_model=schemas.Qemu) @@ -101,7 +101,7 @@ async def update_qemu_node(node_data: schemas.QemuUpdate, node: QemuVM = Depends if hasattr(node, name) and getattr(node, name) != value: await node.update_property(name, value) node.updated() - return node.__json__() + return node.asdict() @router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT) @@ -120,7 +120,7 @@ async def duplicate_qemu_node(destination_node_id: UUID = Body(..., embed=True), """ new_node = await Qemu.instance().duplicate_node(node.id, str(destination_node_id)) - return new_node.__json__() + return new_node.asdict() @router.post("/{node_id}/resize_disk", status_code=status.HTTP_204_NO_CONTENT) @@ -196,7 +196,7 @@ async def create_qemu_node_nio( nio = Qemu.instance().create_nio(jsonable_encoder(nio_data, exclude_unset=True)) await node.adapter_add_nio_binding(adapter_number, nio) - return nio.__json__() + return nio.asdict() @router.put( @@ -218,7 +218,7 @@ async def update_qemu_node_nio( if nio_data.suspend: nio.suspend = nio_data.suspend await node.adapter_update_nio_binding(adapter_number, nio) - return nio.__json__() + return nio.asdict() @router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT) diff --git a/gns3server/api/routes/compute/virtualbox_nodes.py b/gns3server/api/routes/compute/virtualbox_nodes.py index 7fa51d9e..bb23563c 100644 --- a/gns3server/api/routes/compute/virtualbox_nodes.py +++ b/gns3server/api/routes/compute/virtualbox_nodes.py @@ -80,7 +80,7 @@ async def create_virtualbox_node(project_id: UUID, node_data: schemas.VirtualBox if hasattr(vm, name) and getattr(vm, name) != value: setattr(vm, name, value) - return vm.__json__() + return vm.asdict() @router.get("/{node_id}", response_model=schemas.VirtualBox) @@ -89,7 +89,7 @@ def get_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)): Return a VirtualBox node. """ - return node.__json__() + return node.asdict() @router.put("/{node_id}", response_model=schemas.VirtualBox) @@ -131,7 +131,7 @@ async def update_virtualbox_node(node_data: schemas.VirtualBoxUpdate, node: Virt setattr(node, name, value) node.updated() - return node.__json__() + return node.asdict() @router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT) @@ -203,7 +203,7 @@ async def create_virtualbox_node_nio( nio = VirtualBox.instance().create_nio(jsonable_encoder(nio_data, exclude_unset=True)) await node.adapter_add_nio_binding(adapter_number, nio) - return nio.__json__() + return nio.asdict() @router.put( @@ -225,7 +225,7 @@ async def update_virtualbox_node_nio( if nio_data.suspend: nio.suspend = nio_data.suspend await node.adapter_update_nio_binding(adapter_number, nio) - return nio.__json__() + return nio.asdict() @router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT) diff --git a/gns3server/api/routes/compute/vmware_nodes.py b/gns3server/api/routes/compute/vmware_nodes.py index 2617f791..05ef6929 100644 --- a/gns3server/api/routes/compute/vmware_nodes.py +++ b/gns3server/api/routes/compute/vmware_nodes.py @@ -73,7 +73,7 @@ async def create_vmware_node(project_id: UUID, node_data: schemas.VMwareCreate): if hasattr(vm, name) and getattr(vm, name) != value: setattr(vm, name, value) - return vm.__json__() + return vm.asdict() @router.get("/{node_id}", response_model=schemas.VMware) @@ -82,7 +82,7 @@ def get_vmware_node(node: VMwareVM = Depends(dep_node)): Return a VMware node. """ - return node.__json__() + return node.asdict() @router.put("/{node_id}", response_model=schemas.VMware) @@ -99,7 +99,7 @@ def update_vmware_node(node_data: schemas.VMwareUpdate, node: VMwareVM = Depends setattr(node, name, value) node.updated() - return node.__json__() + return node.asdict() @router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT) @@ -171,7 +171,7 @@ async def create_vmware_node_nio( nio = VMware.instance().create_nio(jsonable_encoder(nio_data, exclude_unset=True)) await node.adapter_add_nio_binding(adapter_number, nio) - return nio.__json__() + return nio.asdict() @router.put( @@ -191,7 +191,7 @@ async def update_vmware_node_nio( if nio_data.filters: nio.filters = nio_data.filters await node.adapter_update_nio_binding(adapter_number, nio) - return nio.__json__() + return nio.asdict() @router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT) diff --git a/gns3server/api/routes/compute/vpcs_nodes.py b/gns3server/api/routes/compute/vpcs_nodes.py index 0ebfe721..a34d99d5 100644 --- a/gns3server/api/routes/compute/vpcs_nodes.py +++ b/gns3server/api/routes/compute/vpcs_nodes.py @@ -66,7 +66,7 @@ async def create_vpcs_node(project_id: UUID, node_data: schemas.VPCSCreate): startup_script=node_data.get("startup_script"), ) - return vm.__json__() + return vm.asdict() @router.get("/{node_id}", response_model=schemas.VPCS) @@ -75,7 +75,7 @@ def get_vpcs_node(node: VPCSVM = Depends(dep_node)): Return a VPCS node. """ - return node.__json__() + return node.asdict() @router.put("/{node_id}", response_model=schemas.VPCS) @@ -89,7 +89,7 @@ def update_vpcs_node(node_data: schemas.VPCSUpdate, node: VPCSVM = Depends(dep_n node.console = node_data.get("console", node.console) node.console_type = node_data.get("console_type", node.console_type) node.updated() - return node.__json__() + return node.asdict() @router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT) @@ -108,7 +108,7 @@ async def duplicate_vpcs_node(destination_node_id: UUID = Body(..., embed=True), """ new_node = await VPCS.instance().duplicate_node(node.id, str(destination_node_id)) - return new_node.__json__() + return new_node.asdict() @router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT) @@ -163,7 +163,7 @@ async def create_vpcs_node_nio( nio = VPCS.instance().create_nio(jsonable_encoder(nio_data, exclude_unset=True)) await node.port_add_nio_binding(port_number, nio) - return nio.__json__() + return nio.asdict() @router.put( @@ -183,7 +183,7 @@ async def update_vpcs_node_nio( if nio_data.filters: nio.filters = nio_data.filters await node.port_update_nio_binding(port_number, nio) - return nio.__json__() + return nio.asdict() @router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT) diff --git a/gns3server/api/routes/controller/appliances.py b/gns3server/api/routes/controller/appliances.py index f307e67c..8876c83b 100644 --- a/gns3server/api/routes/controller/appliances.py +++ b/gns3server/api/routes/controller/appliances.py @@ -36,4 +36,4 @@ async def get_appliances(update: Optional[bool] = None, symbol_theme: Optional[s if update: await controller.appliance_manager.download_appliances() controller.appliance_manager.load_appliances(symbol_theme=symbol_theme) - return [c.__json__() for c in controller.appliance_manager.appliances.values()] + return [c.asdict() for c in controller.appliance_manager.appliances.values()] diff --git a/gns3server/api/routes/controller/drawings.py b/gns3server/api/routes/controller/drawings.py index ca6e2b95..b3804947 100644 --- a/gns3server/api/routes/controller/drawings.py +++ b/gns3server/api/routes/controller/drawings.py @@ -38,7 +38,7 @@ async def get_drawings(project_id: UUID): """ project = await Controller.instance().get_loaded_project(str(project_id)) - return [v.__json__() for v in project.drawings.values()] + return [v.asdict() for v in project.drawings.values()] @router.post("", status_code=status.HTTP_201_CREATED, response_model=schemas.Drawing) @@ -49,7 +49,7 @@ async def create_drawing(project_id: UUID, drawing_data: schemas.Drawing): project = await Controller.instance().get_loaded_project(str(project_id)) drawing = await project.add_drawing(**jsonable_encoder(drawing_data, exclude_unset=True)) - return drawing.__json__() + return drawing.asdict() @router.get("/{drawing_id}", response_model=schemas.Drawing, response_model_exclude_unset=True) @@ -60,7 +60,7 @@ async def get_drawing(project_id: UUID, drawing_id: UUID): project = await Controller.instance().get_loaded_project(str(project_id)) drawing = project.get_drawing(str(drawing_id)) - return drawing.__json__() + return drawing.asdict() @router.put("/{drawing_id}", response_model=schemas.Drawing, response_model_exclude_unset=True) @@ -72,7 +72,7 @@ async def update_drawing(project_id: UUID, drawing_id: UUID, drawing_data: schem project = await Controller.instance().get_loaded_project(str(project_id)) drawing = project.get_drawing(str(drawing_id)) await drawing.update(**jsonable_encoder(drawing_data, exclude_unset=True)) - return drawing.__json__() + return drawing.asdict() @router.delete("/{drawing_id}", status_code=status.HTTP_204_NO_CONTENT) diff --git a/gns3server/api/routes/controller/gns3vm.py b/gns3server/api/routes/controller/gns3vm.py index 0a42c88a..80329dfe 100644 --- a/gns3server/api/routes/controller/gns3vm.py +++ b/gns3server/api/routes/controller/gns3vm.py @@ -54,7 +54,7 @@ async def get_gns3vm_settings(): Return the GNS3 VM settings. """ - return Controller.instance().gns3vm.__json__() + return Controller.instance().gns3vm.asdict() @router.put("", response_model=schemas.GNS3VM, response_model_exclude_unset=True) @@ -67,4 +67,4 @@ async def update_gns3vm_settings(gns3vm_data: schemas.GNS3VM): gns3_vm = controller.gns3vm await gns3_vm.update_settings(jsonable_encoder(gns3vm_data, exclude_unset=True)) controller.save() - return gns3_vm.__json__() + return gns3_vm.asdict() diff --git a/gns3server/api/routes/controller/links.py b/gns3server/api/routes/controller/links.py index 5ae0fe03..0cab04a6 100644 --- a/gns3server/api/routes/controller/links.py +++ b/gns3server/api/routes/controller/links.py @@ -59,7 +59,7 @@ async def get_links(project_id: UUID): """ project = await Controller.instance().get_loaded_project(str(project_id)) - return [v.__json__() for v in project.links.values()] + return [v.asdict() for v in project.links.values()] @router.post( @@ -94,7 +94,7 @@ async def create_link(project_id: UUID, link_data: schemas.LinkCreate): except ControllerError as e: await project.delete_link(link.id) raise e - return link.__json__() + return link.asdict() @router.get("/{link_id}/available_filters") @@ -112,7 +112,7 @@ async def get_link(link: Link = Depends(dep_link)): Return a link. """ - return link.__json__() + return link.asdict() @router.put("/{link_id}", response_model=schemas.Link, response_model_exclude_unset=True) @@ -128,7 +128,7 @@ async def update_link(link_data: schemas.LinkUpdate, link: Link = Depends(dep_li await link.update_suspend(link_data["suspend"]) if "nodes" in link_data: await link.update_nodes(link_data["nodes"]) - return link.__json__() + return link.asdict() @router.delete("/{link_id}", status_code=status.HTTP_204_NO_CONTENT) @@ -148,7 +148,7 @@ async def reset_link(link: Link = Depends(dep_link)): """ await link.reset() - return link.__json__() + return link.asdict() @router.post("/{link_id}/capture/start", status_code=status.HTTP_201_CREATED, response_model=schemas.Link) @@ -161,7 +161,7 @@ async def start_capture(capture_data: dict, link: Link = Depends(dep_link)): data_link_type=capture_data.get("data_link_type", "DLT_EN10MB"), capture_file_name=capture_data.get("capture_file_name"), ) - return link.__json__() + return link.asdict() @router.post("/{link_id}/capture/stop", status_code=status.HTTP_204_NO_CONTENT) diff --git a/gns3server/api/routes/controller/nodes.py b/gns3server/api/routes/controller/nodes.py index 93e2a28b..58d0e114 100644 --- a/gns3server/api/routes/controller/nodes.py +++ b/gns3server/api/routes/controller/nodes.py @@ -117,7 +117,7 @@ async def create_node(node_data: schemas.NodeCreate, project: Project = Depends( compute = controller.get_compute(str(node_data.compute_id)) node_data = jsonable_encoder(node_data, exclude_unset=True) node = await project.add_node(compute, node_data.pop("name"), node_data.pop("node_id", None), **node_data) - return node.__json__() + return node.asdict() @router.get("", response_model=List[schemas.Node], response_model_exclude_unset=True) @@ -126,7 +126,7 @@ async def get_nodes(project: Project = Depends(dep_project)): Return all nodes belonging to a given project. """ - return [v.__json__() for v in project.nodes.values()] + return [v.asdict() for v in project.nodes.values()] @router.post("/start", status_code=status.HTTP_204_NO_CONTENT) @@ -172,7 +172,7 @@ def get_node(node: Node = Depends(dep_node)): Return a node from a given project. """ - return node.__json__() + return node.asdict() @router.put("/{node_id}", response_model=schemas.Node, response_model_exclude_unset=True) @@ -189,7 +189,7 @@ async def update_node(node_data: schemas.NodeUpdate, node: Node = Depends(dep_no node_data.pop("compute_id", None) await node.update(**node_data) - return node.__json__() + return node.asdict() @router.delete( @@ -212,7 +212,7 @@ async def duplicate_node(duplicate_data: schemas.NodeDuplicate, node: Node = Dep """ new_node = await node.project.duplicate_node(node, duplicate_data.x, duplicate_data.y, duplicate_data.z) - return new_node.__json__() + return new_node.asdict() @router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT) @@ -259,7 +259,7 @@ async def get_node_links(node: Node = Depends(dep_node)): links = [] for link in node.links: - links.append(link.__json__()) + links.append(link.asdict()) return links diff --git a/gns3server/api/routes/controller/projects.py b/gns3server/api/routes/controller/projects.py index 60168412..7b5775a2 100644 --- a/gns3server/api/routes/controller/projects.py +++ b/gns3server/api/routes/controller/projects.py @@ -70,7 +70,7 @@ def get_projects(): """ controller = Controller.instance() - return [p.__json__() for p in controller.projects.values()] + return [p.asdict() for p in controller.projects.values()] @router.post( @@ -87,7 +87,7 @@ async def create_project(project_data: schemas.ProjectCreate): controller = Controller.instance() project = await controller.add_project(**jsonable_encoder(project_data, exclude_unset=True)) - return project.__json__() + return project.asdict() @router.get("/{project_id}", response_model=schemas.Project) @@ -96,7 +96,7 @@ def get_project(project: Project = Depends(dep_project)): Return a project. """ - return project.__json__() + return project.asdict() @router.put("/{project_id}", response_model=schemas.Project, response_model_exclude_unset=True) @@ -106,7 +106,7 @@ async def update_project(project_data: schemas.ProjectUpdate, project: Project = """ await project.update(**jsonable_encoder(project_data, exclude_unset=True)) - return project.__json__() + return project.asdict() @router.delete("/{project_id}", status_code=status.HTTP_204_NO_CONTENT) @@ -154,7 +154,7 @@ async def open_project(project: Project = Depends(dep_project)): """ await project.open() - return project.__json__() + return project.asdict() @router.post( @@ -176,7 +176,7 @@ async def load_project(path: str = Body(..., embed=True)): project = await controller.load_project( dot_gns3_file, ) - return project.__json__() + return project.asdict() @router.get("/{project_id}/notifications") @@ -323,7 +323,7 @@ async def import_project(project_id: UUID, request: Request, path: Optional[Path log.info(f"Project '{project.name}' imported in {time.time() - begin:.4f} seconds") except OSError as e: raise ControllerError(f"Could not import the project: {e}") - return project.__json__() + return project.asdict() @router.post( @@ -348,7 +348,7 @@ async def duplicate_project(project_data: schemas.ProjectDuplicate, project: Pro new_project = await project.duplicate( name=project_data.name, location=location, reset_mac_addresses=reset_mac_addresses ) - return new_project.__json__() + return new_project.asdict() @router.get("/{project_id}/files/{file_path:path}") diff --git a/gns3server/api/routes/controller/snapshots.py b/gns3server/api/routes/controller/snapshots.py index 54284f66..5cf2f067 100644 --- a/gns3server/api/routes/controller/snapshots.py +++ b/gns3server/api/routes/controller/snapshots.py @@ -52,7 +52,7 @@ async def create_snapshot(snapshot_data: schemas.SnapshotCreate, project: Projec """ snapshot = await project.snapshot(snapshot_data.name) - return snapshot.__json__() + return snapshot.asdict() @router.get("", response_model=List[schemas.Snapshot], response_model_exclude_unset=True) @@ -62,7 +62,7 @@ def get_snapshots(project: Project = Depends(dep_project)): """ snapshots = [s for s in project.snapshots.values()] - return [s.__json__() for s in sorted(snapshots, key=lambda s: (s.created_at, s.name))] + return [s.asdict() for s in sorted(snapshots, key=lambda s: (s.created_at, s.name))] @router.delete("/{snapshot_id}", status_code=status.HTTP_204_NO_CONTENT) @@ -82,4 +82,4 @@ async def restore_snapshot(snapshot_id: UUID, project: Project = Depends(dep_pro snapshot = project.get_snapshot(str(snapshot_id)) project = await snapshot.restore() - return project.__json__() + return project.asdict() diff --git a/gns3server/api/routes/controller/templates.py b/gns3server/api/routes/controller/templates.py index 725f3776..0b1f50b2 100644 --- a/gns3server/api/routes/controller/templates.py +++ b/gns3server/api/routes/controller/templates.py @@ -145,4 +145,4 @@ async def create_node_from_template( node = await project.add_node_from_template( template, x=template_usage.x, y=template_usage.y, compute_id=template_usage.compute_id ) - return node.__json__() + return node.asdict() diff --git a/gns3server/compute/builtin/nodes/cloud.py b/gns3server/compute/builtin/nodes/cloud.py index dcfdd5c7..dc7b1de0 100644 --- a/gns3server/compute/builtin/nodes/cloud.py +++ b/gns3server/compute/builtin/nodes/cloud.py @@ -77,7 +77,7 @@ class Cloud(BaseNode): def _interfaces(self): return gns3server.utils.interfaces.interfaces() - def __json__(self): + def asdict(self): host_interfaces = [] network_interfaces = gns3server.utils.interfaces.interfaces() diff --git a/gns3server/compute/builtin/nodes/ethernet_hub.py b/gns3server/compute/builtin/nodes/ethernet_hub.py index c2ca8f61..fc601ff0 100644 --- a/gns3server/compute/builtin/nodes/ethernet_hub.py +++ b/gns3server/compute/builtin/nodes/ethernet_hub.py @@ -38,7 +38,7 @@ class EthernetHub(BaseNode): super().__init__(name, node_id, project, manager) - def __json__(self): + def asdict(self): return { "name": self.name, diff --git a/gns3server/compute/builtin/nodes/ethernet_switch.py b/gns3server/compute/builtin/nodes/ethernet_switch.py index 22aa8647..d4a4863a 100644 --- a/gns3server/compute/builtin/nodes/ethernet_switch.py +++ b/gns3server/compute/builtin/nodes/ethernet_switch.py @@ -38,7 +38,7 @@ class EthernetSwitch(BaseNode): super().__init__(name, node_id, project, manager) - def __json__(self): + def asdict(self): return { "name": self.name, diff --git a/gns3server/compute/builtin/nodes/nat.py b/gns3server/compute/builtin/nodes/nat.py index a3da907f..21deca92 100644 --- a/gns3server/compute/builtin/nodes/nat.py +++ b/gns3server/compute/builtin/nodes/nat.py @@ -77,7 +77,7 @@ class Nat(Cloud): def is_supported(self): return True - def __json__(self): + def asdict(self): return { "name": self.name, "usage": self.usage, diff --git a/gns3server/compute/docker/docker_vm.py b/gns3server/compute/docker/docker_vm.py index a10d3ec2..55a38e0c 100644 --- a/gns3server/compute/docker/docker_vm.py +++ b/gns3server/compute/docker/docker_vm.py @@ -133,7 +133,7 @@ class DockerVM(BaseNode): ) ) - def __json__(self): + def asdict(self): return { "name": self._name, "usage": self.usage, diff --git a/gns3server/compute/dynamips/nios/nio_generic_ethernet.py b/gns3server/compute/dynamips/nios/nio_generic_ethernet.py index 94a1c0c7..cf2cc750 100644 --- a/gns3server/compute/dynamips/nios/nio_generic_ethernet.py +++ b/gns3server/compute/dynamips/nios/nio_generic_ethernet.py @@ -65,7 +65,7 @@ class NIOGenericEthernet(NIO): return self._ethernet_device - def __json__(self): + def asdict(self): return { "type": "nio_generic_ethernet", diff --git a/gns3server/compute/dynamips/nios/nio_linux_ethernet.py b/gns3server/compute/dynamips/nios/nio_linux_ethernet.py index 1d76eeee..64f7e938 100644 --- a/gns3server/compute/dynamips/nios/nio_linux_ethernet.py +++ b/gns3server/compute/dynamips/nios/nio_linux_ethernet.py @@ -64,7 +64,7 @@ class NIOLinuxEthernet(NIO): return self._ethernet_device - def __json__(self): + def asdict(self): return { "type": "nio_linux_ethernet", diff --git a/gns3server/compute/dynamips/nios/nio_null.py b/gns3server/compute/dynamips/nios/nio_null.py index 6f83f11d..08144646 100644 --- a/gns3server/compute/dynamips/nios/nio_null.py +++ b/gns3server/compute/dynamips/nios/nio_null.py @@ -46,6 +46,6 @@ class NIONull(NIO): await self._hypervisor.send(f"nio create_null {self._name}") log.info(f"NIO NULL {self._name} created.") - def __json__(self): + def asdict(self): return {"type": "nio_null"} diff --git a/gns3server/compute/dynamips/nios/nio_tap.py b/gns3server/compute/dynamips/nios/nio_tap.py index cf93eeeb..50efd553 100644 --- a/gns3server/compute/dynamips/nios/nio_tap.py +++ b/gns3server/compute/dynamips/nios/nio_tap.py @@ -58,7 +58,7 @@ class NIOTAP(NIO): return self._tap_device - def __json__(self): + def asdict(self): return { "type": "nio_tap", diff --git a/gns3server/compute/dynamips/nios/nio_udp.py b/gns3server/compute/dynamips/nios/nio_udp.py index 52a29d61..47faacc4 100644 --- a/gns3server/compute/dynamips/nios/nio_udp.py +++ b/gns3server/compute/dynamips/nios/nio_udp.py @@ -126,7 +126,7 @@ class NIOUDP(NIO): return self._rport - def __json__(self): + def asdict(self): return { "type": "nio_udp", diff --git a/gns3server/compute/dynamips/nios/nio_unix.py b/gns3server/compute/dynamips/nios/nio_unix.py index 44010b27..e6edaa4b 100644 --- a/gns3server/compute/dynamips/nios/nio_unix.py +++ b/gns3server/compute/dynamips/nios/nio_unix.py @@ -79,7 +79,7 @@ class NIOUNIX(NIO): return self._remote_file - def __json__(self): + def asdict(self): return { "type": "nio_unix", diff --git a/gns3server/compute/dynamips/nios/nio_vde.py b/gns3server/compute/dynamips/nios/nio_vde.py index 5971b892..d6568773 100644 --- a/gns3server/compute/dynamips/nios/nio_vde.py +++ b/gns3server/compute/dynamips/nios/nio_vde.py @@ -79,7 +79,7 @@ class NIOVDE(NIO): return self._local_file - def __json__(self): + def asdict(self): return { "type": "nio_vde", diff --git a/gns3server/compute/dynamips/nodes/atm_switch.py b/gns3server/compute/dynamips/nodes/atm_switch.py index e47b8599..c97b3ad3 100644 --- a/gns3server/compute/dynamips/nodes/atm_switch.py +++ b/gns3server/compute/dynamips/nodes/atm_switch.py @@ -52,7 +52,7 @@ class ATMSwitch(Device): if mappings: self._mappings = mappings - def __json__(self): + def asdict(self): mappings = {} for source, destination in self._mappings.items(): diff --git a/gns3server/compute/dynamips/nodes/c1700.py b/gns3server/compute/dynamips/nodes/c1700.py index 641a5fad..55b8dc4d 100644 --- a/gns3server/compute/dynamips/nodes/c1700.py +++ b/gns3server/compute/dynamips/nodes/c1700.py @@ -76,11 +76,11 @@ class C1700(Router): self._clock_divisor = 8 self._sparsemem = False # never activate sparsemem for c1700 (unstable) - def __json__(self): + def asdict(self): c1700_router_info = {"iomem": self._iomem, "chassis": self._chassis, "sparsemem": self._sparsemem} - router_info = Router.__json__(self) + router_info = Router.asdict(self) router_info.update(c1700_router_info) return router_info diff --git a/gns3server/compute/dynamips/nodes/c2600.py b/gns3server/compute/dynamips/nodes/c2600.py index 65240202..8c39f43b 100644 --- a/gns3server/compute/dynamips/nodes/c2600.py +++ b/gns3server/compute/dynamips/nodes/c2600.py @@ -93,11 +93,11 @@ class C2600(Router): self._clock_divisor = 8 self._sparsemem = False # never activate sparsemem for c2600 (unstable) - def __json__(self): + def asdict(self): c2600_router_info = {"iomem": self._iomem, "chassis": self._chassis, "sparsemem": self._sparsemem} - router_info = Router.__json__(self) + router_info = Router.asdict(self) router_info.update(c2600_router_info) return router_info diff --git a/gns3server/compute/dynamips/nodes/c2691.py b/gns3server/compute/dynamips/nodes/c2691.py index cc4255ab..3ef3f73f 100644 --- a/gns3server/compute/dynamips/nodes/c2691.py +++ b/gns3server/compute/dynamips/nodes/c2691.py @@ -77,11 +77,11 @@ class C2691(Router): if chassis is not None: raise DynamipsError("c2691 routers do not have chassis") - def __json__(self): + def asdict(self): c2691_router_info = {"iomem": self._iomem} - router_info = Router.__json__(self) + router_info = Router.asdict(self) router_info.update(c2691_router_info) return router_info diff --git a/gns3server/compute/dynamips/nodes/c3600.py b/gns3server/compute/dynamips/nodes/c3600.py index 14f875da..fc10a162 100644 --- a/gns3server/compute/dynamips/nodes/c3600.py +++ b/gns3server/compute/dynamips/nodes/c3600.py @@ -73,11 +73,11 @@ class C3600(Router): self._chassis = chassis self._clock_divisor = 4 - def __json__(self): + def asdict(self): c3600_router_info = {"iomem": self._iomem, "chassis": self._chassis} - router_info = Router.__json__(self) + router_info = Router.asdict(self) router_info.update(c3600_router_info) return router_info diff --git a/gns3server/compute/dynamips/nodes/c3725.py b/gns3server/compute/dynamips/nodes/c3725.py index 443ac1be..c20cdbec 100644 --- a/gns3server/compute/dynamips/nodes/c3725.py +++ b/gns3server/compute/dynamips/nodes/c3725.py @@ -77,11 +77,11 @@ class C3725(Router): if chassis is not None: raise DynamipsError("c3725 routers do not have chassis") - def __json__(self): + def asdict(self): c3725_router_info = {"iomem": self._iomem} - router_info = Router.__json__(self) + router_info = Router.asdict(self) router_info.update(c3725_router_info) return router_info diff --git a/gns3server/compute/dynamips/nodes/c3745.py b/gns3server/compute/dynamips/nodes/c3745.py index 98e8efc3..9da5de8d 100644 --- a/gns3server/compute/dynamips/nodes/c3745.py +++ b/gns3server/compute/dynamips/nodes/c3745.py @@ -77,11 +77,11 @@ class C3745(Router): if chassis is not None: raise DynamipsError("c3745 routers do not have chassis") - def __json__(self): + def asdict(self): c3745_router_info = {"iomem": self._iomem} - router_info = Router.__json__(self) + router_info = Router.asdict(self) router_info.update(c3745_router_info) return router_info diff --git a/gns3server/compute/dynamips/nodes/c7200.py b/gns3server/compute/dynamips/nodes/c7200.py index 991c87b7..fe2f2b03 100644 --- a/gns3server/compute/dynamips/nodes/c7200.py +++ b/gns3server/compute/dynamips/nodes/c7200.py @@ -92,7 +92,7 @@ class C7200(Router): if chassis is not None: raise DynamipsError("c7200 routers do not have chassis") - def __json__(self): + def asdict(self): c7200_router_info = { "npe": self._npe, @@ -101,7 +101,7 @@ class C7200(Router): "power_supplies": self._power_supplies, } - router_info = Router.__json__(self) + router_info = Router.asdict(self) router_info.update(c7200_router_info) return router_info diff --git a/gns3server/compute/dynamips/nodes/ethernet_hub.py b/gns3server/compute/dynamips/nodes/ethernet_hub.py index bad1d20f..46f8cc5a 100644 --- a/gns3server/compute/dynamips/nodes/ethernet_hub.py +++ b/gns3server/compute/dynamips/nodes/ethernet_hub.py @@ -53,7 +53,7 @@ class EthernetHub(Bridge): else: self._ports = ports - def __json__(self): + def asdict(self): return { "name": self.name, diff --git a/gns3server/compute/dynamips/nodes/ethernet_switch.py b/gns3server/compute/dynamips/nodes/ethernet_switch.py index 906df019..1ca67f79 100644 --- a/gns3server/compute/dynamips/nodes/ethernet_switch.py +++ b/gns3server/compute/dynamips/nodes/ethernet_switch.py @@ -109,7 +109,7 @@ class EthernetSwitch(Device): else: self._ports = ports - def __json__(self): + def asdict(self): ethernet_switch_info = { "name": self.name, diff --git a/gns3server/compute/dynamips/nodes/frame_relay_switch.py b/gns3server/compute/dynamips/nodes/frame_relay_switch.py index 91058c1c..32c92089 100644 --- a/gns3server/compute/dynamips/nodes/frame_relay_switch.py +++ b/gns3server/compute/dynamips/nodes/frame_relay_switch.py @@ -51,7 +51,7 @@ class FrameRelaySwitch(Device): if mappings: self._mappings = mappings - def __json__(self): + def asdict(self): mappings = {} for source, destination in self._mappings.items(): diff --git a/gns3server/compute/dynamips/nodes/router.py b/gns3server/compute/dynamips/nodes/router.py index f144d673..a2bbe235 100644 --- a/gns3server/compute/dynamips/nodes/router.py +++ b/gns3server/compute/dynamips/nodes/router.py @@ -155,7 +155,7 @@ class Router(BaseNode): log.error(f"Can't move {path}: {str(e)}") continue - def __json__(self): + def asdict(self): router_info = { "name": self.name, diff --git a/gns3server/compute/iou/iou_vm.py b/gns3server/compute/iou/iou_vm.py index cd2f1c1c..3ca8f371 100644 --- a/gns3server/compute/iou/iou_vm.py +++ b/gns3server/compute/iou/iou_vm.py @@ -219,7 +219,7 @@ class IOUVM(BaseNode): if not os.access(self._path, os.X_OK): raise IOUError(f"IOU image '{self._path}' is not executable") - def __json__(self): + def asdict(self): iou_vm_info = { "name": self.name, diff --git a/gns3server/compute/nios/nio_ethernet.py b/gns3server/compute/nios/nio_ethernet.py index 1f521a69..0f7b48d3 100644 --- a/gns3server/compute/nios/nio_ethernet.py +++ b/gns3server/compute/nios/nio_ethernet.py @@ -48,7 +48,7 @@ class NIOEthernet(NIO): return "NIO Ethernet" - def __json__(self): + def asdict(self): return { "type": "nio_ethernet", diff --git a/gns3server/compute/nios/nio_tap.py b/gns3server/compute/nios/nio_tap.py index fee2d3d4..a81b0906 100644 --- a/gns3server/compute/nios/nio_tap.py +++ b/gns3server/compute/nios/nio_tap.py @@ -48,7 +48,7 @@ class NIOTAP(NIO): return "NIO TAP" - def __json__(self): + def asdict(self): return { "type": "nio_tap", diff --git a/gns3server/compute/nios/nio_udp.py b/gns3server/compute/nios/nio_udp.py index b9a87caf..32fe16b8 100644 --- a/gns3server/compute/nios/nio_udp.py +++ b/gns3server/compute/nios/nio_udp.py @@ -72,7 +72,7 @@ class NIOUDP(NIO): return "NIO UDP" - def __json__(self): + def asdict(self): return { "type": "nio_udp", diff --git a/gns3server/compute/port_manager.py b/gns3server/compute/port_manager.py index cdfaa72a..1dfa58e2 100644 --- a/gns3server/compute/port_manager.py +++ b/gns3server/compute/port_manager.py @@ -127,7 +127,7 @@ class PortManager: cls._instance = cls() return cls._instance - def __json__(self): + def asdict(self): return { "console_port_range": self._console_port_range, diff --git a/gns3server/compute/project.py b/gns3server/compute/project.py index 4f224f79..d2538d16 100644 --- a/gns3server/compute/project.py +++ b/gns3server/compute/project.py @@ -77,7 +77,7 @@ class Project: log.info(f"Project {self._id} with path '{self._path}' created") - def __json__(self): + def asdict(self): return { "name": self._name, diff --git a/gns3server/compute/qemu/qemu_vm.py b/gns3server/compute/qemu/qemu_vm.py index 87f825bb..ed0cbefa 100644 --- a/gns3server/compute/qemu/qemu_vm.py +++ b/gns3server/compute/qemu/qemu_vm.py @@ -1112,7 +1112,7 @@ class QemuVM(BaseNode): # In case user upload image manually we don't have md5 sums. # We need generate hashes at this point, otherwise they will be generated - # at __json__ but not on separate thread. + # at asdict but not on separate thread. await cancellable_wait_run_in_executor(md5sum, self._hda_disk_image) await cancellable_wait_run_in_executor(md5sum, self._hdb_disk_image) await cancellable_wait_run_in_executor(md5sum, self._hdc_disk_image) @@ -2506,7 +2506,7 @@ class QemuVM(BaseNode): raise QemuError(f"Invalid additional options: {additional_options} error {e}") return command - def __json__(self): + def asdict(self): answer = {"project_id": self.project.id, "node_id": self.id, "node_directory": self.working_path} # Qemu has a long list of options. The JSON schema is the single source of information for field in Qemu.schema()["properties"]: diff --git a/gns3server/compute/virtualbox/virtualbox_vm.py b/gns3server/compute/virtualbox/virtualbox_vm.py index 0174b32a..88719dd4 100644 --- a/gns3server/compute/virtualbox/virtualbox_vm.py +++ b/gns3server/compute/virtualbox/virtualbox_vm.py @@ -86,7 +86,7 @@ class VirtualBoxVM(BaseNode): self._ram = 0 self._adapter_type = "Intel PRO/1000 MT Desktop (82540EM)" - def __json__(self): + def asdict(self): json = { "name": self.name, diff --git a/gns3server/compute/vmware/vmware_vm.py b/gns3server/compute/vmware/vmware_vm.py index f2262e4c..6478f2a4 100644 --- a/gns3server/compute/vmware/vmware_vm.py +++ b/gns3server/compute/vmware/vmware_vm.py @@ -75,7 +75,7 @@ class VMwareVM(BaseNode): def ethernet_adapters(self): return self._ethernet_adapters - def __json__(self): + def asdict(self): json = { "name": self.name, diff --git a/gns3server/compute/vpcs/vpcs_vm.py b/gns3server/compute/vpcs/vpcs_vm.py index a5acb49f..3ca0e297 100644 --- a/gns3server/compute/vpcs/vpcs_vm.py +++ b/gns3server/compute/vpcs/vpcs_vm.py @@ -120,7 +120,7 @@ class VPCSVM(BaseNode): await self._check_vpcs_version() - def __json__(self): + def asdict(self): return { "name": self.name, diff --git a/gns3server/controller/__init__.py b/gns3server/controller/__init__.py index f7976b90..2774dd0f 100644 --- a/gns3server/controller/__init__.py +++ b/gns3server/controller/__init__.py @@ -355,12 +355,12 @@ class Controller: if connect: # call compute.connect() later to give time to the controller to be fully started asyncio.get_event_loop().call_later(1, lambda: asyncio.ensure_future(compute.connect())) - self.notification.controller_emit("compute.created", compute.__json__()) + self.notification.controller_emit("compute.created", compute.asdict()) return compute else: if connect: await self._computes[compute_id].connect() - self.notification.controller_emit("compute.updated", self._computes[compute_id].__json__()) + self.notification.controller_emit("compute.updated", self._computes[compute_id].asdict()) return self._computes[compute_id] async def close_compute_projects(self, compute): @@ -399,7 +399,7 @@ class Controller: await compute.close() del self._computes[compute_id] # self.save() - self.notification.controller_emit("compute.deleted", compute.__json__()) + self.notification.controller_emit("compute.deleted", compute.asdict()) @property def notification(self): diff --git a/gns3server/controller/appliance.py b/gns3server/controller/appliance.py index 47ac24cf..7913f0ab 100644 --- a/gns3server/controller/appliance.py +++ b/gns3server/controller/appliance.py @@ -60,7 +60,7 @@ class Appliance: def symbol(self, new_symbol): self._data["symbol"] = new_symbol - def __json__(self): + def asdict(self): """ Appliance data (a hash) """ diff --git a/gns3server/controller/appliance_manager.py b/gns3server/controller/appliance_manager.py index e98274e6..375c778a 100644 --- a/gns3server/controller/appliance_manager.py +++ b/gns3server/controller/appliance_manager.py @@ -104,7 +104,7 @@ class ApplianceManager: try: with open(path, encoding="utf-8") as f: appliance = Appliance(appliance_id, json.load(f), builtin=builtin) - json_data = appliance.__json__() # Check if loaded without error + json_data = appliance.asdict() # Check if loaded without error if appliance.status != "broken": self._appliances[appliance.id] = appliance if not appliance.symbol or appliance.symbol.startswith(":/symbols/"): diff --git a/gns3server/controller/compute.py b/gns3server/controller/compute.py index 4c5969f0..ebe969b7 100644 --- a/gns3server/controller/compute.py +++ b/gns3server/controller/compute.py @@ -162,7 +162,7 @@ class Compute: if self._http_session and not self._http_session.closed: await self._http_session.close() self._connected = False - self._controller.notification.controller_emit("compute.updated", self.__json__()) + self._controller.notification.controller_emit("compute.updated", self.asdict()) self._controller.save() async def close(self): @@ -290,7 +290,7 @@ class Compute: def disk_usage_percent(self): return self._disk_usage_percent - def __json__(self, topology_dump=False): + def asdict(self, topology_dump=False): """ :param topology_dump: Filter to keep only properties require for saving on disk """ @@ -444,7 +444,7 @@ class Compute: self._connected = True self._connection_failure = 0 self._last_error = None - self._controller.notification.controller_emit("compute.updated", self.__json__()) + self._controller.notification.controller_emit("compute.updated", self.asdict()) async def _connect_notification(self): """ @@ -466,7 +466,7 @@ class Compute: self._memory_usage_percent = event["memory_usage_percent"] self._disk_usage_percent = event["disk_usage_percent"] # FIXME: slow down number of compute events - self._controller.notification.controller_emit("compute.updated", self.__json__()) + self._controller.notification.controller_emit("compute.updated", self.asdict()) else: await self._controller.notification.dispatch( action, event, project_id=project_id, compute_id=self.id @@ -494,7 +494,7 @@ class Compute: self._cpu_usage_percent = None self._memory_usage_percent = None self._disk_usage_percent = None - self._controller.notification.controller_emit("compute.updated", self.__json__()) + self._controller.notification.controller_emit("compute.updated", self.asdict()) def _getUrl(self, path): host = self._host @@ -523,8 +523,8 @@ class Compute: if data == {}: data = None elif data is not None: - if hasattr(data, "__json__"): - data = json.dumps(data.__json__()) + if hasattr(data, "asdict"): + data = json.dumps(data.asdict()) elif isinstance(data, aiohttp.streams.EmptyStreamReader): data = None # Stream the request diff --git a/gns3server/controller/drawing.py b/gns3server/controller/drawing.py index 83e174da..ce858147 100644 --- a/gns3server/controller/drawing.py +++ b/gns3server/controller/drawing.py @@ -193,13 +193,13 @@ class Drawing: # To avoid spamming client with large data we don't send the svg if the SVG didn't change svg_changed = True setattr(self, prop, kwargs[prop]) - data = self.__json__() + data = self.asdict() if not svg_changed: del data["svg"] self._project.emit_notification("drawing.updated", data) self._project.dump() - def __json__(self, topology_dump=False): + def asdict(self, topology_dump=False): """ :param topology_dump: Filter to keep only properties require for saving on disk """ diff --git a/gns3server/controller/gns3vm/__init__.py b/gns3server/controller/gns3vm/__init__.py index e23f6999..0c3c9512 100644 --- a/gns3server/controller/gns3vm/__init__.py +++ b/gns3server/controller/gns3vm/__init__.py @@ -257,7 +257,7 @@ class GNS3VM: return self._engines["remote"] raise NotImplementedError(f"The engine {engine} for the GNS3 VM is not supported") - def __json__(self): + def asdict(self): return self._settings @locking diff --git a/gns3server/controller/link.py b/gns3server/controller/link.py index b2ab3819..d5a1bc41 100644 --- a/gns3server/controller/link.py +++ b/gns3server/controller/link.py @@ -160,14 +160,14 @@ class Link: self._filters = new_filters if self._created: await self.update() - self._project.emit_notification("link.updated", self.__json__()) + self._project.emit_notification("link.updated", self.asdict()) self._project.dump() async def update_suspend(self, value): if value != self._suspended: self._suspended = value await self.update() - self._project.emit_notification("link.updated", self.__json__()) + self._project.emit_notification("link.updated", self.asdict()) self._project.dump() @property @@ -231,7 +231,7 @@ class Link: n["node"].add_link(self) n["port"].link = self self._created = True - self._project.emit_notification("link.created", self.__json__()) + self._project.emit_notification("link.created", self.asdict()) if dump: self._project.dump() @@ -244,7 +244,7 @@ class Link: label = node_data.get("label") if label: port["label"] = label - self._project.emit_notification("link.updated", self.__json__()) + self._project.emit_notification("link.updated", self.asdict()) self._project.dump() async def create(self): @@ -286,7 +286,7 @@ class Link: self._capturing = True self._capture_file_name = capture_file_name - self._project.emit_notification("link.updated", self.__json__()) + self._project.emit_notification("link.updated", self.asdict()) async def stop_capture(self): """ @@ -294,7 +294,7 @@ class Link: """ self._capturing = False - self._project.emit_notification("link.updated", self.__json__()) + self._project.emit_notification("link.updated", self.asdict()) def pcap_streaming_url(self): """ @@ -417,7 +417,7 @@ class Link: def __hash__(self): return hash(self._id) - def __json__(self, topology_dump=False): + def asdict(self, topology_dump=False): """ :param topology_dump: Filter to keep only properties require for saving on disk """ diff --git a/gns3server/controller/node.py b/gns3server/controller/node.py index 0032b49a..ca086455 100644 --- a/gns3server/controller/node.py +++ b/gns3server/controller/node.py @@ -419,7 +419,7 @@ class Node: # When updating properties used only on controller we don't need to call the compute update_compute = False - old_json = self.__json__() + old_json = self.asdict() compute_properties = None # Update node properties with additional elements @@ -449,9 +449,9 @@ class Node: data = self._node_data(properties=compute_properties) response = await self.put(None, data=data) await self.parse_node_response(response.json) - elif old_json != self.__json__(): + elif old_json != self.asdict(): # We send notif only if object has changed - self.project.emit_notification("node.updated", self.__json__()) + self.project.emit_notification("node.updated", self.asdict()) self.project.dump() async def parse_node_response(self, response): @@ -777,7 +777,7 @@ class Node: return False return self.id == other.id and other.project.id == self.project.id - def __json__(self, topology_dump=False): + def asdict(self, topology_dump=False): """ :param topology_dump: Filter to keep only properties required for saving on disk """ @@ -817,7 +817,7 @@ class Node: "status": self._status, "console_host": str(self._compute.console_host), "node_directory": self._node_directory, - "ports": [port.__json__() for port in self.ports] + "ports": [port.asdict() for port in self.ports] } topology.update(additional_data) return topology diff --git a/gns3server/controller/notification.py b/gns3server/controller/notification.py index c7aaf0b4..48d5a3d2 100644 --- a/gns3server/controller/notification.py +++ b/gns3server/controller/notification.py @@ -98,7 +98,7 @@ class Notification: project = self._controller.get_project(event["project_id"]) node = project.get_node(event["node_id"]) await node.parse_node_response(event) - self.project_emit("node.updated", node.__json__()) + self.project_emit("node.updated", node.asdict()) except ControllerError: # Project closing return elif action == "ping": diff --git a/gns3server/controller/ports/port.py b/gns3server/controller/ports/port.py index 9db29c45..e0a0406b 100644 --- a/gns3server/controller/ports/port.py +++ b/gns3server/controller/ports/port.py @@ -93,7 +93,7 @@ class Port: def short_name(self, val): self._short_name = val - def __json__(self): + def asdict(self): info = { "name": self._name, "short_name": self.short_name, diff --git a/gns3server/controller/project.py b/gns3server/controller/project.py index 38131609..51146194 100644 --- a/gns3server/controller/project.py +++ b/gns3server/controller/project.py @@ -169,14 +169,14 @@ class Project: :param kwargs: Project properties """ - old_json = self.__json__() + old_json = self.asdict() for prop in kwargs: setattr(self, prop, kwargs[prop]) # We send notif only if object has changed - if old_json != self.__json__(): - self.emit_notification("project.updated", self.__json__()) + if old_json != self.asdict(): + self.emit_notification("project.updated", self.asdict()) self.dump() # update on computes @@ -589,7 +589,7 @@ class Project: node = await self._create_node(compute, name, node_id, node_type, **kwargs) else: node = await self._create_node(compute, name, node_id, node_type, **kwargs) - self.emit_notification("node.created", node.__json__()) + self.emit_notification("node.created", node.asdict()) if dump: self.dump() return node @@ -618,7 +618,7 @@ class Project: # refresh the compute IDs list self._computes = [n.compute.id for n in self.nodes.values()] self.dump() - self.emit_notification("node.deleted", node.__json__()) + self.emit_notification("node.deleted", node.asdict()) @open_required def get_node(self, node_id): @@ -683,7 +683,7 @@ class Project: if drawing_id not in self._drawings: drawing = Drawing(self, drawing_id=drawing_id, **kwargs) self._drawings[drawing.id] = drawing - self.emit_notification("drawing.created", drawing.__json__()) + self.emit_notification("drawing.created", drawing.asdict()) if dump: self.dump() return drawing @@ -706,7 +706,7 @@ class Project: raise ControllerError(f"Drawing ID {drawing_id} cannot be deleted because it is locked") del self._drawings[drawing.id] self.dump() - self.emit_notification("drawing.deleted", drawing.__json__()) + self.emit_notification("drawing.deleted", drawing.asdict()) @open_required async def add_link(self, link_id=None, dump=True): @@ -733,7 +733,7 @@ class Project: if force_delete is False: raise self.dump() - self.emit_notification("link.deleted", link.__json__()) + self.emit_notification("link.deleted", link.asdict()) @open_required def get_link(self, link_id): @@ -810,7 +810,7 @@ class Project: self._clean_pictures() self._status = "closed" if not ignore_notification: - self.emit_notification("project.closed", self.__json__()) + self.emit_notification("project.closed", self.asdict()) self.reset() self._closing = False @@ -1180,7 +1180,7 @@ class Project: if node.status != "stopped" and not node.is_always_running(): raise ControllerError("Cannot duplicate node data while the node is running") - data = copy.deepcopy(node.__json__(topology_dump=True)) + data = copy.deepcopy(node.asdict(topology_dump=True)) # Some properties like internal ID should not be duplicated for unique_property in ( "node_id", @@ -1220,7 +1220,7 @@ class Project: "snapshots": len(self._snapshots), } - def __json__(self): + def asdict(self): return { "name": self._name, "project_id": self._id, diff --git a/gns3server/controller/snapshot.py b/gns3server/controller/snapshot.py index 35883f93..5b389d74 100644 --- a/gns3server/controller/snapshot.py +++ b/gns3server/controller/snapshot.py @@ -133,10 +133,10 @@ class Snapshot: except (OSError, PermissionError) as e: raise ControllerError(str(e)) await project.open() - self._project.emit_notification("snapshot.restored", self.__json__()) + self._project.emit_notification("snapshot.restored", self.asdict()) return self._project - def __json__(self): + def asdict(self): return { "snapshot_id": self._id, "name": self._name, diff --git a/gns3server/controller/topology.py b/gns3server/controller/topology.py index 7b3134e8..80f63129 100644 --- a/gns3server/controller/topology.py +++ b/gns3server/controller/topology.py @@ -30,6 +30,10 @@ from ..version import __version__ from ..utils.qt import qt_font_to_style from ..compute.dynamips import PLATFORMS_DEFAULT_RAM from .controller_error import ControllerError +from .compute import Compute +from .drawing import Drawing +from .node import Node +from .link import Link from gns3server.schemas.controller.topology import Topology from gns3server.schemas.compute.dynamips_nodes import DynamipsCreate @@ -89,23 +93,23 @@ def project_to_topology(project): } for node in project.nodes.values(): - if hasattr(node, "__json__"): - data["topology"]["nodes"].append(node.__json__(topology_dump=True)) + if isinstance(node, Node): + data["topology"]["nodes"].append(node.asdict(topology_dump=True)) else: data["topology"]["nodes"].append(node) for link in project.links.values(): - if hasattr(link, "__json__"): - data["topology"]["links"].append(link.__json__(topology_dump=True)) + if isinstance(link, Link): + data["topology"]["links"].append(link.asdict(topology_dump=True)) else: data["topology"]["links"].append(link) for drawing in project.drawings.values(): - if hasattr(drawing, "__json__"): - data["topology"]["drawings"].append(drawing.__json__(topology_dump=True)) + if isinstance(drawing, Drawing): + data["topology"]["drawings"].append(drawing.asdict(topology_dump=True)) else: data["topology"]["drawings"].append(drawing) for compute in project.computes: - if hasattr(compute, "__json__"): - compute = compute.__json__(topology_dump=True) + if isinstance(compute, Compute): + compute = compute.asdict(topology_dump=True) if compute["compute_id"] not in ( "vm", "local", diff --git a/gns3server/utils/notification_queue.py b/gns3server/utils/notification_queue.py index e5371b0a..e198ffc9 100644 --- a/gns3server/utils/notification_queue.py +++ b/gns3server/utils/notification_queue.py @@ -71,8 +71,8 @@ class NotificationQueue(asyncio.Queue): Get a message as a JSON """ (action, msg, kwargs) = await self.get(timeout) - if hasattr(msg, "__json__"): - msg = {"action": action, "event": msg.__json__()} + if hasattr(msg, "asdict"): + msg = {"action": action, "event": msg.asdict()} else: msg = {"action": action, "event": msg} msg.update(kwargs) diff --git a/tests/compute/builtin/nodes/test_cloud.py b/tests/compute/builtin/nodes/test_cloud.py index fd0684e8..a45718fe 100644 --- a/tests/compute/builtin/nodes/test_cloud.py +++ b/tests/compute/builtin/nodes/test_cloud.py @@ -51,7 +51,7 @@ async def test_json_with_ports(on_gns3vm, compute_project, manager): } ] cloud = Cloud("cloud1", str(uuid.uuid4()), compute_project, manager, ports=ports) - assert cloud.__json__() == { + assert cloud.asdict() == { "name": "cloud1", "usage": "", "node_id": cloud.id, @@ -84,7 +84,7 @@ def test_json_without_ports(on_gns3vm, compute_project, manager): """ cloud = Cloud("cloud1", str(uuid.uuid4()), compute_project, manager, ports=None) - assert cloud.__json__() == { + assert cloud.asdict() == { "name": "cloud1", "usage": "", "node_id": cloud.id, diff --git a/tests/compute/builtin/nodes/test_nat.py b/tests/compute/builtin/nodes/test_nat.py index c26a87f7..60f2da95 100644 --- a/tests/compute/builtin/nodes/test_nat.py +++ b/tests/compute/builtin/nodes/test_nat.py @@ -24,7 +24,7 @@ from gns3server.compute.builtin.nodes.nat import Nat def test_json_gns3vm(on_gns3vm, compute_project): nat = Nat("nat1", str(uuid.uuid4()), compute_project, MagicMock()) - assert nat.__json__() == { + assert nat.asdict() == { "name": "nat1", "usage": "", "node_id": nat.id, @@ -47,7 +47,7 @@ def test_json_darwin(darwin_platform, compute_project): {"name": "eth0", "special": False, "type": "ethernet"}, {"name": "vmnet8", "special": True, "type": "ethernet"}]): nat = Nat("nat1", str(uuid.uuid4()), compute_project, MagicMock()) - assert nat.__json__() == { + assert nat.asdict() == { "name": "nat1", "usage": "", "node_id": nat.id, @@ -68,7 +68,7 @@ def test_json_windows_with_full_name_of_interface(windows_platform, project): with patch("gns3server.utils.interfaces.interfaces", return_value=[ {"name": "VMware Network Adapter VMnet8", "special": True, "type": "ethernet"}]): nat = Nat("nat1", str(uuid.uuid4()), project, MagicMock()) - assert nat.__json__() == { + assert nat.asdict() == { "name": "nat1", "usage": "", "node_id": nat.id, diff --git a/tests/compute/docker/test_docker_vm.py b/tests/compute/docker/test_docker_vm.py index 5226c802..8bc99ff3 100644 --- a/tests/compute/docker/test_docker_vm.py +++ b/tests/compute/docker/test_docker_vm.py @@ -53,7 +53,7 @@ async def vm(compute_project, manager): def test_json(vm, compute_project): - assert vm.__json__() == { + assert vm.asdict() == { 'container_id': 'e90e34656842', 'image': 'ubuntu:latest', 'name': 'test', diff --git a/tests/compute/qemu/test_qemu_vm.py b/tests/compute/qemu/test_qemu_vm.py index 0ef3f6bb..4ca8a766 100644 --- a/tests/compute/qemu/test_qemu_vm.py +++ b/tests/compute/qemu/test_qemu_vm.py @@ -472,7 +472,7 @@ async def test_set_process_priority_normal(vm, fake_qemu_img_binary): def test_json(vm, compute_project): - json = vm.__json__() + json = vm.asdict() assert json["name"] == vm.name assert json["project_id"] == compute_project.id diff --git a/tests/compute/test_project.py b/tests/compute/test_project.py index dd692784..f7b643d1 100644 --- a/tests/compute/test_project.py +++ b/tests/compute/test_project.py @@ -112,7 +112,7 @@ async def test_variables(): async def test_json(): p = Project(project_id=str(uuid4())) - assert p.__json__() == { + assert p.asdict() == { "name": p.name, "project_id": p.id, "variables": None @@ -124,7 +124,7 @@ async def test_json_with_variables(): variables = [{"name": "VAR1", "value": "VAL1"}] p = Project(project_id=str(uuid4()), variables=variables) - assert p.__json__() == { + assert p.asdict() == { "name": p.name, "project_id": p.id, "variables": variables diff --git a/tests/compute/virtualbox/test_virtualbox_vm.py b/tests/compute/virtualbox/test_virtualbox_vm.py index 80714f26..a69660db 100644 --- a/tests/compute/virtualbox/test_virtualbox_vm.py +++ b/tests/compute/virtualbox/test_virtualbox_vm.py @@ -107,10 +107,10 @@ async def test_vm_adapter_add_nio_binding_adapter_not_exist(vm, manager, free_co def test_json(vm, tmpdir, project): - assert vm.__json__()["node_directory"] is None + assert vm.asdict()["node_directory"] is None project._path = str(tmpdir) vm._linked_clone = True - assert vm.__json__()["node_directory"] is not None + assert vm.asdict()["node_directory"] is not None def test_patch_vm_uuid(vm): diff --git a/tests/compute/vmware/test_vmware_vm.py b/tests/compute/vmware/test_vmware_vm.py index e9bc12bc..6b797c7b 100644 --- a/tests/compute/vmware/test_vmware_vm.py +++ b/tests/compute/vmware/test_vmware_vm.py @@ -49,10 +49,10 @@ async def test_vm(vm): @pytest.mark.asyncio async def test_json(vm, tmpdir, compute_project): - assert vm.__json__()["node_directory"] is not None + assert vm.asdict()["node_directory"] is not None compute_project._path = str(tmpdir) vm._linked_clone = True - assert vm.__json__()["node_directory"] is not None + assert vm.asdict()["node_directory"] is not None @pytest.mark.asyncio diff --git a/tests/controller/test_compute.py b/tests/controller/test_compute.py index 610e5aa2..fc229cff 100644 --- a/tests/controller/test_compute.py +++ b/tests/controller/test_compute.py @@ -121,7 +121,7 @@ async def test_compute_httpQueryAuth(compute): # mock.assert_any_call("POST", "https://example.com:84/v2/compute/projects", data=b'{"a": "b"}', headers={'content-type': 'application/json'}, auth=None, chunked=None, timeout=20) # #assert compute._connected # assert compute._capabilities["version"] == __version__ -# controller.notification.controller_emit.assert_called_with("compute.updated", compute.__json__()) +# controller.notification.controller_emit.assert_called_with("compute.updated", compute.asdict()) # await compute.close() @@ -148,7 +148,7 @@ async def test_compute_httpQueryAuth(compute): # assert controller.gns3vm.start.called # #assert compute._connected # assert compute._capabilities["version"] == __version__ -# controller.notification.controller_emit.assert_called_with("compute.updated", compute.__json__()) +# controller.notification.controller_emit.assert_called_with("compute.updated", compute.asdict()) # await compute.close() @@ -226,7 +226,7 @@ async def test_compute_httpQuery_project(compute): response.status = 200 project = Project(name="Test") await compute.post("/projects", project) - mock.assert_called_with("POST", "https://example.com:84/v3/compute/projects", data=json.dumps(project.__json__()), headers={'content-type': 'application/json'}, auth=None, chunked=None, timeout=20) + mock.assert_called_with("POST", "https://example.com:84/v3/compute/projects", data=json.dumps(project.asdict()), headers={'content-type': 'application/json'}, auth=None, chunked=None, timeout=20) await compute.close() # FIXME: https://github.com/aio-libs/aiohttp/issues/2525 @@ -298,7 +298,7 @@ async def test_compute_httpQuery_project(compute): async def test_json(compute): compute.user = "test" - assert compute.__json__() == { + assert compute.asdict() == { "compute_id": "my_compute_id", "name": compute.name, "protocol": "https", @@ -319,7 +319,7 @@ async def test_json(compute): "node_types": [] } } - assert compute.__json__(topology_dump=True) == { + assert compute.asdict(topology_dump=True) == { "compute_id": "my_compute_id", "name": compute.name, "protocol": "https", @@ -358,7 +358,7 @@ async def test_update(compute, controller): await compute.update(name="Test 2") assert compute.name == "Test 2" assert compute.host == "example.org" - controller.notification.controller_emit.assert_called_with("compute.updated", compute.__json__()) + controller.notification.controller_emit.assert_called_with("compute.updated", compute.asdict()) assert compute.connected is False assert compute._controller.save.called diff --git a/tests/controller/test_controller.py b/tests/controller/test_controller.py index b39b8bca..4b26b054 100644 --- a/tests/controller/test_controller.py +++ b/tests/controller/test_controller.py @@ -36,7 +36,7 @@ from gns3server.version import __version__ # data = json.load(f) # assert data["version"] == __version__ # assert data["iou_license"] == controller.iou_license -# assert data["gns3vm"] == controller.gns3vm.__json__() +# assert data["gns3vm"] == controller.gns3vm.asdict() # # # def test_load_controller_settings(controller, controller_config_path): @@ -115,10 +115,10 @@ async def test_add_compute(controller): controller._notification = MagicMock() c = await controller.add_compute(compute_id="test1", connect=False) - controller._notification.controller_emit.assert_called_with("compute.created", c.__json__()) + controller._notification.controller_emit.assert_called_with("compute.created", c.asdict()) assert len(controller.computes) == 1 await controller.add_compute(compute_id="test1", connect=False) - controller._notification.controller_emit.assert_called_with("compute.updated", c.__json__()) + controller._notification.controller_emit.assert_called_with("compute.updated", c.asdict()) assert len(controller.computes) == 1 await controller.add_compute(compute_id="test2", connect=False) assert len(controller.computes) == 2 @@ -156,7 +156,7 @@ async def test_deleteComputeProjectOpened(controller, controller_config_path): c._connected = True await controller.delete_compute("test1") assert len(controller.computes) == 0 - controller._notification.controller_emit.assert_called_with("compute.deleted", c.__json__()) + controller._notification.controller_emit.assert_called_with("compute.deleted", c.asdict()) assert c.connected is False # Project 1 use this compute it should be close before deleting the compute @@ -383,12 +383,12 @@ def test_appliances(controller, config, tmpdir): controller.appliance_manager.load_appliances() assert len(controller.appliance_manager.appliances) > 0 for appliance in controller.appliance_manager.appliances.values(): - assert appliance.__json__()["status"] != "broken" - assert "Alpine Linux" in [c.__json__()["name"] for c in controller.appliance_manager.appliances.values()] - assert "My Appliance" in [c.__json__()["name"] for c in controller.appliance_manager.appliances.values()] + assert appliance.asdict()["status"] != "broken" + assert "Alpine Linux" in [c.asdict()["name"] for c in controller.appliance_manager.appliances.values()] + assert "My Appliance" in [c.asdict()["name"] for c in controller.appliance_manager.appliances.values()] for c in controller.appliance_manager.appliances.values(): - j = c.__json__() + j = c.asdict() if j["name"] == "Alpine Linux": assert j["builtin"] elif j["name"] == "My Appliance": diff --git a/tests/controller/test_drawing.py b/tests/controller/test_drawing.py index 2a422d48..f8ea464a 100644 --- a/tests/controller/test_drawing.py +++ b/tests/controller/test_drawing.py @@ -47,7 +47,7 @@ def test_init_with_uuid(project): def test_json(project): i = Drawing(project, None, svg="") - assert i.__json__() == { + assert i.asdict() == { "drawing_id": i.id, "project_id": project.id, "x": i.x, @@ -57,7 +57,7 @@ def test_json(project): "svg": i.svg, "rotation": i.rotation } - assert i.__json__(topology_dump=True) == { + assert i.asdict(topology_dump=True) == { "drawing_id": i.id, "x": i.x, "y": i.y, diff --git a/tests/controller/test_gns3vm.py b/tests/controller/test_gns3vm.py index 6e818b55..ed886250 100644 --- a/tests/controller/test_gns3vm.py +++ b/tests/controller/test_gns3vm.py @@ -66,7 +66,7 @@ async def test_list(controller): async def test_json(controller): vm = GNS3VM(controller) - assert vm.__json__() == vm._settings + assert vm.asdict() == vm._settings @pytest.mark.skipif(sys.platform.startswith("win"), reason="Not working well on Windows") diff --git a/tests/controller/test_link.py b/tests/controller/test_link.py index 450847e2..b9cf8f75 100644 --- a/tests/controller/test_link.py +++ b/tests/controller/test_link.py @@ -81,7 +81,7 @@ async def test_add_node(project, compute): await link.add_node(node2, 0, 4) assert link.create.called - link._project.emit_notification.assert_called_with("link.created", link.__json__()) + link._project.emit_notification.assert_called_with("link.created", link.asdict()) assert link in node2.links @@ -197,7 +197,7 @@ async def test_json(project, compute): link.create = AsyncioMagicMock() await link.add_node(node1, 0, 4) await link.add_node(node2, 1, 3) - assert link.__json__() == { + assert link.asdict() == { "link_id": link.id, "project_id": project.id, "nodes": [ @@ -228,7 +228,7 @@ async def test_json(project, compute): "capture_file_path": None, "capture_compute_id": None } - assert link.__json__(topology_dump=True) == { + assert link.asdict(topology_dump=True) == { "link_id": link.id, "nodes": [ { @@ -267,7 +267,7 @@ async def test_json_serial_link(project, compute): link.create = AsyncioMagicMock() await link.add_node(node1, 0, 4) await link.add_node(node2, 1, 3) - assert link.__json__()["link_type"] == "serial" + assert link.asdict()["link_type"] == "serial" @pytest.mark.asyncio @@ -297,7 +297,7 @@ async def test_start_capture(link): await link.start_capture(capture_file_name="test.pcap") assert link._capturing assert link._capture_file_name == "test.pcap" - link._project.emit_notification.assert_called_with("link.updated", link.__json__()) + link._project.emit_notification.assert_called_with("link.updated", link.asdict()) @pytest.mark.asyncio @@ -307,7 +307,7 @@ async def test_stop_capture(link): link._project.emit_notification = MagicMock() await link.stop_capture() assert link._capturing is False - link._project.emit_notification.assert_called_with("link.updated", link.__json__()) + link._project.emit_notification.assert_called_with("link.updated", link.asdict()) @pytest.mark.asyncio diff --git a/tests/controller/test_node.py b/tests/controller/test_node.py index 2118e7b6..2192444b 100644 --- a/tests/controller/test_node.py +++ b/tests/controller/test_node.py @@ -112,7 +112,7 @@ async def test_eq(compute, project, node, controller): def test_json(node, compute): - assert node.__json__() == { + assert node.asdict() == { "compute_id": str(compute.id), "project_id": node.project.id, "node_id": node.id, @@ -153,7 +153,7 @@ def test_json(node, compute): ] } - assert node.__json__(topology_dump=True) == { + assert node.asdict(topology_dump=True) == { "compute_id": str(compute.id), "node_id": node.id, "template_id": None, @@ -327,7 +327,7 @@ async def test_update(node, compute, project, controller): assert node._console == 2048 assert node.x == 42 assert node._properties == {"startup_script": "echo test"} - #controller._notification.emit.assert_called_with("node.updated", node.__json__()) + #controller._notification.emit.assert_called_with("node.updated", node.asdict()) assert project.dump.called @@ -355,7 +355,7 @@ async def test_update_properties(node, compute, controller): # The notif should contain the old properties because it's the compute that will emit # the correct info - #node_notif = copy.deepcopy(node.__json__()) + #node_notif = copy.deepcopy(node.asdict()) #node_notif["properties"]["startup_script"] = "echo test" #controller._notification.emit.assert_called_with("node.updated", node_notif) @@ -373,7 +373,7 @@ async def test_update_only_controller(node, compute): await node.update(x=42) assert not compute.put.called assert node.x == 42 - node._project.emit_notification.assert_called_with("node.updated", node.__json__()) + node._project.emit_notification.assert_called_with("node.updated", node.asdict()) # If nothing change a second notif should not be sent node._project.emit_notification = AsyncioMagicMock() diff --git a/tests/controller/test_node_port_name.py b/tests/controller/test_node_port_name.py index 03b4c4d8..0e254f9a 100644 --- a/tests/controller/test_node_port_name.py +++ b/tests/controller/test_node_port_name.py @@ -45,7 +45,7 @@ def test_list_ports(node): List port by default """ - assert node.__json__()["ports"] == [ + assert node.asdict()["ports"] == [ { "name": "Ethernet0", "short_name": "e0", @@ -63,7 +63,7 @@ def test_list_ports_vpcs(node): """ node._node_type = "vpcs" - assert node.__json__()["ports"] == [ + assert node.asdict()["ports"] == [ { "name": "Ethernet0", "short_name": "e0", @@ -82,7 +82,7 @@ def test_list_ports_docker(node): node._node_type = "docker" node._properties["adapters"] = 2 - assert node.__json__()["ports"] == [ + assert node.asdict()["ports"] == [ { "name": "eth0", "short_name": "eth0", @@ -110,26 +110,26 @@ def test_list_ports_port_name_format(node): node._first_port_name = None node._port_name_format = "eth{}" node._list_ports() - assert node.__json__()["ports"][0]["name"] == "eth0" + assert node.asdict()["ports"][0]["name"] == "eth0" node._port_name_format = "eth{port0}" node._list_ports() - assert node.__json__()["ports"][0]["name"] == "eth0" + assert node.asdict()["ports"][0]["name"] == "eth0" node._port_name_format = "eth{port1}" node._list_ports() - assert node.__json__()["ports"][0]["name"] == "eth1" + assert node.asdict()["ports"][0]["name"] == "eth1" node._first_port_name = "" node._port_segment_size = 2 node._port_name_format = "eth{segment0}/{port0}" node.properties["adapters"] = 8 node._list_ports() - assert node.__json__()["ports"][6]["name"] == "eth3/0" - assert node.__json__()["ports"][7]["name"] == "eth3/1" + assert node.asdict()["ports"][6]["name"] == "eth3/0" + assert node.asdict()["ports"][7]["name"] == "eth3/1" node._first_port_name = "mgnt0" node._list_ports() - assert node.__json__()["ports"][0]["name"] == "mgnt0" - assert node.__json__()["ports"][1]["name"] == "eth0/0" + assert node.asdict()["ports"][0]["name"] == "mgnt0" + assert node.asdict()["ports"][1]["name"] == "eth0/0" def test_list_ports_adapters(node): @@ -138,7 +138,7 @@ def test_list_ports_adapters(node): """ node.properties["adapters"] = 2 - assert node.__json__()["ports"] == [ + assert node.asdict()["ports"] == [ { "name": "Ethernet0", "short_name": "e0", @@ -175,7 +175,7 @@ def test_list_ports_adapters_cloud(project, compute): } ] - assert node.__json__()["ports"] == [ + assert node.asdict()["ports"] == [ { "name": "eth0", "short_name": "eth0", @@ -206,7 +206,7 @@ def test_list_ports_ethernet_hub(project, compute): } ] - assert node.__json__()["ports"] == [ + assert node.asdict()["ports"] == [ { "name": "Ethernet0", "short_name": "e0", @@ -238,7 +238,7 @@ def test_list_ports_atm_switch(project, compute): "1:0:100": "10:0:200" } - assert node.__json__()["ports"] == [ + assert node.asdict()["ports"] == [ { "name": "1", "short_name": "1", @@ -271,7 +271,7 @@ def test_list_ports_frame_relay_switch(project, compute): "2:102": "11:203" } - assert node.__json__()["ports"] == [ + assert node.asdict()["ports"] == [ { "name": "1", "short_name": "1", @@ -317,7 +317,7 @@ def test_list_ports_iou(compute, project): node_type="iou") node.properties["serial_adapters"] = 2 node.properties["ethernet_adapters"] = 3 - assert node.__json__()["ports"] == [ + assert node.asdict()["ports"] == [ { "name": "Ethernet0/0", "short_name": "e0/0", @@ -526,7 +526,7 @@ def test_list_ports_dynamips(project, compute): node.properties["wic0"] = "WIC-2T" node.properties["wic1"] = "WIC-2T" - assert node.__json__()["ports"] == [ + assert node.asdict()["ports"] == [ { "name": "FastEthernet0/0", "short_name": "f0/0", diff --git a/tests/controller/test_notification.py b/tests/controller/test_notification.py index b526143a..17be9d79 100644 --- a/tests/controller/test_notification.py +++ b/tests/controller/test_notification.py @@ -126,4 +126,4 @@ def test_various_notification(controller, node): notif.project_emit("log.info", {"message": "Image uploaded"}) notif.project_emit("log.warning", {"message": "Warning ASA 8 is not officially supported by GNS3"}) notif.project_emit("log.error", {"message": "Permission denied on /tmp"}) - notif.project_emit("node.updated", node.__json__()) + notif.project_emit("node.updated", node.asdict()) diff --git a/tests/controller/test_project.py b/tests/controller/test_project.py index 0e4b81c6..d196a1eb 100644 --- a/tests/controller/test_project.py +++ b/tests/controller/test_project.py @@ -59,7 +59,7 @@ async def test_json(): p = Project(name="Test") - assert p.__json__() == { + assert p.asdict() == { "name": "Test", "project_id": p.id, "path": p.path, @@ -90,7 +90,7 @@ async def test_update(controller): assert project.name == "Hello" await project.update(name="World") assert project.name == "World" - project.emit_notification.assert_any_call("project.updated", project.__json__()) + project.emit_notification.assert_any_call("project.updated", project.asdict()) @pytest.mark.asyncio @@ -180,7 +180,7 @@ async def test_add_node_local(controller): 'name': 'test'}, timeout=1200) assert compute in project._project_created_on_compute - project.emit_notification.assert_any_call("node.created", node.__json__()) + project.emit_notification.assert_any_call("node.created", node.asdict()) @pytest.mark.asyncio @@ -208,7 +208,7 @@ async def test_add_node_non_local(controller): 'startup_script': 'test.cfg', 'name': 'test'}, timeout=1200) assert compute in project._project_created_on_compute - project.emit_notification.assert_any_call("node.created", node.__json__()) + project.emit_notification.assert_any_call("node.created", node.asdict()) @pytest.mark.asyncio @@ -373,7 +373,7 @@ async def test_add_node_iou_no_id_available(controller): # }) # # assert compute in project._project_created_on_compute -# project.emit_notification.assert_any_call("node.created", node.__json__()) +# project.emit_notification.assert_any_call("node.created", node.asdict()) # # # @pytest.mark.asyncio @@ -392,7 +392,7 @@ async def test_add_node_iou_no_id_available(controller): # }, builtin=True) # # controller.template_manager.templates[template.id] = template -# template.__json__() +# template.asdict() # controller._computes["local"] = compute # # response = MagicMock() @@ -407,7 +407,7 @@ async def test_add_node_iou_no_id_available(controller): # }) # # assert compute in project._project_created_on_compute -# project.emit_notification.assert_any_call("node.created", node.__json__()) +# project.emit_notification.assert_any_call("node.created", node.asdict()) @pytest.mark.asyncio @@ -429,7 +429,7 @@ async def test_delete_node(controller): assert node.id not in project._nodes compute.delete.assert_any_call('/projects/{}/vpcs/nodes/{}'.format(project.id, node.id)) - project.emit_notification.assert_any_call("node.deleted", node.__json__()) + project.emit_notification.assert_any_call("node.deleted", node.asdict()) @pytest.mark.asyncio @@ -476,8 +476,8 @@ async def test_delete_node_delete_link(controller): assert link.id not in project._links compute.delete.assert_any_call('/projects/{}/vpcs/nodes/{}'.format(project.id, node.id)) - project.emit_notification.assert_any_call("node.deleted", node.__json__()) - project.emit_notification.assert_any_call("link.deleted", link.__json__()) + project.emit_notification.assert_any_call("node.deleted", node.asdict()) + project.emit_notification.assert_any_call("link.deleted", link.asdict()) @pytest.mark.asyncio @@ -541,7 +541,7 @@ async def test_add_link(project): await link.add_node(vm2, 4, 2) assert mock_udp_create.called assert len(link._nodes) == 2 - project.emit_notification.assert_any_call("link.created", link.__json__()) + project.emit_notification.assert_any_call("link.created", link.asdict()) @pytest.mark.asyncio @@ -587,7 +587,7 @@ async def test_delete_link(project): assert len(project._links) == 1 project.emit_notification = MagicMock() await project.delete_link(link.id) - project.emit_notification.assert_any_call("link.deleted", link.__json__()) + project.emit_notification.assert_any_call("link.deleted", link.asdict()) assert len(project._links) == 0 @@ -597,7 +597,7 @@ async def test_add_drawing(project): project.emit_notification = MagicMock() drawing = await project.add_drawing(None, svg="") assert len(project._drawings) == 1 - project.emit_notification.assert_any_call("drawing.created", drawing.__json__()) + project.emit_notification.assert_any_call("drawing.created", drawing.asdict()) @pytest.mark.asyncio @@ -628,7 +628,7 @@ async def test_delete_drawing(project): assert len(project._drawings) == 1 project.emit_notification = MagicMock() await project.delete_drawing(drawing.id) - project.emit_notification.assert_any_call("drawing.deleted", drawing.__json__()) + project.emit_notification.assert_any_call("drawing.deleted", drawing.asdict()) assert len(project._drawings) == 0 @@ -703,7 +703,7 @@ async def test_open_close(controller): project.emit_notification = MagicMock() await project.close() assert project.status == "closed" - project.emit_notification.assert_any_call("project.closed", project.__json__()) + project.emit_notification.assert_any_call("project.closed", project.asdict()) @pytest.mark.asyncio diff --git a/tests/controller/test_snapshot.py b/tests/controller/test_snapshot.py index fa6d2237..6223e188 100644 --- a/tests/controller/test_snapshot.py +++ b/tests/controller/test_snapshot.py @@ -62,7 +62,7 @@ def test_snapshot_filename(project): def test_json(project): snapshot = Snapshot(project, filename="test1_260716_100439.gns3project") - assert snapshot.__json__() == { + assert snapshot.asdict() == { "snapshot_id": snapshot._id, "name": "test1", "project_id": project.id, diff --git a/tests/controller/test_topology.py b/tests/controller/test_topology.py index a0707335..c5688648 100644 --- a/tests/controller/test_topology.py +++ b/tests/controller/test_topology.py @@ -82,10 +82,10 @@ async def test_basic_topology(controller): topo = project_to_topology(project) assert len(topo["topology"]["nodes"]) == 2 - assert node1.__json__(topology_dump=True) in topo["topology"]["nodes"] - assert topo["topology"]["links"][0] == link.__json__(topology_dump=True) - assert topo["topology"]["computes"][0] == compute.__json__(topology_dump=True) - assert topo["topology"]["drawings"][0] == drawing.__json__(topology_dump=True) + assert node1.asdict(topology_dump=True) in topo["topology"]["nodes"] + assert topo["topology"]["links"][0] == link.asdict(topology_dump=True) + assert topo["topology"]["computes"][0] == compute.asdict(topology_dump=True) + assert topo["topology"]["drawings"][0] == drawing.asdict(topology_dump=True) @pytest.mark.asyncio From 9404c00411abc811201ef976668a8a82d943cc37 Mon Sep 17 00:00:00 2001 From: grossmj Date: Sun, 18 Apr 2021 15:40:38 +0930 Subject: [PATCH 06/10] Complete type annotations for API endpoints. --- .../api/routes/compute/atm_switch_nodes.py | 58 +++++++++----- gns3server/api/routes/compute/capabilities.py | 2 +- gns3server/api/routes/compute/cloud_nodes.py | 69 +++++++++++------ gns3server/api/routes/compute/compute.py | 14 ++-- gns3server/api/routes/compute/docker_nodes.py | 63 ++++++++++------ .../api/routes/compute/dynamips_nodes.py | 59 +++++++++------ .../api/routes/compute/ethernet_hub_nodes.py | 60 ++++++++++----- .../routes/compute/ethernet_switch_nodes.py | 66 ++++++++++------ .../compute/frame_relay_switch_nodes.py | 69 +++++++++++------ gns3server/api/routes/compute/images.py | 12 +-- gns3server/api/routes/compute/iou_nodes.py | 48 +++++++----- gns3server/api/routes/compute/nat_nodes.py | 69 +++++++++++------ .../api/routes/compute/notifications.py | 2 +- gns3server/api/routes/compute/projects.py | 23 +++--- gns3server/api/routes/compute/qemu_nodes.py | 75 ++++++++++++------- .../api/routes/compute/virtualbox_nodes.py | 75 ++++++++++++------- gns3server/api/routes/compute/vmware_nodes.py | 68 +++++++++++------ gns3server/api/routes/compute/vpcs_nodes.py | 73 ++++++++++++------ .../api/routes/controller/appliances.py | 4 +- gns3server/api/routes/controller/computes.py | 12 +-- .../api/routes/controller/controller.py | 13 ++-- gns3server/api/routes/controller/drawings.py | 10 +-- gns3server/api/routes/controller/gns3vm.py | 9 ++- gns3server/api/routes/controller/links.py | 22 +++--- gns3server/api/routes/controller/nodes.py | 52 ++++++------- .../api/routes/controller/notifications.py | 4 +- gns3server/api/routes/controller/projects.py | 45 ++++++----- gns3server/api/routes/controller/snapshots.py | 13 ++-- gns3server/api/routes/controller/symbols.py | 11 +-- gns3server/api/routes/controller/templates.py | 10 +-- gns3server/compute/base_manager.py | 14 ++-- gns3server/db/repositories/users.py | 4 +- gns3server/schemas/controller/computes.py | 8 +- gns3server/schemas/controller/links.py | 2 +- gns3server/schemas/controller/users.py | 6 +- 35 files changed, 730 insertions(+), 414 deletions(-) diff --git a/gns3server/api/routes/compute/atm_switch_nodes.py b/gns3server/api/routes/compute/atm_switch_nodes.py index 6a84ab27..44dbc57a 100644 --- a/gns3server/api/routes/compute/atm_switch_nodes.py +++ b/gns3server/api/routes/compute/atm_switch_nodes.py @@ -20,7 +20,7 @@ API routes for ATM switch nodes. import os -from fastapi import APIRouter, Depends, Body, status +from fastapi import APIRouter, Depends, Body, Path, status from fastapi.encoders import jsonable_encoder from fastapi.responses import StreamingResponse from uuid import UUID @@ -34,7 +34,7 @@ responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find router = APIRouter(responses=responses) -async def dep_node(project_id: UUID, node_id: UUID): +async def dep_node(project_id: UUID, node_id: UUID) -> ATMSwitch: """ Dependency to retrieve a node. """ @@ -50,7 +50,7 @@ async def dep_node(project_id: UUID, node_id: UUID): status_code=status.HTTP_201_CREATED, responses={409: {"model": schemas.ErrorMessage, "description": "Could not create ATM switch node"}}, ) -async def create_atm_switch(project_id: UUID, node_data: schemas.ATMSwitchCreate): +async def create_atm_switch(project_id: UUID, node_data: schemas.ATMSwitchCreate) -> schemas.ATMSwitch: """ Create a new ATM switch node. """ @@ -59,7 +59,7 @@ async def create_atm_switch(project_id: UUID, node_data: schemas.ATMSwitchCreate dynamips_manager = Dynamips.instance() node_data = jsonable_encoder(node_data, exclude_unset=True) node = await dynamips_manager.create_node( - node_data.pop("name"), + node_data.get("name"), str(project_id), node_data.get("node_id"), node_type="atm_switch", @@ -69,7 +69,7 @@ async def create_atm_switch(project_id: UUID, node_data: schemas.ATMSwitchCreate @router.get("/{node_id}", response_model=schemas.ATMSwitch) -def get_atm_switch(node: ATMSwitch = Depends(dep_node)): +def get_atm_switch(node: ATMSwitch = Depends(dep_node)) -> schemas.ATMSwitch: """ Return an ATM switch node. """ @@ -78,7 +78,10 @@ def get_atm_switch(node: ATMSwitch = Depends(dep_node)): @router.post("/{node_id}/duplicate", response_model=schemas.ATMSwitch, status_code=status.HTTP_201_CREATED) -async def duplicate_atm_switch(destination_node_id: UUID = Body(..., embed=True), node: ATMSwitch = Depends(dep_node)): +async def duplicate_atm_switch( + destination_node_id: UUID = Body(..., embed=True), + node: ATMSwitch = Depends(dep_node) +) -> schemas.ATMSwitch: """ Duplicate an ATM switch node. """ @@ -88,7 +91,10 @@ async def duplicate_atm_switch(destination_node_id: UUID = Body(..., embed=True) @router.put("/{node_id}", response_model=schemas.ATMSwitch) -async def update_atm_switch(node_data: schemas.ATMSwitchUpdate, node: ATMSwitch = Depends(dep_node)): +async def update_atm_switch( + node_data: schemas.ATMSwitchUpdate, + node: ATMSwitch = Depends(dep_node) +) -> schemas.ATMSwitch: """ Update an ATM switch node. """ @@ -103,7 +109,7 @@ async def update_atm_switch(node_data: schemas.ATMSwitchUpdate, node: ATMSwitch @router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT) -async def delete_atm_switch_node(node: ATMSwitch = Depends(dep_node)): +async def delete_atm_switch_node(node: ATMSwitch = Depends(dep_node)) -> None: """ Delete an ATM switch node. """ @@ -122,7 +128,7 @@ def start_atm_switch(node: ATMSwitch = Depends(dep_node)): @router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT) -def stop_atm_switch(node: ATMSwitch = Depends(dep_node)): +def stop_atm_switch(node: ATMSwitch = Depends(dep_node)) -> None: """ Stop an ATM switch node. This endpoint results in no action since ATM switch nodes are always on. @@ -132,7 +138,7 @@ def stop_atm_switch(node: ATMSwitch = Depends(dep_node)): @router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT) -def suspend_atm_switch(node: ATMSwitch = Depends(dep_node)): +def suspend_atm_switch(node: ATMSwitch = Depends(dep_node)) -> None: """ Suspend an ATM switch node. This endpoint results in no action since ATM switch nodes are always on. @@ -147,8 +153,12 @@ def suspend_atm_switch(node: ATMSwitch = Depends(dep_node)): response_model=schemas.UDPNIO, ) async def create_nio( - adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: ATMSwitch = Depends(dep_node) -): + *, + adapter_number: int = Path(..., ge=0, le=0), + port_number: int, + nio_data: schemas.UDPNIO, + node: ATMSwitch = Depends(dep_node) +) -> schemas.UDPNIO: """ Add a NIO (Network Input/Output) to the node. The adapter number on the switch is always 0. @@ -160,7 +170,7 @@ async def create_nio( @router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT) -async def delete_nio(adapter_number: int, port_number: int, node: ATMSwitch = Depends(dep_node)): +async def delete_nio(adapter_number: int, port_number: int, node: ATMSwitch = Depends(dep_node)) -> None: """ Remove a NIO (Network Input/Output) from the node. The adapter number on the switch is always 0. @@ -172,8 +182,12 @@ async def delete_nio(adapter_number: int, port_number: int, node: ATMSwitch = De @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start") async def start_capture( - adapter_number: int, port_number: int, node_capture_data: schemas.NodeCapture, node: ATMSwitch = Depends(dep_node) -): + *, + adapter_number: int = Path(..., ge=0, le=0), + port_number: int, + node_capture_data: schemas.NodeCapture, + node: ATMSwitch = Depends(dep_node) +) -> dict: """ Start a packet capture on the node. The adapter number on the switch is always 0. @@ -188,7 +202,12 @@ async def start_capture( "/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop_capture", status_code=status.HTTP_204_NO_CONTENT, ) -async def stop_capture(adapter_number: int, port_number: int, node: ATMSwitch = Depends(dep_node)): +async def stop_capture( + *, + adapter_number: int = Path(..., ge=0, le=0), + port_number: int, + node: ATMSwitch = Depends(dep_node) +) -> None: """ Stop a packet capture on the node. The adapter number on the switch is always 0. @@ -198,7 +217,12 @@ async def stop_capture(adapter_number: int, port_number: int, node: ATMSwitch = @router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream") -async def stream_pcap_file(adapter_number: int, port_number: int, node: ATMSwitch = Depends(dep_node)): +async def stream_pcap_file( + *, + adapter_number: int = Path(..., ge=0, le=0), + port_number: int, + node: ATMSwitch = Depends(dep_node) +) -> StreamingResponse: """ Stream the pcap capture file. The adapter number on the switch is always 0. diff --git a/gns3server/api/routes/compute/capabilities.py b/gns3server/api/routes/compute/capabilities.py index 0931aa5c..8e865348 100644 --- a/gns3server/api/routes/compute/capabilities.py +++ b/gns3server/api/routes/compute/capabilities.py @@ -32,7 +32,7 @@ router = APIRouter() @router.get("/capabilities", response_model=schemas.Capabilities) -def get_capabilities(): +def get_capabilities() -> dict: node_types = [] for module in MODULES: diff --git a/gns3server/api/routes/compute/cloud_nodes.py b/gns3server/api/routes/compute/cloud_nodes.py index 34bda678..65ad686f 100644 --- a/gns3server/api/routes/compute/cloud_nodes.py +++ b/gns3server/api/routes/compute/cloud_nodes.py @@ -20,7 +20,7 @@ API routes for cloud nodes. import os -from fastapi import APIRouter, Depends, status +from fastapi import APIRouter, Depends, Path, status from fastapi.encoders import jsonable_encoder from fastapi.responses import StreamingResponse from typing import Union @@ -35,7 +35,7 @@ responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find router = APIRouter(responses=responses) -def dep_node(project_id: UUID, node_id: UUID): +def dep_node(project_id: UUID, node_id: UUID) -> Cloud: """ Dependency to retrieve a node. """ @@ -51,7 +51,7 @@ def dep_node(project_id: UUID, node_id: UUID): status_code=status.HTTP_201_CREATED, responses={409: {"model": schemas.ErrorMessage, "description": "Could not create cloud node"}}, ) -async def create_cloud(project_id: UUID, node_data: schemas.CloudCreate): +async def create_cloud(project_id: UUID, node_data: schemas.CloudCreate) -> schemas.Cloud: """ Create a new cloud node. """ @@ -76,7 +76,7 @@ async def create_cloud(project_id: UUID, node_data: schemas.CloudCreate): @router.get("/{node_id}", response_model=schemas.Cloud) -def get_cloud(node: Cloud = Depends(dep_node)): +def get_cloud(node: Cloud = Depends(dep_node)) -> schemas.Cloud: """ Return a cloud node. """ @@ -85,7 +85,7 @@ def get_cloud(node: Cloud = Depends(dep_node)): @router.put("/{node_id}", response_model=schemas.Cloud) -def update_cloud(node_data: schemas.CloudUpdate, node: Cloud = Depends(dep_node)): +def update_cloud(node_data: schemas.CloudUpdate, node: Cloud = Depends(dep_node)) -> schemas.Cloud: """ Update a cloud node. """ @@ -99,7 +99,7 @@ def update_cloud(node_data: schemas.CloudUpdate, node: Cloud = Depends(dep_node) @router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT) -async def delete_cloud(node: Cloud = Depends(dep_node)): +async def delete_cloud(node: Cloud = Depends(dep_node)) -> None: """ Delete a cloud node. """ @@ -108,7 +108,7 @@ async def delete_cloud(node: Cloud = Depends(dep_node)): @router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT) -async def start_cloud(node: Cloud = Depends(dep_node)): +async def start_cloud(node: Cloud = Depends(dep_node)) -> None: """ Start a cloud node. """ @@ -117,7 +117,7 @@ async def start_cloud(node: Cloud = Depends(dep_node)): @router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT) -async def stop_cloud(node: Cloud = Depends(dep_node)): +async def stop_cloud(node: Cloud = Depends(dep_node)) -> None: """ Stop a cloud node. This endpoint results in no action since cloud nodes cannot be stopped. @@ -127,7 +127,7 @@ async def stop_cloud(node: Cloud = Depends(dep_node)): @router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT) -async def suspend_cloud(node: Cloud = Depends(dep_node)): +async def suspend_cloud(node: Cloud = Depends(dep_node)) -> None: """ Suspend a cloud node. This endpoint results in no action since cloud nodes cannot be suspended. @@ -142,11 +142,12 @@ async def suspend_cloud(node: Cloud = Depends(dep_node)): response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO], ) async def create_cloud_nio( - adapter_number: int, - port_number: int, - nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO], - node: Cloud = Depends(dep_node), -): + *, + adapter_number: int = Path(..., ge=0, le=0), + port_number: int, + nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO], + node: Cloud = Depends(dep_node), +) -> Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO]: """ Add a NIO (Network Input/Output) to the node. The adapter number on the cloud is always 0. @@ -163,11 +164,12 @@ async def create_cloud_nio( response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO], ) async def update_cloud_nio( - adapter_number: int, - port_number: int, - nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO], - node: Cloud = Depends(dep_node), -): + *, + adapter_number: int = Path(..., ge=0, le=0), + port_number: int, + nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO], + node: Cloud = Depends(dep_node), +) -> Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO]: """ Update a NIO (Network Input/Output) to the node. The adapter number on the cloud is always 0. @@ -181,7 +183,12 @@ async def update_cloud_nio( @router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT) -async def delete_cloud_nio(adapter_number: int, port_number: int, node: Cloud = Depends(dep_node)): +async def delete_cloud_nio( + *, + adapter_number: int = Path(..., ge=0, le=0), + port_number: int, + node: Cloud = Depends(dep_node) +) -> None: """ Remove a NIO (Network Input/Output) from the node. The adapter number on the cloud is always 0. @@ -192,8 +199,12 @@ async def delete_cloud_nio(adapter_number: int, port_number: int, node: Cloud = @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start") async def start_cloud_capture( - adapter_number: int, port_number: int, node_capture_data: schemas.NodeCapture, node: Cloud = Depends(dep_node) -): + *, + adapter_number: int = Path(..., ge=0, le=0), + port_number: int, + node_capture_data: schemas.NodeCapture, + node: Cloud = Depends(dep_node) +) -> dict: """ Start a packet capture on the node. The adapter number on the cloud is always 0. @@ -207,7 +218,12 @@ async def start_cloud_capture( @router.post( "/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT ) -async def stop_cloud_capture(adapter_number: int, port_number: int, node: Cloud = Depends(dep_node)): +async def stop_cloud_capture( + *, + adapter_number: int = Path(..., ge=0, le=0), + port_number: int, + node: Cloud = Depends(dep_node) +) -> None: """ Stop a packet capture on the node. The adapter number on the cloud is always 0. @@ -217,7 +233,12 @@ async def stop_cloud_capture(adapter_number: int, port_number: int, node: Cloud @router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/pcap") -async def stream_pcap_file(adapter_number: int, port_number: int, node: Cloud = Depends(dep_node)): +async def stream_pcap_file( + *, + adapter_number: int = Path(..., ge=0, le=0), + port_number: int, + node: Cloud = Depends(dep_node) +) -> StreamingResponse: """ Stream the pcap capture file. The adapter number on the cloud is always 0. diff --git a/gns3server/api/routes/compute/compute.py b/gns3server/api/routes/compute/compute.py index 2ae6ec2f..d97ab3f2 100644 --- a/gns3server/api/routes/compute/compute.py +++ b/gns3server/api/routes/compute/compute.py @@ -123,13 +123,15 @@ def compute_statistics() -> dict: @router.get("/qemu/binaries") -async def get_qemu_binaries(archs: Optional[List[str]] = Body(None, embed=True)): +async def get_qemu_binaries( + archs: Optional[List[str]] = Body(None, embed=True) +) -> List[str]: return await Qemu.binary_list(archs) @router.get("/qemu/img-binaries") -async def get_image_binaries(): +async def get_image_binaries() -> List[str]: return await Qemu.img_binary_list() @@ -148,7 +150,7 @@ async def get_qemu_capabilities() -> dict: status_code=status.HTTP_204_NO_CONTENT, responses={403: {"model": schemas.ErrorMessage, "description": "Forbidden to create Qemu image"}}, ) -async def create_qemu_image(image_data: schemas.QemuImageCreate): +async def create_qemu_image(image_data: schemas.QemuImageCreate) -> None: """ Create a Qemu image. """ @@ -167,7 +169,7 @@ async def create_qemu_image(image_data: schemas.QemuImageCreate): status_code=status.HTTP_204_NO_CONTENT, responses={403: {"model": schemas.ErrorMessage, "description": "Forbidden to update Qemu image"}}, ) -async def update_qemu_image(image_data: schemas.QemuImageUpdate): +async def update_qemu_image(image_data: schemas.QemuImageUpdate) -> None: """ Update a Qemu image. """ @@ -181,13 +183,13 @@ async def update_qemu_image(image_data: schemas.QemuImageUpdate): @router.get("/virtualbox/vms", response_model=List[dict]) -async def get_virtualbox_vms(): +async def get_virtualbox_vms() -> List[dict]: vbox_manager = VirtualBox.instance() return await vbox_manager.list_vms() @router.get("/vmware/vms", response_model=List[dict]) -async def get_vms(): +async def get_vms() -> List[dict]: vmware_manager = VMware.instance() return await vmware_manager.list_vms() diff --git a/gns3server/api/routes/compute/docker_nodes.py b/gns3server/api/routes/compute/docker_nodes.py index 0f90e267..042f2312 100644 --- a/gns3server/api/routes/compute/docker_nodes.py +++ b/gns3server/api/routes/compute/docker_nodes.py @@ -34,7 +34,7 @@ responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find router = APIRouter(responses=responses) -def dep_node(project_id: UUID, node_id: UUID): +def dep_node(project_id: UUID, node_id: UUID) -> DockerVM: """ Dependency to retrieve a node. """ @@ -50,7 +50,7 @@ def dep_node(project_id: UUID, node_id: UUID): status_code=status.HTTP_201_CREATED, responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Docker node"}}, ) -async def create_docker_node(project_id: UUID, node_data: schemas.DockerCreate): +async def create_docker_node(project_id: UUID, node_data: schemas.DockerCreate) -> schemas.Docker: """ Create a new Docker node. """ @@ -86,7 +86,7 @@ async def create_docker_node(project_id: UUID, node_data: schemas.DockerCreate): @router.get("/{node_id}", response_model=schemas.Docker) -def get_docker_node(node: DockerVM = Depends(dep_node)): +def get_docker_node(node: DockerVM = Depends(dep_node)) -> schemas.Docker: """ Return a Docker node. """ @@ -95,7 +95,7 @@ def get_docker_node(node: DockerVM = Depends(dep_node)): @router.put("/{node_id}", response_model=schemas.Docker) -async def update_docker_node(node_data: schemas.DockerUpdate, node: DockerVM = Depends(dep_node)): +async def update_docker_node(node_data: schemas.DockerUpdate, node: DockerVM = Depends(dep_node)) -> schemas.Docker: """ Update a Docker node. """ @@ -132,7 +132,7 @@ async def update_docker_node(node_data: schemas.DockerUpdate, node: DockerVM = D @router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT) -async def start_docker_node(node: DockerVM = Depends(dep_node)): +async def start_docker_node(node: DockerVM = Depends(dep_node)) -> None: """ Start a Docker node. """ @@ -141,7 +141,7 @@ async def start_docker_node(node: DockerVM = Depends(dep_node)): @router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT) -async def stop_docker_node(node: DockerVM = Depends(dep_node)): +async def stop_docker_node(node: DockerVM = Depends(dep_node)) -> None: """ Stop a Docker node. """ @@ -150,7 +150,7 @@ async def stop_docker_node(node: DockerVM = Depends(dep_node)): @router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT) -async def suspend_docker_node(node: DockerVM = Depends(dep_node)): +async def suspend_docker_node(node: DockerVM = Depends(dep_node)) -> None: """ Suspend a Docker node. """ @@ -159,7 +159,7 @@ async def suspend_docker_node(node: DockerVM = Depends(dep_node)): @router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT) -async def reload_docker_node(node: DockerVM = Depends(dep_node)): +async def reload_docker_node(node: DockerVM = Depends(dep_node)) -> None: """ Reload a Docker node. """ @@ -168,7 +168,7 @@ async def reload_docker_node(node: DockerVM = Depends(dep_node)): @router.post("/{node_id}/pause", status_code=status.HTTP_204_NO_CONTENT) -async def pause_docker_node(node: DockerVM = Depends(dep_node)): +async def pause_docker_node(node: DockerVM = Depends(dep_node)) -> None: """ Pause a Docker node. """ @@ -177,7 +177,7 @@ async def pause_docker_node(node: DockerVM = Depends(dep_node)): @router.post("/{node_id}/unpause", status_code=status.HTTP_204_NO_CONTENT) -async def unpause_docker_node(node: DockerVM = Depends(dep_node)): +async def unpause_docker_node(node: DockerVM = Depends(dep_node)) -> None: """ Unpause a Docker node. """ @@ -186,7 +186,7 @@ async def unpause_docker_node(node: DockerVM = Depends(dep_node)): @router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT) -async def delete_docker_node(node: DockerVM = Depends(dep_node)): +async def delete_docker_node(node: DockerVM = Depends(dep_node)) -> None: """ Delete a Docker node. """ @@ -195,7 +195,10 @@ async def delete_docker_node(node: DockerVM = Depends(dep_node)): @router.post("/{node_id}/duplicate", response_model=schemas.Docker, status_code=status.HTTP_201_CREATED) -async def duplicate_docker_node(destination_node_id: UUID = Body(..., embed=True), node: DockerVM = Depends(dep_node)): +async def duplicate_docker_node( + destination_node_id: UUID = Body(..., embed=True), + node: DockerVM = Depends(dep_node) +) -> schemas.Docker: """ Duplicate a Docker node. """ @@ -211,7 +214,7 @@ async def duplicate_docker_node(destination_node_id: UUID = Body(..., embed=True ) async def create_docker_node_nio( adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: DockerVM = Depends(dep_node) -): +) -> schemas.UDPNIO: """ Add a NIO (Network Input/Output) to the node. The port number on the Docker node is always 0. @@ -229,7 +232,7 @@ async def create_docker_node_nio( ) async def update_docker_node_nio( adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: DockerVM = Depends(dep_node) -): +) -> schemas.UDPNIO: """ Update a NIO (Network Input/Output) on the node. The port number on the Docker node is always 0. @@ -243,7 +246,11 @@ async def update_docker_node_nio( @router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT) -async def delete_docker_node_nio(adapter_number: int, port_number: int, node: DockerVM = Depends(dep_node)): +async def delete_docker_node_nio( + adapter_number: int, + port_number: int, + node: DockerVM = Depends(dep_node) +) -> None: """ Delete a NIO (Network Input/Output) from the node. The port number on the Docker node is always 0. @@ -254,8 +261,11 @@ async def delete_docker_node_nio(adapter_number: int, port_number: int, node: Do @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start") async def start_docker_node_capture( - adapter_number: int, port_number: int, node_capture_data: schemas.NodeCapture, node: DockerVM = Depends(dep_node) -): + adapter_number: int, + port_number: int, + node_capture_data: schemas.NodeCapture, + node: DockerVM = Depends(dep_node) +) -> dict: """ Start a packet capture on the node. The port number on the Docker node is always 0. @@ -267,9 +277,14 @@ async def start_docker_node_capture( @router.post( - "/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT + "/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", + status_code=status.HTTP_204_NO_CONTENT ) -async def stop_docker_node_capture(adapter_number: int, port_number: int, node: DockerVM = Depends(dep_node)): +async def stop_docker_node_capture( + adapter_number: int, + port_number: int, + node: DockerVM = Depends(dep_node) +) -> None: """ Stop a packet capture on the node. The port number on the Docker node is always 0. @@ -279,7 +294,11 @@ async def stop_docker_node_capture(adapter_number: int, port_number: int, node: @router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream") -async def stream_pcap_file(adapter_number: int, port_number: int, node: DockerVM = Depends(dep_node)): +async def stream_pcap_file( + adapter_number: int, + port_number: int, + node: DockerVM = Depends(dep_node) +) -> StreamingResponse: """ Stream the pcap capture file. The port number on the Docker node is always 0. @@ -291,7 +310,7 @@ async def stream_pcap_file(adapter_number: int, port_number: int, node: DockerVM @router.websocket("/{node_id}/console/ws") -async def console_ws(websocket: WebSocket, node: DockerVM = Depends(dep_node)): +async def console_ws(websocket: WebSocket, node: DockerVM = Depends(dep_node)) -> None: """ Console WebSocket. """ @@ -300,6 +319,6 @@ async def console_ws(websocket: WebSocket, node: DockerVM = Depends(dep_node)): @router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT) -async def reset_console(node: DockerVM = Depends(dep_node)): +async def reset_console(node: DockerVM = Depends(dep_node)) -> None: await node.reset_console() diff --git a/gns3server/api/routes/compute/dynamips_nodes.py b/gns3server/api/routes/compute/dynamips_nodes.py index bff59c6c..81317fd6 100644 --- a/gns3server/api/routes/compute/dynamips_nodes.py +++ b/gns3server/api/routes/compute/dynamips_nodes.py @@ -40,7 +40,7 @@ router = APIRouter(responses=responses) DEFAULT_CHASSIS = {"c1700": "1720", "c2600": "2610", "c3600": "3640"} -def dep_node(project_id: UUID, node_id: UUID): +def dep_node(project_id: UUID, node_id: UUID) -> Router: """ Dependency to retrieve a node. """ @@ -56,7 +56,7 @@ def dep_node(project_id: UUID, node_id: UUID): status_code=status.HTTP_201_CREATED, responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Dynamips node"}}, ) -async def create_router(project_id: UUID, node_data: schemas.DynamipsCreate): +async def create_router(project_id: UUID, node_data: schemas.DynamipsCreate) -> schemas.Dynamips: """ Create a new Dynamips router. """ @@ -85,7 +85,7 @@ async def create_router(project_id: UUID, node_data: schemas.DynamipsCreate): @router.get("/{node_id}", response_model=schemas.Dynamips) -def get_router(node: Router = Depends(dep_node)): +def get_router(node: Router = Depends(dep_node)) -> schemas.Dynamips: """ Return Dynamips router. """ @@ -94,7 +94,7 @@ def get_router(node: Router = Depends(dep_node)): @router.put("/{node_id}", response_model=schemas.Dynamips) -async def update_router(node_data: schemas.DynamipsUpdate, node: Router = Depends(dep_node)): +async def update_router(node_data: schemas.DynamipsUpdate, node: Router = Depends(dep_node)) -> schemas.Dynamips: """ Update a Dynamips router. """ @@ -105,7 +105,7 @@ async def update_router(node_data: schemas.DynamipsUpdate, node: Router = Depend @router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT) -async def delete_router(node: Router = Depends(dep_node)): +async def delete_router(node: Router = Depends(dep_node)) -> None: """ Delete a Dynamips router. """ @@ -114,7 +114,7 @@ async def delete_router(node: Router = Depends(dep_node)): @router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT) -async def start_router(node: Router = Depends(dep_node)): +async def start_router(node: Router = Depends(dep_node)) -> None: """ Start a Dynamips router. """ @@ -127,7 +127,7 @@ async def start_router(node: Router = Depends(dep_node)): @router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT) -async def stop_router(node: Router = Depends(dep_node)): +async def stop_router(node: Router = Depends(dep_node)) -> None: """ Stop a Dynamips router. """ @@ -136,13 +136,13 @@ async def stop_router(node: Router = Depends(dep_node)): @router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT) -async def suspend_router(node: Router = Depends(dep_node)): +async def suspend_router(node: Router = Depends(dep_node)) -> None: await node.suspend() @router.post("/{node_id}/resume", status_code=status.HTTP_204_NO_CONTENT) -async def resume_router(node: Router = Depends(dep_node)): +async def resume_router(node: Router = Depends(dep_node)) -> None: """ Resume a suspended Dynamips router. """ @@ -151,7 +151,7 @@ async def resume_router(node: Router = Depends(dep_node)): @router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT) -async def reload_router(node: Router = Depends(dep_node)): +async def reload_router(node: Router = Depends(dep_node)) -> None: """ Reload a suspended Dynamips router. """ @@ -164,7 +164,12 @@ async def reload_router(node: Router = Depends(dep_node)): status_code=status.HTTP_201_CREATED, response_model=schemas.UDPNIO, ) -async def create_nio(adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: Router = Depends(dep_node)): +async def create_nio( + adapter_number: int, + port_number: int, + nio_data: schemas.UDPNIO, + node: Router = Depends(dep_node) +) -> schemas.UDPNIO: """ Add a NIO (Network Input/Output) to the node. """ @@ -179,7 +184,12 @@ async def create_nio(adapter_number: int, port_number: int, nio_data: schemas.UD status_code=status.HTTP_201_CREATED, response_model=schemas.UDPNIO, ) -async def update_nio(adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: Router = Depends(dep_node)): +async def update_nio( + adapter_number: int, + port_number: int, + nio_data: schemas.UDPNIO, + node: Router = Depends(dep_node) +) -> schemas.UDPNIO: """ Update a NIO (Network Input/Output) on the node. """ @@ -192,7 +202,7 @@ async def update_nio(adapter_number: int, port_number: int, nio_data: schemas.UD @router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT) -async def delete_nio(adapter_number: int, port_number: int, node: Router = Depends(dep_node)): +async def delete_nio(adapter_number: int, port_number: int, node: Router = Depends(dep_node)) -> None: """ Delete a NIO (Network Input/Output) from the node. """ @@ -203,8 +213,11 @@ async def delete_nio(adapter_number: int, port_number: int, node: Router = Depen @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start") async def start_capture( - adapter_number: int, port_number: int, node_capture_data: schemas.NodeCapture, node: Router = Depends(dep_node) -): + adapter_number: int, + port_number: int, + node_capture_data: schemas.NodeCapture, + node: Router = Depends(dep_node) +) -> dict: """ Start a packet capture on the node. """ @@ -227,7 +240,7 @@ async def start_capture( @router.post( "/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT ) -async def stop_capture(adapter_number: int, port_number: int, node: Router = Depends(dep_node)): +async def stop_capture(adapter_number: int, port_number: int, node: Router = Depends(dep_node)) -> None: """ Stop a packet capture on the node. """ @@ -236,7 +249,11 @@ async def stop_capture(adapter_number: int, port_number: int, node: Router = Dep @router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream") -async def stream_pcap_file(adapter_number: int, port_number: int, node: Router = Depends(dep_node)): +async def stream_pcap_file( + adapter_number: int, + port_number: int, + node: Router = Depends(dep_node) +) -> StreamingResponse: """ Stream the pcap capture file. """ @@ -266,8 +283,8 @@ async def get_auto_idlepc(node: Router = Depends(dep_node)) -> dict: return {"idlepc": idlepc} -@router.post("/{node_id}/duplicate", status_code=status.HTTP_201_CREATED) -async def duplicate_router(destination_node_id: UUID, node: Router = Depends(dep_node)): +@router.post("/{node_id}/duplicate", response_model=schemas.Dynamips, status_code=status.HTTP_201_CREATED) +async def duplicate_router(destination_node_id: UUID, node: Router = Depends(dep_node)) -> schemas.Dynamips: """ Duplicate a router. """ @@ -277,7 +294,7 @@ async def duplicate_router(destination_node_id: UUID, node: Router = Depends(dep @router.websocket("/{node_id}/console/ws") -async def console_ws(websocket: WebSocket, node: Router = Depends(dep_node)): +async def console_ws(websocket: WebSocket, node: Router = Depends(dep_node)) -> None: """ Console WebSocket. """ @@ -286,6 +303,6 @@ async def console_ws(websocket: WebSocket, node: Router = Depends(dep_node)): @router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT) -async def reset_console(node: Router = Depends(dep_node)): +async def reset_console(node: Router = Depends(dep_node)) -> None: await node.reset_console() diff --git a/gns3server/api/routes/compute/ethernet_hub_nodes.py b/gns3server/api/routes/compute/ethernet_hub_nodes.py index c14dce63..783cd397 100644 --- a/gns3server/api/routes/compute/ethernet_hub_nodes.py +++ b/gns3server/api/routes/compute/ethernet_hub_nodes.py @@ -20,7 +20,7 @@ API routes for Ethernet hub nodes. import os -from fastapi import APIRouter, Depends, Body, status +from fastapi import APIRouter, Depends, Body, Path, status from fastapi.encoders import jsonable_encoder from fastapi.responses import StreamingResponse from uuid import UUID @@ -34,7 +34,7 @@ responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find router = APIRouter(responses=responses) -def dep_node(project_id: UUID, node_id: UUID): +def dep_node(project_id: UUID, node_id: UUID) -> EthernetHub: """ Dependency to retrieve a node. """ @@ -50,7 +50,7 @@ def dep_node(project_id: UUID, node_id: UUID): status_code=status.HTTP_201_CREATED, responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Ethernet hub node"}}, ) -async def create_ethernet_hub(project_id: UUID, node_data: schemas.EthernetHubCreate): +async def create_ethernet_hub(project_id: UUID, node_data: schemas.EthernetHubCreate) -> schemas.EthernetHub: """ Create a new Ethernet hub. """ @@ -69,7 +69,7 @@ async def create_ethernet_hub(project_id: UUID, node_data: schemas.EthernetHubCr @router.get("/{node_id}", response_model=schemas.EthernetHub) -def get_ethernet_hub(node: EthernetHub = Depends(dep_node)): +def get_ethernet_hub(node: EthernetHub = Depends(dep_node)) -> schemas.EthernetHub: """ Return an Ethernet hub. """ @@ -80,7 +80,7 @@ def get_ethernet_hub(node: EthernetHub = Depends(dep_node)): @router.post("/{node_id}/duplicate", response_model=schemas.EthernetHub, status_code=status.HTTP_201_CREATED) async def duplicate_ethernet_hub( destination_node_id: UUID = Body(..., embed=True), node: EthernetHub = Depends(dep_node) -): +) -> schemas.EthernetHub: """ Duplicate an Ethernet hub. """ @@ -90,7 +90,10 @@ async def duplicate_ethernet_hub( @router.put("/{node_id}", response_model=schemas.EthernetHub) -async def update_ethernet_hub(node_data: schemas.EthernetHubUpdate, node: EthernetHub = Depends(dep_node)): +async def update_ethernet_hub( + node_data: schemas.EthernetHubUpdate, + node: EthernetHub = Depends(dep_node) +) -> schemas.EthernetHub: """ Update an Ethernet hub. """ @@ -105,7 +108,7 @@ async def update_ethernet_hub(node_data: schemas.EthernetHubUpdate, node: Ethern @router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT) -async def delete_ethernet_hub(node: EthernetHub = Depends(dep_node)): +async def delete_ethernet_hub(node: EthernetHub = Depends(dep_node)) -> None: """ Delete an Ethernet hub. """ @@ -114,7 +117,7 @@ async def delete_ethernet_hub(node: EthernetHub = Depends(dep_node)): @router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT) -def start_ethernet_hub(node: EthernetHub = Depends(dep_node)): +def start_ethernet_hub(node: EthernetHub = Depends(dep_node)) -> None: """ Start an Ethernet hub. This endpoint results in no action since Ethernet hub nodes are always on. @@ -124,7 +127,7 @@ def start_ethernet_hub(node: EthernetHub = Depends(dep_node)): @router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT) -def stop_ethernet_hub(node: EthernetHub = Depends(dep_node)): +def stop_ethernet_hub(node: EthernetHub = Depends(dep_node)) -> None: """ Stop an Ethernet hub. This endpoint results in no action since Ethernet hub nodes are always on. @@ -134,7 +137,7 @@ def stop_ethernet_hub(node: EthernetHub = Depends(dep_node)): @router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT) -def suspend_ethernet_hub(node: EthernetHub = Depends(dep_node)): +def suspend_ethernet_hub(node: EthernetHub = Depends(dep_node)) -> None: """ Suspend an Ethernet hub. This endpoint results in no action since Ethernet hub nodes are always on. @@ -149,8 +152,12 @@ def suspend_ethernet_hub(node: EthernetHub = Depends(dep_node)): response_model=schemas.UDPNIO, ) async def create_nio( - adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: EthernetHub = Depends(dep_node) -): + *, + adapter_number: int = Path(..., ge=0, le=0), + port_number: int, + nio_data: schemas.UDPNIO, + node: EthernetHub = Depends(dep_node) +) -> schemas.UDPNIO: """ Add a NIO (Network Input/Output) to the node. The adapter number on the hub is always 0. @@ -162,7 +169,12 @@ async def create_nio( @router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT) -async def delete_nio(adapter_number: int, port_number: int, node: EthernetHub = Depends(dep_node)): +async def delete_nio( + *, + adapter_number: int = Path(..., ge=0, le=0), + port_number: int, + node: EthernetHub = Depends(dep_node) +) -> None: """ Delete a NIO (Network Input/Output) from the node. The adapter number on the hub is always 0. @@ -174,8 +186,12 @@ async def delete_nio(adapter_number: int, port_number: int, node: EthernetHub = @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start") async def start_capture( - adapter_number: int, port_number: int, node_capture_data: schemas.NodeCapture, node: EthernetHub = Depends(dep_node) -): + *, + adapter_number: int = Path(..., ge=0, le=0), + port_number: int, + node_capture_data: schemas.NodeCapture, + node: EthernetHub = Depends(dep_node) +) -> dict: """ Start a packet capture on the node. The adapter number on the hub is always 0. @@ -189,7 +205,12 @@ async def start_capture( @router.post( "/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT ) -async def stop_capture(adapter_number: int, port_number: int, node: EthernetHub = Depends(dep_node)): +async def stop_capture( + *, + adapter_number: int = Path(..., ge=0, le=0), + port_number: int, + node: EthernetHub = Depends(dep_node) +) -> None: """ Stop a packet capture on the node. The adapter number on the hub is always 0. @@ -199,7 +220,12 @@ async def stop_capture(adapter_number: int, port_number: int, node: EthernetHub @router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream") -async def stream_pcap_file(adapter_number: int, port_number: int, node: EthernetHub = Depends(dep_node)): +async def stream_pcap_file( + *, + adapter_number: int = Path(..., ge=0, le=0), + port_number: int, + node: EthernetHub = Depends(dep_node) +) -> StreamingResponse: """ Stream the pcap capture file. The adapter number on the hub is always 0. diff --git a/gns3server/api/routes/compute/ethernet_switch_nodes.py b/gns3server/api/routes/compute/ethernet_switch_nodes.py index 51628dfb..3375dd19 100644 --- a/gns3server/api/routes/compute/ethernet_switch_nodes.py +++ b/gns3server/api/routes/compute/ethernet_switch_nodes.py @@ -20,7 +20,7 @@ API routes for Ethernet switch nodes. import os -from fastapi import APIRouter, Depends, Body, status +from fastapi import APIRouter, Depends, Body, Path, status from fastapi.encoders import jsonable_encoder from fastapi.responses import StreamingResponse from uuid import UUID @@ -34,7 +34,7 @@ responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find router = APIRouter(responses=responses) -def dep_node(project_id: UUID, node_id: UUID): +def dep_node(project_id: UUID, node_id: UUID) -> EthernetSwitch: """ Dependency to retrieve a node. """ @@ -50,7 +50,7 @@ def dep_node(project_id: UUID, node_id: UUID): status_code=status.HTTP_201_CREATED, responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Ethernet switch node"}}, ) -async def create_ethernet_switch(project_id: UUID, node_data: schemas.EthernetSwitchCreate): +async def create_ethernet_switch(project_id: UUID, node_data: schemas.EthernetSwitchCreate) -> schemas.EthernetSwitch: """ Create a new Ethernet switch. """ @@ -72,15 +72,16 @@ async def create_ethernet_switch(project_id: UUID, node_data: schemas.EthernetSw @router.get("/{node_id}", response_model=schemas.EthernetSwitch) -def get_ethernet_switch(node: EthernetSwitch = Depends(dep_node)): +def get_ethernet_switch(node: EthernetSwitch = Depends(dep_node)) -> schemas.EthernetSwitch: return node.asdict() @router.post("/{node_id}/duplicate", response_model=schemas.EthernetSwitch, status_code=status.HTTP_201_CREATED) async def duplicate_ethernet_switch( - destination_node_id: UUID = Body(..., embed=True), node: EthernetSwitch = Depends(dep_node) -): + destination_node_id: UUID = Body(..., embed=True), + node: EthernetSwitch = Depends(dep_node) +) -> schemas.EthernetSwitch: """ Duplicate an Ethernet switch. """ @@ -90,7 +91,10 @@ async def duplicate_ethernet_switch( @router.put("/{node_id}", response_model=schemas.EthernetSwitch) -async def update_ethernet_switch(node_data: schemas.EthernetSwitchUpdate, node: EthernetSwitch = Depends(dep_node)): +async def update_ethernet_switch( + node_data: schemas.EthernetSwitchUpdate, + node: EthernetSwitch = Depends(dep_node) +) -> schemas.EthernetSwitch: """ Update an Ethernet switch. """ @@ -108,7 +112,7 @@ async def update_ethernet_switch(node_data: schemas.EthernetSwitchUpdate, node: @router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT) -async def delete_ethernet_switch(node: EthernetSwitch = Depends(dep_node)): +async def delete_ethernet_switch(node: EthernetSwitch = Depends(dep_node)) -> None: """ Delete an Ethernet switch. """ @@ -117,7 +121,7 @@ async def delete_ethernet_switch(node: EthernetSwitch = Depends(dep_node)): @router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT) -def start_ethernet_switch(node: EthernetSwitch = Depends(dep_node)): +def start_ethernet_switch(node: EthernetSwitch = Depends(dep_node)) -> None: """ Start an Ethernet switch. This endpoint results in no action since Ethernet switch nodes are always on. @@ -127,7 +131,7 @@ def start_ethernet_switch(node: EthernetSwitch = Depends(dep_node)): @router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT) -def stop_ethernet_switch(node: EthernetSwitch = Depends(dep_node)): +def stop_ethernet_switch(node: EthernetSwitch = Depends(dep_node)) -> None: """ Stop an Ethernet switch. This endpoint results in no action since Ethernet switch nodes are always on. @@ -137,7 +141,7 @@ def stop_ethernet_switch(node: EthernetSwitch = Depends(dep_node)): @router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT) -def suspend_ethernet_switch(node: EthernetSwitch = Depends(dep_node)): +def suspend_ethernet_switch(node: EthernetSwitch = Depends(dep_node)) -> None: """ Suspend an Ethernet switch. This endpoint results in no action since Ethernet switch nodes are always on. @@ -152,8 +156,12 @@ def suspend_ethernet_switch(node: EthernetSwitch = Depends(dep_node)): response_model=schemas.UDPNIO, ) async def create_nio( - adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: EthernetSwitch = Depends(dep_node) -): + *, + adapter_number: int = Path(..., ge=0, le=0), + port_number: int, + nio_data: schemas.UDPNIO, + node: EthernetSwitch = Depends(dep_node) +) -> schemas.UDPNIO: nio = await Dynamips.instance().create_nio(node, jsonable_encoder(nio_data, exclude_unset=True)) await node.add_nio(nio, port_number) @@ -161,7 +169,12 @@ async def create_nio( @router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT) -async def delete_nio(adapter_number: int, port_number: int, node: EthernetSwitch = Depends(dep_node)): +async def delete_nio( + *, + adapter_number: int = Path(..., ge=0, le=0), + port_number: int, + node: EthernetSwitch = Depends(dep_node) +) -> None: """ Delete a NIO (Network Input/Output) from the node. The adapter number on the switch is always 0. @@ -173,11 +186,12 @@ async def delete_nio(adapter_number: int, port_number: int, node: EthernetSwitch @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start") async def start_capture( - adapter_number: int, - port_number: int, - node_capture_data: schemas.NodeCapture, - node: EthernetSwitch = Depends(dep_node), -): + *, + adapter_number: int = Path(..., ge=0, le=0), + port_number: int, + node_capture_data: schemas.NodeCapture, + node: EthernetSwitch = Depends(dep_node), +) -> dict: """ Start a packet capture on the node. The adapter number on the switch is always 0. @@ -191,7 +205,12 @@ async def start_capture( @router.post( "/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT ) -async def stop_capture(adapter_number: int, port_number: int, node: EthernetSwitch = Depends(dep_node)): +async def stop_capture( + *, + adapter_number: int = Path(..., ge=0, le=0), + port_number: int, + node: EthernetSwitch = Depends(dep_node) +) -> None: """ Stop a packet capture on the node. The adapter number on the switch is always 0. @@ -201,7 +220,12 @@ async def stop_capture(adapter_number: int, port_number: int, node: EthernetSwit @router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream") -async def stream_pcap_file(adapter_number: int, port_number: int, node: EthernetSwitch = Depends(dep_node)): +async def stream_pcap_file( + *, + adapter_number: int = Path(..., ge=0, le=0), + port_number: int, + node: EthernetSwitch = Depends(dep_node) +) -> StreamingResponse: """ Stream the pcap capture file. The adapter number on the switch is always 0. diff --git a/gns3server/api/routes/compute/frame_relay_switch_nodes.py b/gns3server/api/routes/compute/frame_relay_switch_nodes.py index 7b9f5ecb..d0ea5537 100644 --- a/gns3server/api/routes/compute/frame_relay_switch_nodes.py +++ b/gns3server/api/routes/compute/frame_relay_switch_nodes.py @@ -20,7 +20,7 @@ API routes for Frame Relay switch nodes. import os -from fastapi import APIRouter, Depends, Body, status +from fastapi import APIRouter, Depends, Body, Path, status from fastapi.encoders import jsonable_encoder from fastapi.responses import StreamingResponse from uuid import UUID @@ -34,7 +34,7 @@ responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find router = APIRouter(responses=responses) -def dep_node(project_id: UUID, node_id: UUID): +def dep_node(project_id: UUID, node_id: UUID) -> FrameRelaySwitch: """ Dependency to retrieve a node. """ @@ -50,7 +50,10 @@ def dep_node(project_id: UUID, node_id: UUID): status_code=status.HTTP_201_CREATED, responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Frame Relay switch node"}}, ) -async def create_frame_relay_switch(project_id: UUID, node_data: schemas.FrameRelaySwitchCreate): +async def create_frame_relay_switch( + project_id: UUID, + node_data: schemas.FrameRelaySwitchCreate +) -> schemas.FrameRelaySwitch: """ Create a new Frame Relay switch node. """ @@ -69,7 +72,7 @@ async def create_frame_relay_switch(project_id: UUID, node_data: schemas.FrameRe @router.get("/{node_id}", response_model=schemas.FrameRelaySwitch) -def get_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)): +def get_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)) -> schemas.FrameRelaySwitch: """ Return a Frame Relay switch node. """ @@ -79,8 +82,9 @@ def get_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)): @router.post("/{node_id}/duplicate", response_model=schemas.FrameRelaySwitch, status_code=status.HTTP_201_CREATED) async def duplicate_frame_relay_switch( - destination_node_id: UUID = Body(..., embed=True), node: FrameRelaySwitch = Depends(dep_node) -): + destination_node_id: UUID = Body(..., embed=True), + node: FrameRelaySwitch = Depends(dep_node) +) -> schemas.FrameRelaySwitch: """ Duplicate a Frame Relay switch node. """ @@ -91,8 +95,9 @@ async def duplicate_frame_relay_switch( @router.put("/{node_id}", response_model=schemas.FrameRelaySwitch) async def update_frame_relay_switch( - node_data: schemas.FrameRelaySwitchUpdate, node: FrameRelaySwitch = Depends(dep_node) -): + node_data: schemas.FrameRelaySwitchUpdate, + node: FrameRelaySwitch = Depends(dep_node) +) -> schemas.FrameRelaySwitch: """ Update an Frame Relay switch node. """ @@ -107,7 +112,7 @@ async def update_frame_relay_switch( @router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT) -async def delete_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)): +async def delete_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)) -> None: """ Delete a Frame Relay switch node. """ @@ -116,7 +121,7 @@ async def delete_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)): @router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT) -def start_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)): +def start_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)) -> None: """ Start a Frame Relay switch node. This endpoint results in no action since Frame Relay switch nodes are always on. @@ -126,7 +131,7 @@ def start_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)): @router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT) -def stop_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)): +def stop_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)) -> None: """ Stop a Frame Relay switch node. This endpoint results in no action since Frame Relay switch nodes are always on. @@ -136,7 +141,7 @@ def stop_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)): @router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT) -def suspend_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)): +def suspend_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)) -> None: """ Suspend a Frame Relay switch node. This endpoint results in no action since Frame Relay switch nodes are always on. @@ -151,8 +156,12 @@ def suspend_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)): response_model=schemas.UDPNIO, ) async def create_nio( - adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: FrameRelaySwitch = Depends(dep_node) -): + *, + adapter_number: int = Path(..., ge=0, le=0), + port_number: int, + nio_data: schemas.UDPNIO, + node: FrameRelaySwitch = Depends(dep_node) +) -> schemas.UDPNIO: """ Add a NIO (Network Input/Output) to the node. The adapter number on the switch is always 0. @@ -164,7 +173,12 @@ async def create_nio( @router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT) -async def delete_nio(adapter_number: int, port_number: int, node: FrameRelaySwitch = Depends(dep_node)): +async def delete_nio( + *, + adapter_number: int = Path(..., ge=0, le=0), + port_number: int, + node: FrameRelaySwitch = Depends(dep_node) +) -> None: """ Remove a NIO (Network Input/Output) from the node. The adapter number on the switch is always 0. @@ -176,11 +190,12 @@ async def delete_nio(adapter_number: int, port_number: int, node: FrameRelaySwit @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start") async def start_capture( - adapter_number: int, - port_number: int, - node_capture_data: schemas.NodeCapture, - node: FrameRelaySwitch = Depends(dep_node), -): + *, + adapter_number: int = Path(..., ge=0, le=0), + port_number: int, + node_capture_data: schemas.NodeCapture, + node: FrameRelaySwitch = Depends(dep_node), +) -> dict: """ Start a packet capture on the node. The adapter number on the switch is always 0. @@ -194,7 +209,12 @@ async def start_capture( @router.post( "/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT ) -async def stop_capture(adapter_number: int, port_number: int, node: FrameRelaySwitch = Depends(dep_node)): +async def stop_capture( + *, + adapter_number: int = Path(..., ge=0, le=0), + port_number: int, + node: FrameRelaySwitch = Depends(dep_node) +) -> None: """ Stop a packet capture on the node. The adapter number on the switch is always 0. @@ -204,7 +224,12 @@ async def stop_capture(adapter_number: int, port_number: int, node: FrameRelaySw @router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream") -async def stream_pcap_file(adapter_number: int, port_number: int, node: FrameRelaySwitch = Depends(dep_node)): +async def stream_pcap_file( + *, + adapter_number: int = Path(..., ge=0, le=0), + port_number: int, + node: FrameRelaySwitch = Depends(dep_node) +) -> StreamingResponse: """ Stream the pcap capture file. The adapter number on the hub is always 0. diff --git a/gns3server/api/routes/compute/images.py b/gns3server/api/routes/compute/images.py index 6f5fe522..b5103aaf 100644 --- a/gns3server/api/routes/compute/images.py +++ b/gns3server/api/routes/compute/images.py @@ -54,7 +54,7 @@ async def get_dynamips_images() -> List[str]: @router.post("/dynamips/images/{filename:path}", status_code=status.HTTP_204_NO_CONTENT) -async def upload_dynamips_image(filename: str, request: Request): +async def upload_dynamips_image(filename: str, request: Request) -> None: """ Upload a Dynamips IOS image. """ @@ -64,7 +64,7 @@ async def upload_dynamips_image(filename: str, request: Request): @router.get("/dynamips/images/{filename:path}") -async def download_dynamips_image(filename: str): +async def download_dynamips_image(filename: str) -> FileResponse: """ Download a Dynamips IOS image. """ @@ -93,7 +93,7 @@ async def get_iou_images() -> List[str]: @router.post("/iou/images/{filename:path}", status_code=status.HTTP_204_NO_CONTENT) -async def upload_iou_image(filename: str, request: Request): +async def upload_iou_image(filename: str, request: Request) -> None: """ Upload an IOU image. """ @@ -103,7 +103,7 @@ async def upload_iou_image(filename: str, request: Request): @router.get("/iou/images/{filename:path}") -async def download_iou_image(filename: str): +async def download_iou_image(filename: str) -> FileResponse: """ Download an IOU image. """ @@ -129,14 +129,14 @@ async def get_qemu_images() -> List[str]: @router.post("/qemu/images/{filename:path}", status_code=status.HTTP_204_NO_CONTENT) -async def upload_qemu_image(filename: str, request: Request): +async def upload_qemu_image(filename: str, request: Request) -> None: qemu_manager = Qemu.instance() await qemu_manager.write_image(urllib.parse.unquote(filename), request.stream()) @router.get("/qemu/images/{filename:path}") -async def download_qemu_image(filename: str): +async def download_qemu_image(filename: str) -> FileResponse: qemu_manager = Qemu.instance() filename = urllib.parse.unquote(filename) diff --git a/gns3server/api/routes/compute/iou_nodes.py b/gns3server/api/routes/compute/iou_nodes.py index 32e19fcf..1234c10d 100644 --- a/gns3server/api/routes/compute/iou_nodes.py +++ b/gns3server/api/routes/compute/iou_nodes.py @@ -35,7 +35,7 @@ responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find router = APIRouter(responses=responses) -def dep_node(project_id: UUID, node_id: UUID): +def dep_node(project_id: UUID, node_id: UUID) -> IOUVM: """ Dependency to retrieve a node. """ @@ -51,7 +51,7 @@ def dep_node(project_id: UUID, node_id: UUID): status_code=status.HTTP_201_CREATED, responses={409: {"model": schemas.ErrorMessage, "description": "Could not create IOU node"}}, ) -async def create_iou_node(project_id: UUID, node_data: schemas.IOUCreate): +async def create_iou_node(project_id: UUID, node_data: schemas.IOUCreate) -> schemas.IOU: """ Create a new IOU node. """ @@ -83,7 +83,7 @@ async def create_iou_node(project_id: UUID, node_data: schemas.IOUCreate): @router.get("/{node_id}", response_model=schemas.IOU) -def get_iou_node(node: IOUVM = Depends(dep_node)): +def get_iou_node(node: IOUVM = Depends(dep_node)) -> schemas.IOU: """ Return an IOU node. """ @@ -92,7 +92,7 @@ def get_iou_node(node: IOUVM = Depends(dep_node)): @router.put("/{node_id}", response_model=schemas.IOU) -async def update_iou_node(node_data: schemas.IOUUpdate, node: IOUVM = Depends(dep_node)): +async def update_iou_node(node_data: schemas.IOUUpdate, node: IOUVM = Depends(dep_node)) -> schemas.IOU: """ Update an IOU node. """ @@ -113,7 +113,7 @@ async def update_iou_node(node_data: schemas.IOUUpdate, node: IOUVM = Depends(de @router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT) -async def delete_iou_node(node: IOUVM = Depends(dep_node)): +async def delete_iou_node(node: IOUVM = Depends(dep_node)) -> None: """ Delete an IOU node. """ @@ -122,7 +122,10 @@ async def delete_iou_node(node: IOUVM = Depends(dep_node)): @router.post("/{node_id}/duplicate", response_model=schemas.IOU, status_code=status.HTTP_201_CREATED) -async def duplicate_iou_node(destination_node_id: UUID = Body(..., embed=True), node: IOUVM = Depends(dep_node)): +async def duplicate_iou_node( + destination_node_id: UUID = Body(..., embed=True), + node: IOUVM = Depends(dep_node) +) -> schemas.IOU: """ Duplicate an IOU node. """ @@ -132,7 +135,7 @@ async def duplicate_iou_node(destination_node_id: UUID = Body(..., embed=True), @router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT) -async def start_iou_node(start_data: schemas.IOUStart, node: IOUVM = Depends(dep_node)): +async def start_iou_node(start_data: schemas.IOUStart, node: IOUVM = Depends(dep_node)) -> None: """ Start an IOU node. """ @@ -147,7 +150,7 @@ async def start_iou_node(start_data: schemas.IOUStart, node: IOUVM = Depends(dep @router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT) -async def stop_iou_node(node: IOUVM = Depends(dep_node)): +async def stop_iou_node(node: IOUVM = Depends(dep_node)) -> None: """ Stop an IOU node. """ @@ -156,7 +159,7 @@ async def stop_iou_node(node: IOUVM = Depends(dep_node)): @router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT) -def suspend_iou_node(node: IOUVM = Depends(dep_node)): +def suspend_iou_node(node: IOUVM = Depends(dep_node)) -> None: """ Suspend an IOU node. Does nothing since IOU doesn't support being suspended. @@ -166,7 +169,7 @@ def suspend_iou_node(node: IOUVM = Depends(dep_node)): @router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT) -async def reload_iou_node(node: IOUVM = Depends(dep_node)): +async def reload_iou_node(node: IOUVM = Depends(dep_node)) -> None: """ Reload an IOU node. """ @@ -184,7 +187,7 @@ async def create_iou_node_nio( port_number: int, nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO], node: IOUVM = Depends(dep_node), -): +) -> Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO]: """ Add a NIO (Network Input/Output) to the node. """ @@ -204,7 +207,7 @@ async def update_iou_node_nio( port_number: int, nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO], node: IOUVM = Depends(dep_node), -): +) -> Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO]: """ Update a NIO (Network Input/Output) on the node. """ @@ -217,7 +220,7 @@ async def update_iou_node_nio( @router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT) -async def delete_iou_node_nio(adapter_number: int, port_number: int, node: IOUVM = Depends(dep_node)): +async def delete_iou_node_nio(adapter_number: int, port_number: int, node: IOUVM = Depends(dep_node)) -> None: """ Delete a NIO (Network Input/Output) from the node. """ @@ -227,8 +230,11 @@ async def delete_iou_node_nio(adapter_number: int, port_number: int, node: IOUVM @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start") async def start_iou_node_capture( - adapter_number: int, port_number: int, node_capture_data: schemas.NodeCapture, node: IOUVM = Depends(dep_node) -): + adapter_number: int, + port_number: int, + node_capture_data: schemas.NodeCapture, + node: IOUVM = Depends(dep_node) +) -> dict: """ Start a packet capture on the node. """ @@ -241,7 +247,7 @@ async def start_iou_node_capture( @router.post( "/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT ) -async def stop_iou_node_capture(adapter_number: int, port_number: int, node: IOUVM = Depends(dep_node)): +async def stop_iou_node_capture(adapter_number: int, port_number: int, node: IOUVM = Depends(dep_node)) -> None: """ Stop a packet capture on the node. """ @@ -250,7 +256,11 @@ async def stop_iou_node_capture(adapter_number: int, port_number: int, node: IOU @router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream") -async def stream_pcap_file(adapter_number: int, port_number: int, node: IOUVM = Depends(dep_node)): +async def stream_pcap_file( + adapter_number: int, + port_number: int, + node: IOUVM = Depends(dep_node) +) -> StreamingResponse: """ Stream the pcap capture file. """ @@ -261,7 +271,7 @@ async def stream_pcap_file(adapter_number: int, port_number: int, node: IOUVM = @router.websocket("/{node_id}/console/ws") -async def console_ws(websocket: WebSocket, node: IOUVM = Depends(dep_node)): +async def console_ws(websocket: WebSocket, node: IOUVM = Depends(dep_node)) -> None: """ Console WebSocket. """ @@ -270,6 +280,6 @@ async def console_ws(websocket: WebSocket, node: IOUVM = Depends(dep_node)): @router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT) -async def reset_console(node: IOUVM = Depends(dep_node)): +async def reset_console(node: IOUVM = Depends(dep_node)) -> None: await node.reset_console() diff --git a/gns3server/api/routes/compute/nat_nodes.py b/gns3server/api/routes/compute/nat_nodes.py index f4f92f6a..c15f9225 100644 --- a/gns3server/api/routes/compute/nat_nodes.py +++ b/gns3server/api/routes/compute/nat_nodes.py @@ -20,7 +20,7 @@ API routes for NAT nodes. import os -from fastapi import APIRouter, Depends, status +from fastapi import APIRouter, Depends, Path, status from fastapi.encoders import jsonable_encoder from fastapi.responses import StreamingResponse from typing import Union @@ -35,7 +35,7 @@ responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find router = APIRouter(responses=responses) -def dep_node(project_id: UUID, node_id: UUID): +def dep_node(project_id: UUID, node_id: UUID) -> Nat: """ Dependency to retrieve a node. """ @@ -51,7 +51,7 @@ def dep_node(project_id: UUID, node_id: UUID): status_code=status.HTTP_201_CREATED, responses={409: {"model": schemas.ErrorMessage, "description": "Could not create NAT node"}}, ) -async def create_nat_node(project_id: UUID, node_data: schemas.NATCreate): +async def create_nat_node(project_id: UUID, node_data: schemas.NATCreate) -> schemas.NAT: """ Create a new NAT node. """ @@ -71,7 +71,7 @@ async def create_nat_node(project_id: UUID, node_data: schemas.NATCreate): @router.get("/{node_id}", response_model=schemas.NAT) -def get_nat_node(node: Nat = Depends(dep_node)): +def get_nat_node(node: Nat = Depends(dep_node)) -> schemas.NAT: """ Return a NAT node. """ @@ -80,7 +80,7 @@ def get_nat_node(node: Nat = Depends(dep_node)): @router.put("/{node_id}", response_model=schemas.NAT) -def update_nat_node(node_data: schemas.NATUpdate, node: Nat = Depends(dep_node)): +def update_nat_node(node_data: schemas.NATUpdate, node: Nat = Depends(dep_node)) -> schemas.NAT: """ Update a NAT node. """ @@ -94,7 +94,7 @@ def update_nat_node(node_data: schemas.NATUpdate, node: Nat = Depends(dep_node)) @router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT) -async def delete_nat_node(node: Nat = Depends(dep_node)): +async def delete_nat_node(node: Nat = Depends(dep_node)) -> None: """ Delete a cloud node. """ @@ -103,7 +103,7 @@ async def delete_nat_node(node: Nat = Depends(dep_node)): @router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT) -async def start_nat_node(node: Nat = Depends(dep_node)): +async def start_nat_node(node: Nat = Depends(dep_node)) -> None: """ Start a NAT node. """ @@ -112,7 +112,7 @@ async def start_nat_node(node: Nat = Depends(dep_node)): @router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT) -async def stop_nat_node(node: Nat = Depends(dep_node)): +async def stop_nat_node(node: Nat = Depends(dep_node)) -> None: """ Stop a NAT node. This endpoint results in no action since cloud nodes cannot be stopped. @@ -122,7 +122,7 @@ async def stop_nat_node(node: Nat = Depends(dep_node)): @router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT) -async def suspend_nat_node(node: Nat = Depends(dep_node)): +async def suspend_nat_node(node: Nat = Depends(dep_node)) -> None: """ Suspend a NAT node. This endpoint results in no action since NAT nodes cannot be suspended. @@ -137,11 +137,12 @@ async def suspend_nat_node(node: Nat = Depends(dep_node)): response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO], ) async def create_nat_node_nio( - adapter_number: int, - port_number: int, - nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO], - node: Nat = Depends(dep_node), -): + *, + adapter_number: int = Path(..., ge=0, le=0), + port_number: int, + nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO], + node: Nat = Depends(dep_node), +) -> Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO]: """ Add a NIO (Network Input/Output) to the node. The adapter number on the cloud is always 0. @@ -158,11 +159,12 @@ async def create_nat_node_nio( response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO], ) async def update_nat_node_nio( - adapter_number: int, - port_number: int, - nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO], - node: Nat = Depends(dep_node), -): + *, + adapter_number: int = Path(..., ge=0, le=0), + port_number: int, + nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO], + node: Nat = Depends(dep_node), +) -> Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO]: """ Update a NIO (Network Input/Output) to the node. The adapter number on the cloud is always 0. @@ -176,7 +178,12 @@ async def update_nat_node_nio( @router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT) -async def delete_nat_node_nio(adapter_number: int, port_number: int, node: Nat = Depends(dep_node)): +async def delete_nat_node_nio( + *, + adapter_number: int = Path(..., ge=0, le=0), + port_number: int, + node: Nat = Depends(dep_node) +) -> None: """ Remove a NIO (Network Input/Output) from the node. The adapter number on the cloud is always 0. @@ -187,8 +194,12 @@ async def delete_nat_node_nio(adapter_number: int, port_number: int, node: Nat = @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start") async def start_nat_node_capture( - adapter_number: int, port_number: int, node_capture_data: schemas.NodeCapture, node: Nat = Depends(dep_node) -): + *, + adapter_number: int = Path(..., ge=0, le=0), + port_number: int, + node_capture_data: schemas.NodeCapture, + node: Nat = Depends(dep_node) +) -> dict: """ Start a packet capture on the node. The adapter number on the cloud is always 0. @@ -202,7 +213,12 @@ async def start_nat_node_capture( @router.post( "/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT ) -async def stop_nat_node_capture(adapter_number: int, port_number: int, node: Nat = Depends(dep_node)): +async def stop_nat_node_capture( + *, + adapter_number: int = Path(..., ge=0, le=0), + port_number: int, + node: Nat = Depends(dep_node) +): """ Stop a packet capture on the node. The adapter number on the cloud is always 0. @@ -212,7 +228,12 @@ async def stop_nat_node_capture(adapter_number: int, port_number: int, node: Nat @router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream") -async def stream_pcap_file(adapter_number: int, port_number: int, node: Nat = Depends(dep_node)): +async def stream_pcap_file( + *, + adapter_number: int = Path(..., ge=0, le=0), + port_number: int, + node: Nat = Depends(dep_node) +) -> StreamingResponse: """ Stream the pcap capture file. The adapter number on the cloud is always 0. diff --git a/gns3server/api/routes/compute/notifications.py b/gns3server/api/routes/compute/notifications.py index 830488e5..99d9ed31 100644 --- a/gns3server/api/routes/compute/notifications.py +++ b/gns3server/api/routes/compute/notifications.py @@ -31,7 +31,7 @@ router = APIRouter() @router.websocket("/notifications/ws") -async def notification_ws(websocket: WebSocket): +async def notification_ws(websocket: WebSocket) -> None: """ Receive project notifications about the project from WebSocket. """ diff --git a/gns3server/api/routes/compute/projects.py b/gns3server/api/routes/compute/projects.py index fd446db9..8d9fe3b8 100644 --- a/gns3server/api/routes/compute/projects.py +++ b/gns3server/api/routes/compute/projects.py @@ -41,7 +41,7 @@ router = APIRouter() _notifications_listening = {} -def dep_project(project_id: UUID): +def dep_project(project_id: UUID) -> Project: """ Dependency to retrieve a project. """ @@ -52,7 +52,7 @@ def dep_project(project_id: UUID): @router.get("/projects", response_model=List[schemas.Project]) -def get_compute_projects(): +def get_compute_projects() -> List[schemas.Project]: """ Get all projects opened on the compute. """ @@ -62,7 +62,7 @@ def get_compute_projects(): @router.post("/projects", status_code=status.HTTP_201_CREATED, response_model=schemas.Project) -def create_compute_project(project_data: schemas.ProjectCreate): +def create_compute_project(project_data: schemas.ProjectCreate) -> schemas.Project: """ Create a new project on the compute. """ @@ -79,7 +79,10 @@ def create_compute_project(project_data: schemas.ProjectCreate): @router.put("/projects/{project_id}", response_model=schemas.Project) -async def update_compute_project(project_data: schemas.ProjectUpdate, project: Project = Depends(dep_project)): +async def update_compute_project( + project_data: schemas.ProjectUpdate, + project: Project = Depends(dep_project) +) -> schemas.Project: """ Update project on the compute. """ @@ -89,7 +92,7 @@ async def update_compute_project(project_data: schemas.ProjectUpdate, project: P @router.get("/projects/{project_id}", response_model=schemas.Project) -def get_compute_project(project: Project = Depends(dep_project)): +def get_compute_project(project: Project = Depends(dep_project)) -> schemas.Project: """ Return a project from the compute. """ @@ -98,7 +101,7 @@ def get_compute_project(project: Project = Depends(dep_project)): @router.post("/projects/{project_id}/close", status_code=status.HTTP_204_NO_CONTENT) -async def close_compute_project(project: Project = Depends(dep_project)): +async def close_compute_project(project: Project = Depends(dep_project)) -> None: """ Close a project on the compute. """ @@ -116,7 +119,7 @@ async def close_compute_project(project: Project = Depends(dep_project)): @router.delete("/projects/{project_id}", status_code=status.HTTP_204_NO_CONTENT) -async def delete_compute_project(project: Project = Depends(dep_project)): +async def delete_compute_project(project: Project = Depends(dep_project)) -> None: """ Delete project from the compute. """ @@ -180,7 +183,7 @@ async def delete_compute_project(project: Project = Depends(dep_project)): @router.get("/projects/{project_id}/files", response_model=List[schemas.ProjectFile]) -async def get_compute_project_files(project: Project = Depends(dep_project)): +async def get_compute_project_files(project: Project = Depends(dep_project)) -> List[schemas.ProjectFile]: """ Return files belonging to a project. """ @@ -189,7 +192,7 @@ async def get_compute_project_files(project: Project = Depends(dep_project)): @router.get("/projects/{project_id}/files/{file_path:path}") -async def get_compute_project_file(file_path: str, project: Project = Depends(dep_project)): +async def get_compute_project_file(file_path: str, project: Project = Depends(dep_project)) -> FileResponse: """ Get a file from a project. """ @@ -208,7 +211,7 @@ async def get_compute_project_file(file_path: str, project: Project = Depends(de @router.post("/projects/{project_id}/files/{file_path:path}", status_code=status.HTTP_204_NO_CONTENT) -async def write_compute_project_file(file_path: str, request: Request, project: Project = Depends(dep_project)): +async def write_compute_project_file(file_path: str, request: Request, project: Project = Depends(dep_project)) -> None: path = os.path.normpath(file_path) diff --git a/gns3server/api/routes/compute/qemu_nodes.py b/gns3server/api/routes/compute/qemu_nodes.py index 97645af6..16a86091 100644 --- a/gns3server/api/routes/compute/qemu_nodes.py +++ b/gns3server/api/routes/compute/qemu_nodes.py @@ -19,9 +19,8 @@ API routes for Qemu nodes. """ import os -import sys -from fastapi import APIRouter, WebSocket, Depends, Body, status +from fastapi import APIRouter, WebSocket, Depends, Body, Path, status from fastapi.encoders import jsonable_encoder from fastapi.responses import StreamingResponse from uuid import UUID @@ -36,7 +35,7 @@ responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find router = APIRouter(responses=responses) -def dep_node(project_id: UUID, node_id: UUID): +def dep_node(project_id: UUID, node_id: UUID) -> QemuVM: """ Dependency to retrieve a node. """ @@ -52,7 +51,7 @@ def dep_node(project_id: UUID, node_id: UUID): status_code=status.HTTP_201_CREATED, responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Qemu node"}}, ) -async def create_qemu_node(project_id: UUID, node_data: schemas.QemuCreate): +async def create_qemu_node(project_id: UUID, node_data: schemas.QemuCreate) -> schemas.Qemu: """ Create a new Qemu node. """ @@ -80,7 +79,7 @@ async def create_qemu_node(project_id: UUID, node_data: schemas.QemuCreate): @router.get("/{node_id}", response_model=schemas.Qemu) -def get_qemu_node(node: QemuVM = Depends(dep_node)): +def get_qemu_node(node: QemuVM = Depends(dep_node)) -> schemas.Qemu: """ Return a Qemu node. """ @@ -89,7 +88,7 @@ def get_qemu_node(node: QemuVM = Depends(dep_node)): @router.put("/{node_id}", response_model=schemas.Qemu) -async def update_qemu_node(node_data: schemas.QemuUpdate, node: QemuVM = Depends(dep_node)): +async def update_qemu_node(node_data: schemas.QemuUpdate, node: QemuVM = Depends(dep_node)) -> schemas.Qemu: """ Update a Qemu node. """ @@ -105,7 +104,7 @@ async def update_qemu_node(node_data: schemas.QemuUpdate, node: QemuVM = Depends @router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT) -async def delete_qemu_node(node: QemuVM = Depends(dep_node)): +async def delete_qemu_node(node: QemuVM = Depends(dep_node)) -> None: """ Delete a Qemu node. """ @@ -114,7 +113,10 @@ async def delete_qemu_node(node: QemuVM = Depends(dep_node)): @router.post("/{node_id}/duplicate", response_model=schemas.Qemu, status_code=status.HTTP_201_CREATED) -async def duplicate_qemu_node(destination_node_id: UUID = Body(..., embed=True), node: QemuVM = Depends(dep_node)): +async def duplicate_qemu_node( + destination_node_id: UUID = Body(..., embed=True), + node: QemuVM = Depends(dep_node) +) -> schemas.Qemu: """ Duplicate a Qemu node. """ @@ -124,13 +126,13 @@ async def duplicate_qemu_node(destination_node_id: UUID = Body(..., embed=True), @router.post("/{node_id}/resize_disk", status_code=status.HTTP_204_NO_CONTENT) -async def resize_qemu_node_disk(node_data: schemas.QemuDiskResize, node: QemuVM = Depends(dep_node)): +async def resize_qemu_node_disk(node_data: schemas.QemuDiskResize, node: QemuVM = Depends(dep_node)) -> None: await node.resize_disk(node_data.drive_name, node_data.extend) @router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT) -async def start_qemu_node(node: QemuVM = Depends(dep_node)): +async def start_qemu_node(node: QemuVM = Depends(dep_node)) -> None: """ Start a Qemu node. """ @@ -146,7 +148,7 @@ async def start_qemu_node(node: QemuVM = Depends(dep_node)): @router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT) -async def stop_qemu_node(node: QemuVM = Depends(dep_node)): +async def stop_qemu_node(node: QemuVM = Depends(dep_node)) -> None: """ Stop a Qemu node. """ @@ -155,7 +157,7 @@ async def stop_qemu_node(node: QemuVM = Depends(dep_node)): @router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT) -async def reload_qemu_node(node: QemuVM = Depends(dep_node)): +async def reload_qemu_node(node: QemuVM = Depends(dep_node)) -> None: """ Reload a Qemu node. """ @@ -164,7 +166,7 @@ async def reload_qemu_node(node: QemuVM = Depends(dep_node)): @router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT) -async def suspend_qemu_node(node: QemuVM = Depends(dep_node)): +async def suspend_qemu_node(node: QemuVM = Depends(dep_node)) -> None: """ Suspend a Qemu node. """ @@ -173,7 +175,7 @@ async def suspend_qemu_node(node: QemuVM = Depends(dep_node)): @router.post("/{node_id}/resume", status_code=status.HTTP_204_NO_CONTENT) -async def resume_qemu_node(node: QemuVM = Depends(dep_node)): +async def resume_qemu_node(node: QemuVM = Depends(dep_node)) -> None: """ Resume a Qemu node. """ @@ -187,8 +189,12 @@ async def resume_qemu_node(node: QemuVM = Depends(dep_node)): response_model=schemas.UDPNIO, ) async def create_qemu_node_nio( - adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: QemuVM = Depends(dep_node) -): + *, + adapter_number: int, + port_number: int = Path(..., ge=0, le=0), + nio_data: schemas.UDPNIO, + node: QemuVM = Depends(dep_node) +) -> schemas.UDPNIO: """ Add a NIO (Network Input/Output) to the node. The port number on the Qemu node is always 0. @@ -205,8 +211,12 @@ async def create_qemu_node_nio( response_model=schemas.UDPNIO, ) async def update_qemu_node_nio( - adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: QemuVM = Depends(dep_node) -): + *, + adapter_number: int, + port_number: int = Path(..., ge=0, le=0), + nio_data: schemas.UDPNIO, + node: QemuVM = Depends(dep_node) +) -> schemas.UDPNIO: """ Update a NIO (Network Input/Output) on the node. The port number on the Qemu node is always 0. @@ -222,7 +232,11 @@ async def update_qemu_node_nio( @router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT) -async def delete_qemu_node_nio(adapter_number: int, port_number: int, node: QemuVM = Depends(dep_node)): +async def delete_qemu_node_nio( + adapter_number: int, + port_number: int = Path(..., ge=0, le=0), + node: QemuVM = Depends(dep_node) +) -> None: """ Delete a NIO (Network Input/Output) from the node. The port number on the Qemu node is always 0. @@ -233,8 +247,12 @@ async def delete_qemu_node_nio(adapter_number: int, port_number: int, node: Qemu @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start") async def start_qemu_node_capture( - adapter_number: int, port_number: int, node_capture_data: schemas.NodeCapture, node: QemuVM = Depends(dep_node) -): + *, + adapter_number: int, + port_number: int = Path(..., ge=0, le=0), + node_capture_data: schemas.NodeCapture, + node: QemuVM = Depends(dep_node) +) -> dict: """ Start a packet capture on the node. The port number on the Qemu node is always 0. @@ -248,7 +266,11 @@ async def start_qemu_node_capture( @router.post( "/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT ) -async def stop_qemu_node_capture(adapter_number: int, port_number: int, node: QemuVM = Depends(dep_node)): +async def stop_qemu_node_capture( + adapter_number: int, + port_number: int = Path(..., ge=0, le=0), + node: QemuVM = Depends(dep_node) +) -> None: """ Stop a packet capture on the node. The port number on the Qemu node is always 0. @@ -258,7 +280,10 @@ async def stop_qemu_node_capture(adapter_number: int, port_number: int, node: Qe @router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream") -async def stream_pcap_file(adapter_number: int, port_number: int, node: QemuVM = Depends(dep_node)): +async def stream_pcap_file( + adapter_number: int, + port_number: int = Path(..., ge=0, le=0), + node: QemuVM = Depends(dep_node)) -> StreamingResponse: """ Stream the pcap capture file. The port number on the Qemu node is always 0. @@ -270,7 +295,7 @@ async def stream_pcap_file(adapter_number: int, port_number: int, node: QemuVM = @router.websocket("/{node_id}/console/ws") -async def console_ws(websocket: WebSocket, node: QemuVM = Depends(dep_node)): +async def console_ws(websocket: WebSocket, node: QemuVM = Depends(dep_node)) -> None: """ Console WebSocket. """ @@ -279,6 +304,6 @@ async def console_ws(websocket: WebSocket, node: QemuVM = Depends(dep_node)): @router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT) -async def reset_console(node: QemuVM = Depends(dep_node)): +async def reset_console(node: QemuVM = Depends(dep_node)) -> None: await node.reset_console() diff --git a/gns3server/api/routes/compute/virtualbox_nodes.py b/gns3server/api/routes/compute/virtualbox_nodes.py index bb23563c..93b548a0 100644 --- a/gns3server/api/routes/compute/virtualbox_nodes.py +++ b/gns3server/api/routes/compute/virtualbox_nodes.py @@ -20,7 +20,7 @@ API routes for VirtualBox nodes. import os -from fastapi import APIRouter, WebSocket, Depends, status +from fastapi import APIRouter, WebSocket, Depends, Path, status from fastapi.encoders import jsonable_encoder from fastapi.responses import StreamingResponse from uuid import UUID @@ -28,7 +28,6 @@ from uuid import UUID from gns3server import schemas from gns3server.compute.virtualbox import VirtualBox from gns3server.compute.virtualbox.virtualbox_error import VirtualBoxError -from gns3server.compute.project_manager import ProjectManager from gns3server.compute.virtualbox.virtualbox_vm import VirtualBoxVM responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or VirtualBox node"}} @@ -36,7 +35,7 @@ responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find router = APIRouter(responses=responses) -def dep_node(project_id: UUID, node_id: UUID): +def dep_node(project_id: UUID, node_id: UUID) -> VirtualBoxVM: """ Dependency to retrieve a node. """ @@ -52,7 +51,7 @@ def dep_node(project_id: UUID, node_id: UUID): status_code=status.HTTP_201_CREATED, responses={409: {"model": schemas.ErrorMessage, "description": "Could not create VirtualBox node"}}, ) -async def create_virtualbox_node(project_id: UUID, node_data: schemas.VirtualBoxCreate): +async def create_virtualbox_node(project_id: UUID, node_data: schemas.VirtualBoxCreate) -> schemas.VirtualBox: """ Create a new VirtualBox node. """ @@ -84,7 +83,7 @@ async def create_virtualbox_node(project_id: UUID, node_data: schemas.VirtualBox @router.get("/{node_id}", response_model=schemas.VirtualBox) -def get_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)): +def get_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> schemas.VirtualBox: """ Return a VirtualBox node. """ @@ -93,7 +92,10 @@ def get_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)): @router.put("/{node_id}", response_model=schemas.VirtualBox) -async def update_virtualbox_node(node_data: schemas.VirtualBoxUpdate, node: VirtualBoxVM = Depends(dep_node)): +async def update_virtualbox_node( + node_data: schemas.VirtualBoxUpdate, + node: VirtualBoxVM = Depends(dep_node) +) -> schemas.VirtualBox: """ Update a VirtualBox node. """ @@ -135,7 +137,7 @@ async def update_virtualbox_node(node_data: schemas.VirtualBoxUpdate, node: Virt @router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT) -async def delete_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)): +async def delete_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None: """ Delete a VirtualBox node. """ @@ -144,7 +146,7 @@ async def delete_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)): @router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT) -async def start_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)): +async def start_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None: """ Start a VirtualBox node. """ @@ -153,7 +155,7 @@ async def start_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)): @router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT) -async def stop_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)): +async def stop_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None: """ Stop a VirtualBox node. """ @@ -162,7 +164,7 @@ async def stop_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)): @router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT) -async def suspend_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)): +async def suspend_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None: """ Suspend a VirtualBox node. """ @@ -171,7 +173,7 @@ async def suspend_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)): @router.post("/{node_id}/resume", status_code=status.HTTP_204_NO_CONTENT) -async def resume_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)): +async def resume_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None: """ Resume a VirtualBox node. """ @@ -180,7 +182,7 @@ async def resume_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)): @router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT) -async def reload_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)): +async def reload_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None: """ Reload a VirtualBox node. """ @@ -194,8 +196,12 @@ async def reload_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)): response_model=schemas.UDPNIO, ) async def create_virtualbox_node_nio( - adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: VirtualBoxVM = Depends(dep_node) -): + *, + adapter_number: int, + port_number: int = Path(..., ge=0, le=0), + nio_data: schemas.UDPNIO, + node: VirtualBoxVM = Depends(dep_node) +) -> schemas.UDPNIO: """ Add a NIO (Network Input/Output) to the node. The port number on the VirtualBox node is always 0. @@ -212,8 +218,12 @@ async def create_virtualbox_node_nio( response_model=schemas.UDPNIO, ) async def update_virtualbox_node_nio( - adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: VirtualBoxVM = Depends(dep_node) -): + *, + adapter_number: int, + port_number: int = Path(..., ge=0, le=0), + nio_data: schemas.UDPNIO, + node: VirtualBoxVM = Depends(dep_node) +) -> schemas.UDPNIO: """ Update a NIO (Network Input/Output) on the node. The port number on the VirtualBox node is always 0. @@ -229,7 +239,11 @@ async def update_virtualbox_node_nio( @router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT) -async def delete_virtualbox_node_nio(adapter_number: int, port_number: int, node: VirtualBoxVM = Depends(dep_node)): +async def delete_virtualbox_node_nio( + adapter_number: int, + port_number: int = Path(..., ge=0, le=0), + node: VirtualBoxVM = Depends(dep_node) +) -> None: """ Delete a NIO (Network Input/Output) from the node. The port number on the VirtualBox node is always 0. @@ -240,11 +254,12 @@ async def delete_virtualbox_node_nio(adapter_number: int, port_number: int, node @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start") async def start_virtualbox_node_capture( - adapter_number: int, - port_number: int, - node_capture_data: schemas.NodeCapture, - node: VirtualBoxVM = Depends(dep_node), -): + *, + adapter_number: int, + port_number: int = Path(..., ge=0, le=0), + node_capture_data: schemas.NodeCapture, + node: VirtualBoxVM = Depends(dep_node), +) -> dict: """ Start a packet capture on the node. The port number on the VirtualBox node is always 0. @@ -258,7 +273,11 @@ async def start_virtualbox_node_capture( @router.post( "/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT ) -async def stop_virtualbox_node_capture(adapter_number: int, port_number: int, node: VirtualBoxVM = Depends(dep_node)): +async def stop_virtualbox_node_capture( + adapter_number: int, + port_number: int = Path(..., ge=0, le=0), + node: VirtualBoxVM = Depends(dep_node) +) -> None: """ Stop a packet capture on the node. The port number on the VirtualBox node is always 0. @@ -268,7 +287,11 @@ async def stop_virtualbox_node_capture(adapter_number: int, port_number: int, no @router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream") -async def stream_pcap_file(adapter_number: int, port_number: int, node: VirtualBoxVM = Depends(dep_node)): +async def stream_pcap_file( + adapter_number: int, + port_number: int = Path(..., ge=0, le=0), + node: VirtualBoxVM = Depends(dep_node) +) -> StreamingResponse: """ Stream the pcap capture file. The port number on the VirtualBox node is always 0. @@ -280,7 +303,7 @@ async def stream_pcap_file(adapter_number: int, port_number: int, node: VirtualB @router.websocket("/{node_id}/console/ws") -async def console_ws(websocket: WebSocket, node: VirtualBoxVM = Depends(dep_node)): +async def console_ws(websocket: WebSocket, node: VirtualBoxVM = Depends(dep_node)) -> None: """ Console WebSocket. """ @@ -289,6 +312,6 @@ async def console_ws(websocket: WebSocket, node: VirtualBoxVM = Depends(dep_node @router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT) -async def reset_console(node: VirtualBoxVM = Depends(dep_node)): +async def reset_console(node: VirtualBoxVM = Depends(dep_node)) -> None: await node.reset_console() diff --git a/gns3server/api/routes/compute/vmware_nodes.py b/gns3server/api/routes/compute/vmware_nodes.py index 05ef6929..976b749e 100644 --- a/gns3server/api/routes/compute/vmware_nodes.py +++ b/gns3server/api/routes/compute/vmware_nodes.py @@ -20,7 +20,7 @@ API routes for VMware nodes. import os -from fastapi import APIRouter, WebSocket, Depends, status +from fastapi import APIRouter, WebSocket, Depends, Path, status from fastapi.encoders import jsonable_encoder from fastapi.responses import StreamingResponse from uuid import UUID @@ -35,7 +35,7 @@ responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find router = APIRouter(responses=responses) -def dep_node(project_id: UUID, node_id: UUID): +def dep_node(project_id: UUID, node_id: UUID) -> VMwareVM: """ Dependency to retrieve a node. """ @@ -51,7 +51,7 @@ def dep_node(project_id: UUID, node_id: UUID): status_code=status.HTTP_201_CREATED, responses={409: {"model": schemas.ErrorMessage, "description": "Could not create VMware node"}}, ) -async def create_vmware_node(project_id: UUID, node_data: schemas.VMwareCreate): +async def create_vmware_node(project_id: UUID, node_data: schemas.VMwareCreate) -> schemas.VMware: """ Create a new VMware node. """ @@ -77,7 +77,7 @@ async def create_vmware_node(project_id: UUID, node_data: schemas.VMwareCreate): @router.get("/{node_id}", response_model=schemas.VMware) -def get_vmware_node(node: VMwareVM = Depends(dep_node)): +def get_vmware_node(node: VMwareVM = Depends(dep_node)) -> schemas.VMware: """ Return a VMware node. """ @@ -86,7 +86,7 @@ def get_vmware_node(node: VMwareVM = Depends(dep_node)): @router.put("/{node_id}", response_model=schemas.VMware) -def update_vmware_node(node_data: schemas.VMwareUpdate, node: VMwareVM = Depends(dep_node)): +def update_vmware_node(node_data: schemas.VMwareUpdate, node: VMwareVM = Depends(dep_node)) -> schemas.VMware: """ Update a VMware node. """ @@ -103,7 +103,7 @@ def update_vmware_node(node_data: schemas.VMwareUpdate, node: VMwareVM = Depends @router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT) -async def delete_vmware_node(node: VMwareVM = Depends(dep_node)): +async def delete_vmware_node(node: VMwareVM = Depends(dep_node)) -> None: """ Delete a VMware node. """ @@ -112,7 +112,7 @@ async def delete_vmware_node(node: VMwareVM = Depends(dep_node)): @router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT) -async def start_vmware_node(node: VMwareVM = Depends(dep_node)): +async def start_vmware_node(node: VMwareVM = Depends(dep_node)) -> None: """ Start a VMware node. """ @@ -121,7 +121,7 @@ async def start_vmware_node(node: VMwareVM = Depends(dep_node)): @router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT) -async def stop_vmware_node(node: VMwareVM = Depends(dep_node)): +async def stop_vmware_node(node: VMwareVM = Depends(dep_node)) -> None: """ Stop a VMware node. """ @@ -130,7 +130,7 @@ async def stop_vmware_node(node: VMwareVM = Depends(dep_node)): @router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT) -async def suspend_vmware_node(node: VMwareVM = Depends(dep_node)): +async def suspend_vmware_node(node: VMwareVM = Depends(dep_node)) -> None: """ Suspend a VMware node. """ @@ -139,7 +139,7 @@ async def suspend_vmware_node(node: VMwareVM = Depends(dep_node)): @router.post("/{node_id}/resume", status_code=status.HTTP_204_NO_CONTENT) -async def resume_vmware_node(node: VMwareVM = Depends(dep_node)): +async def resume_vmware_node(node: VMwareVM = Depends(dep_node)) -> None: """ Resume a VMware node. """ @@ -148,7 +148,7 @@ async def resume_vmware_node(node: VMwareVM = Depends(dep_node)): @router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT) -async def reload_vmware_node(node: VMwareVM = Depends(dep_node)): +async def reload_vmware_node(node: VMwareVM = Depends(dep_node)) -> None: """ Reload a VMware node. """ @@ -162,8 +162,12 @@ async def reload_vmware_node(node: VMwareVM = Depends(dep_node)): response_model=schemas.UDPNIO, ) async def create_vmware_node_nio( - adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: VMwareVM = Depends(dep_node) -): + *, + adapter_number: int, + port_number: int = Path(..., ge=0, le=0), + nio_data: schemas.UDPNIO, + node: VMwareVM = Depends(dep_node) +) -> schemas.UDPNIO: """ Add a NIO (Network Input/Output) to the node. The port number on the VMware node is always 0. @@ -180,8 +184,12 @@ async def create_vmware_node_nio( response_model=schemas.UDPNIO, ) async def update_vmware_node_nio( - adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: VMwareVM = Depends(dep_node) -): + *, + adapter_number: int, + port_number: int = Path(..., ge=0, le=0), + nio_data: schemas.UDPNIO, + node: VMwareVM = Depends(dep_node) +) -> schemas.UDPNIO: """ Update a NIO (Network Input/Output) on the node. The port number on the VMware node is always 0. @@ -195,7 +203,11 @@ async def update_vmware_node_nio( @router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT) -async def delete_vmware_node_nio(adapter_number: int, port_number: int, node: VMwareVM = Depends(dep_node)): +async def delete_vmware_node_nio( + adapter_number: int, + port_number: int = Path(..., ge=0, le=0), + node: VMwareVM = Depends(dep_node) +) -> None: """ Delete a NIO (Network Input/Output) from the node. The port number on the VMware node is always 0. @@ -206,8 +218,12 @@ async def delete_vmware_node_nio(adapter_number: int, port_number: int, node: VM @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start") async def start_vmware_node_capture( - adapter_number: int, port_number: int, node_capture_data: schemas.NodeCapture, node: VMwareVM = Depends(dep_node) -): + *, + adapter_number: int, + port_number: int = Path(..., ge=0, le=0), + node_capture_data: schemas.NodeCapture, + node: VMwareVM = Depends(dep_node) +) -> dict: """ Start a packet capture on the node. The port number on the VMware node is always 0. @@ -221,7 +237,11 @@ async def start_vmware_node_capture( @router.post( "/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT ) -async def stop_vmware_node_capture(adapter_number: int, port_number: int, node: VMwareVM = Depends(dep_node)): +async def stop_vmware_node_capture( + adapter_number: int, + port_number: int = Path(..., ge=0, le=0), + node: VMwareVM = Depends(dep_node) +) -> None: """ Stop a packet capture on the node. The port number on the VMware node is always 0. @@ -231,7 +251,11 @@ async def stop_vmware_node_capture(adapter_number: int, port_number: int, node: @router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream") -async def stream_pcap_file(adapter_number: int, port_number: int, node: VMwareVM = Depends(dep_node)): +async def stream_pcap_file( + adapter_number: int, + port_number: int = Path(..., ge=0, le=0), + node: VMwareVM = Depends(dep_node) +) -> StreamingResponse: """ Stream the pcap capture file. The port number on the VMware node is always 0. @@ -256,7 +280,7 @@ def allocate_vmnet(node: VMwareVM = Depends(dep_node)) -> dict: @router.websocket("/{node_id}/console/ws") -async def console_ws(websocket: WebSocket, node: VMwareVM = Depends(dep_node)): +async def console_ws(websocket: WebSocket, node: VMwareVM = Depends(dep_node)) -> None: """ Console WebSocket. """ @@ -265,6 +289,6 @@ async def console_ws(websocket: WebSocket, node: VMwareVM = Depends(dep_node)): @router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT) -async def reset_console(node: VMwareVM = Depends(dep_node)): +async def reset_console(node: VMwareVM = Depends(dep_node)) -> None: await node.reset_console() diff --git a/gns3server/api/routes/compute/vpcs_nodes.py b/gns3server/api/routes/compute/vpcs_nodes.py index a34d99d5..7d9ffab9 100644 --- a/gns3server/api/routes/compute/vpcs_nodes.py +++ b/gns3server/api/routes/compute/vpcs_nodes.py @@ -20,7 +20,7 @@ API routes for VPCS nodes. import os -from fastapi import APIRouter, WebSocket, Depends, Body, status +from fastapi import APIRouter, WebSocket, Depends, Body, Path, status from fastapi.encoders import jsonable_encoder from fastapi.responses import StreamingResponse from uuid import UUID @@ -34,7 +34,7 @@ responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find router = APIRouter(responses=responses) -def dep_node(project_id: UUID, node_id: UUID): +def dep_node(project_id: UUID, node_id: UUID) -> VPCSVM: """ Dependency to retrieve a node. """ @@ -50,7 +50,7 @@ def dep_node(project_id: UUID, node_id: UUID): status_code=status.HTTP_201_CREATED, responses={409: {"model": schemas.ErrorMessage, "description": "Could not create VMware node"}}, ) -async def create_vpcs_node(project_id: UUID, node_data: schemas.VPCSCreate): +async def create_vpcs_node(project_id: UUID, node_data: schemas.VPCSCreate) -> schemas.VPCS: """ Create a new VPCS node. """ @@ -70,7 +70,7 @@ async def create_vpcs_node(project_id: UUID, node_data: schemas.VPCSCreate): @router.get("/{node_id}", response_model=schemas.VPCS) -def get_vpcs_node(node: VPCSVM = Depends(dep_node)): +def get_vpcs_node(node: VPCSVM = Depends(dep_node)) -> schemas.VPCS: """ Return a VPCS node. """ @@ -79,7 +79,7 @@ def get_vpcs_node(node: VPCSVM = Depends(dep_node)): @router.put("/{node_id}", response_model=schemas.VPCS) -def update_vpcs_node(node_data: schemas.VPCSUpdate, node: VPCSVM = Depends(dep_node)): +def update_vpcs_node(node_data: schemas.VPCSUpdate, node: VPCSVM = Depends(dep_node)) -> schemas.VPCS: """ Update a VPCS node. """ @@ -93,7 +93,7 @@ def update_vpcs_node(node_data: schemas.VPCSUpdate, node: VPCSVM = Depends(dep_n @router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT) -async def delete_vpcs_node(node: VPCSVM = Depends(dep_node)): +async def delete_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None: """ Delete a VPCS node. """ @@ -102,7 +102,9 @@ async def delete_vpcs_node(node: VPCSVM = Depends(dep_node)): @router.post("/{node_id}/duplicate", response_model=schemas.VPCS, status_code=status.HTTP_201_CREATED) -async def duplicate_vpcs_node(destination_node_id: UUID = Body(..., embed=True), node: VPCSVM = Depends(dep_node)): +async def duplicate_vpcs_node( + destination_node_id: UUID = Body(..., embed=True), + node: VPCSVM = Depends(dep_node)) -> None: """ Duplicate a VPCS node. """ @@ -112,7 +114,7 @@ async def duplicate_vpcs_node(destination_node_id: UUID = Body(..., embed=True), @router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT) -async def start_vpcs_node(node: VPCSVM = Depends(dep_node)): +async def start_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None: """ Start a VPCS node. """ @@ -121,7 +123,7 @@ async def start_vpcs_node(node: VPCSVM = Depends(dep_node)): @router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT) -async def stop_vpcs_node(node: VPCSVM = Depends(dep_node)): +async def stop_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None: """ Stop a VPCS node. """ @@ -130,7 +132,7 @@ async def stop_vpcs_node(node: VPCSVM = Depends(dep_node)): @router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT) -async def suspend_vpcs_node(node: VPCSVM = Depends(dep_node)): +async def suspend_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None: """ Suspend a VPCS node. Does nothing, suspend is not supported by VPCS. @@ -140,7 +142,7 @@ async def suspend_vpcs_node(node: VPCSVM = Depends(dep_node)): @router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT) -async def reload_vpcs_node(node: VPCSVM = Depends(dep_node)): +async def reload_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None: """ Reload a VPCS node. """ @@ -154,8 +156,12 @@ async def reload_vpcs_node(node: VPCSVM = Depends(dep_node)): response_model=schemas.UDPNIO, ) async def create_vpcs_node_nio( - adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: VPCSVM = Depends(dep_node) -): + *, + adapter_number: int = Path(..., ge=0, le=0), + port_number: int, + nio_data: schemas.UDPNIO, + node: VPCSVM = Depends(dep_node) +) -> schemas.UDPNIO: """ Add a NIO (Network Input/Output) to the node. The adapter number on the VPCS node is always 0. @@ -172,8 +178,12 @@ async def create_vpcs_node_nio( response_model=schemas.UDPNIO, ) async def update_vpcs_node_nio( - adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: VPCSVM = Depends(dep_node) -): + *, + adapter_number: int = Path(..., ge=0, le=0), + port_number: int, + nio_data: schemas.UDPNIO, + node: VPCSVM = Depends(dep_node) +) -> schemas.UDPNIO: """ Update a NIO (Network Input/Output) on the node. The adapter number on the VPCS node is always 0. @@ -187,7 +197,12 @@ async def update_vpcs_node_nio( @router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT) -async def delete_vpcs_node_nio(adapter_number: int, port_number: int, node: VPCSVM = Depends(dep_node)): +async def delete_vpcs_node_nio( + *, + adapter_number: int = Path(..., ge=0, le=0), + port_number: int, + node: VPCSVM = Depends(dep_node) +) -> None: """ Delete a NIO (Network Input/Output) from the node. The adapter number on the VPCS node is always 0. @@ -198,8 +213,12 @@ async def delete_vpcs_node_nio(adapter_number: int, port_number: int, node: VPCS @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start") async def start_vpcs_node_capture( - adapter_number: int, port_number: int, node_capture_data: schemas.NodeCapture, node: VPCSVM = Depends(dep_node) -): + *, + adapter_number: int = Path(..., ge=0, le=0), + port_number: int, + node_capture_data: schemas.NodeCapture, + node: VPCSVM = Depends(dep_node) +) -> dict: """ Start a packet capture on the node. The adapter number on the VPCS node is always 0. @@ -213,7 +232,12 @@ async def start_vpcs_node_capture( @router.post( "/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT ) -async def stop_vpcs_node_capture(adapter_number: int, port_number: int, node: VPCSVM = Depends(dep_node)): +async def stop_vpcs_node_capture( + *, + adapter_number: int = Path(..., ge=0, le=0), + port_number: int, + node: VPCSVM = Depends(dep_node) +) -> None: """ Stop a packet capture on the node. The adapter number on the VPCS node is always 0. @@ -223,13 +247,18 @@ async def stop_vpcs_node_capture(adapter_number: int, port_number: int, node: VP @router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT) -async def reset_console(node: VPCSVM = Depends(dep_node)): +async def reset_console(node: VPCSVM = Depends(dep_node)) -> None: await node.reset_console() @router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream") -async def stream_pcap_file(adapter_number: int, port_number: int, node: VPCSVM = Depends(dep_node)): +async def stream_pcap_file( + *, + adapter_number: int = Path(..., ge=0, le=0), + port_number: int, + node: VPCSVM = Depends(dep_node) +) -> StreamingResponse: """ Stream the pcap capture file. The adapter number on the VPCS node is always 0. @@ -241,7 +270,7 @@ async def stream_pcap_file(adapter_number: int, port_number: int, node: VPCSVM = @router.websocket("/{node_id}/console/ws") -async def console_ws(websocket: WebSocket, node: VPCSVM = Depends(dep_node)): +async def console_ws(websocket: WebSocket, node: VPCSVM = Depends(dep_node)) -> None: """ Console WebSocket. """ diff --git a/gns3server/api/routes/controller/appliances.py b/gns3server/api/routes/controller/appliances.py index 8876c83b..0a2dfc7e 100644 --- a/gns3server/api/routes/controller/appliances.py +++ b/gns3server/api/routes/controller/appliances.py @@ -19,13 +19,13 @@ API routes for appliances. """ from fastapi import APIRouter -from typing import Optional +from typing import Optional, List router = APIRouter() @router.get("") -async def get_appliances(update: Optional[bool] = None, symbol_theme: Optional[str] = "Classic"): +async def get_appliances(update: Optional[bool] = None, symbol_theme: Optional[str] = "Classic") -> List[dict]: """ Return all appliances known by the controller. """ diff --git a/gns3server/api/routes/controller/computes.py b/gns3server/api/routes/controller/computes.py index ff0c13e3..2cc8727c 100644 --- a/gns3server/api/routes/controller/computes.py +++ b/gns3server/api/routes/controller/computes.py @@ -93,7 +93,7 @@ async def update_compute( @router.delete("/{compute_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_compute( compute_id: Union[str, UUID], computes_repo: ComputesRepository = Depends(get_repository(ComputesRepository)) -): +) -> None: """ Delete a compute from the controller. """ @@ -102,7 +102,7 @@ async def delete_compute( @router.get("/{compute_id}/{emulator}/images") -async def get_images(compute_id: Union[str, UUID], emulator: str): +async def get_images(compute_id: Union[str, UUID], emulator: str) -> List[str]: """ Return the list of images available on a compute for a given emulator type. """ @@ -113,7 +113,7 @@ async def get_images(compute_id: Union[str, UUID], emulator: str): @router.get("/{compute_id}/{emulator}/{endpoint_path:path}") -async def forward_get(compute_id: Union[str, UUID], emulator: str, endpoint_path: str): +async def forward_get(compute_id: Union[str, UUID], emulator: str, endpoint_path: str) -> dict: """ Forward a GET request to a compute. Read the full compute API documentation for available routes. @@ -125,7 +125,7 @@ async def forward_get(compute_id: Union[str, UUID], emulator: str, endpoint_path @router.post("/{compute_id}/{emulator}/{endpoint_path:path}") -async def forward_post(compute_id: Union[str, UUID], emulator: str, endpoint_path: str, compute_data: dict): +async def forward_post(compute_id: Union[str, UUID], emulator: str, endpoint_path: str, compute_data: dict) -> dict: """ Forward a POST request to a compute. Read the full compute API documentation for available routes. @@ -136,7 +136,7 @@ async def forward_post(compute_id: Union[str, UUID], emulator: str, endpoint_pat @router.put("/{compute_id}/{emulator}/{endpoint_path:path}") -async def forward_put(compute_id: Union[str, UUID], emulator: str, endpoint_path: str, compute_data: dict): +async def forward_put(compute_id: Union[str, UUID], emulator: str, endpoint_path: str, compute_data: dict) -> dict: """ Forward a PUT request to a compute. Read the full compute API documentation for available routes. @@ -147,7 +147,7 @@ async def forward_put(compute_id: Union[str, UUID], emulator: str, endpoint_path @router.post("/{compute_id}/auto_idlepc") -async def autoidlepc(compute_id: Union[str, UUID], auto_idle_pc: schemas.AutoIdlePC): +async def autoidlepc(compute_id: Union[str, UUID], auto_idle_pc: schemas.AutoIdlePC) -> str: """ Find a suitable Idle-PC value for a given IOS image. This may take a few minutes. """ diff --git a/gns3server/api/routes/controller/controller.py b/gns3server/api/routes/controller/controller.py index 8dfa2998..d26e5137 100644 --- a/gns3server/api/routes/controller/controller.py +++ b/gns3server/api/routes/controller/controller.py @@ -20,6 +20,7 @@ import os from fastapi import APIRouter, status from fastapi.encoders import jsonable_encoder +from typing import List from gns3server.config import Config from gns3server.controller import Controller @@ -40,7 +41,7 @@ router = APIRouter() status_code=status.HTTP_204_NO_CONTENT, responses={403: {"model": schemas.ErrorMessage, "description": "Server shutdown not allowed"}}, ) -async def shutdown(): +async def shutdown() -> None: """ Shutdown the local server """ @@ -71,7 +72,7 @@ async def shutdown(): @router.get("/version", response_model=schemas.Version) -def get_version(): +def get_version() -> dict: """ Return the server version number. """ @@ -86,7 +87,7 @@ def get_version(): response_model_exclude_defaults=True, responses={409: {"model": schemas.ErrorMessage, "description": "Invalid version"}}, ) -def check_version(version: schemas.Version): +def check_version(version: schemas.Version) -> dict: """ Check if version is the same as the server. @@ -102,7 +103,7 @@ def check_version(version: schemas.Version): @router.get("/iou_license", response_model=schemas.IOULicense) -def get_iou_license(): +def get_iou_license() -> schemas.IOULicense: """ Return the IOU license settings """ @@ -111,7 +112,7 @@ def get_iou_license(): @router.put("/iou_license", status_code=status.HTTP_201_CREATED, response_model=schemas.IOULicense) -async def update_iou_license(iou_license: schemas.IOULicense): +async def update_iou_license(iou_license: schemas.IOULicense) -> schemas.IOULicense: """ Update the IOU license settings. """ @@ -124,7 +125,7 @@ async def update_iou_license(iou_license: schemas.IOULicense): @router.get("/statistics") -async def statistics(): +async def statistics() -> List[dict]: """ Return server statistics. """ diff --git a/gns3server/api/routes/controller/drawings.py b/gns3server/api/routes/controller/drawings.py index b3804947..bce40ca7 100644 --- a/gns3server/api/routes/controller/drawings.py +++ b/gns3server/api/routes/controller/drawings.py @@ -32,7 +32,7 @@ router = APIRouter(responses=responses) @router.get("", response_model=List[schemas.Drawing], response_model_exclude_unset=True) -async def get_drawings(project_id: UUID): +async def get_drawings(project_id: UUID) -> List[schemas.Drawing]: """ Return the list of all drawings for a given project. """ @@ -42,7 +42,7 @@ async def get_drawings(project_id: UUID): @router.post("", status_code=status.HTTP_201_CREATED, response_model=schemas.Drawing) -async def create_drawing(project_id: UUID, drawing_data: schemas.Drawing): +async def create_drawing(project_id: UUID, drawing_data: schemas.Drawing) -> schemas.Drawing: """ Create a new drawing. """ @@ -53,7 +53,7 @@ async def create_drawing(project_id: UUID, drawing_data: schemas.Drawing): @router.get("/{drawing_id}", response_model=schemas.Drawing, response_model_exclude_unset=True) -async def get_drawing(project_id: UUID, drawing_id: UUID): +async def get_drawing(project_id: UUID, drawing_id: UUID) -> schemas.Drawing: """ Return a drawing. """ @@ -64,7 +64,7 @@ async def get_drawing(project_id: UUID, drawing_id: UUID): @router.put("/{drawing_id}", response_model=schemas.Drawing, response_model_exclude_unset=True) -async def update_drawing(project_id: UUID, drawing_id: UUID, drawing_data: schemas.Drawing): +async def update_drawing(project_id: UUID, drawing_id: UUID, drawing_data: schemas.Drawing) -> schemas.Drawing: """ Update a drawing. """ @@ -76,7 +76,7 @@ async def update_drawing(project_id: UUID, drawing_id: UUID, drawing_data: schem @router.delete("/{drawing_id}", status_code=status.HTTP_204_NO_CONTENT) -async def delete_drawing(project_id: UUID, drawing_id: UUID): +async def delete_drawing(project_id: UUID, drawing_id: UUID) -> None: """ Delete a drawing. """ diff --git a/gns3server/api/routes/controller/gns3vm.py b/gns3server/api/routes/controller/gns3vm.py index 80329dfe..d019189b 100644 --- a/gns3server/api/routes/controller/gns3vm.py +++ b/gns3server/api/routes/controller/gns3vm.py @@ -21,6 +21,7 @@ API routes for managing the GNS3 VM. from fastapi import APIRouter from fastapi.encoders import jsonable_encoder +from typing import List from gns3server.controller import Controller from gns3server import schemas @@ -29,7 +30,7 @@ router = APIRouter() @router.get("/engines") -async def get_engines(): +async def get_engines() -> List[dict]: """ Return the list of supported engines for the GNS3VM. """ @@ -39,7 +40,7 @@ async def get_engines(): @router.get("/engines/{engine}/vms") -async def get_vms(engine: str): +async def get_vms(engine: str) -> List[dict]: """ Return all the available VMs for a specific virtualization engine. """ @@ -49,7 +50,7 @@ async def get_vms(engine: str): @router.get("", response_model=schemas.GNS3VM) -async def get_gns3vm_settings(): +async def get_gns3vm_settings() -> schemas.GNS3VM: """ Return the GNS3 VM settings. """ @@ -58,7 +59,7 @@ async def get_gns3vm_settings(): @router.put("", response_model=schemas.GNS3VM, response_model_exclude_unset=True) -async def update_gns3vm_settings(gns3vm_data: schemas.GNS3VM): +async def update_gns3vm_settings(gns3vm_data: schemas.GNS3VM) -> schemas.GNS3VM: """ Update the GNS3 VM settings. """ diff --git a/gns3server/api/routes/controller/links.py b/gns3server/api/routes/controller/links.py index 0cab04a6..31ecff6e 100644 --- a/gns3server/api/routes/controller/links.py +++ b/gns3server/api/routes/controller/links.py @@ -42,7 +42,7 @@ responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find router = APIRouter(responses=responses) -async def dep_link(project_id: UUID, link_id: UUID): +async def dep_link(project_id: UUID, link_id: UUID) -> Link: """ Dependency to retrieve a link. """ @@ -53,7 +53,7 @@ async def dep_link(project_id: UUID, link_id: UUID): @router.get("", response_model=List[schemas.Link], response_model_exclude_unset=True) -async def get_links(project_id: UUID): +async def get_links(project_id: UUID) -> List[schemas.Link]: """ Return all links for a given project. """ @@ -71,7 +71,7 @@ async def get_links(project_id: UUID): 409: {"model": schemas.ErrorMessage, "description": "Could not create link"}, }, ) -async def create_link(project_id: UUID, link_data: schemas.LinkCreate): +async def create_link(project_id: UUID, link_data: schemas.LinkCreate) -> schemas.Link: """ Create a new link. """ @@ -98,7 +98,7 @@ async def create_link(project_id: UUID, link_data: schemas.LinkCreate): @router.get("/{link_id}/available_filters") -async def get_filters(link: Link = Depends(dep_link)): +async def get_filters(link: Link = Depends(dep_link)) -> List[dict]: """ Return all filters available for a given link. """ @@ -107,7 +107,7 @@ async def get_filters(link: Link = Depends(dep_link)): @router.get("/{link_id}", response_model=schemas.Link, response_model_exclude_unset=True) -async def get_link(link: Link = Depends(dep_link)): +async def get_link(link: Link = Depends(dep_link)) -> schemas.Link: """ Return a link. """ @@ -116,7 +116,7 @@ async def get_link(link: Link = Depends(dep_link)): @router.put("/{link_id}", response_model=schemas.Link, response_model_exclude_unset=True) -async def update_link(link_data: schemas.LinkUpdate, link: Link = Depends(dep_link)): +async def update_link(link_data: schemas.LinkUpdate, link: Link = Depends(dep_link)) -> schemas.Link: """ Update a link. """ @@ -132,7 +132,7 @@ async def update_link(link_data: schemas.LinkUpdate, link: Link = Depends(dep_li @router.delete("/{link_id}", status_code=status.HTTP_204_NO_CONTENT) -async def delete_link(project_id: UUID, link: Link = Depends(dep_link)): +async def delete_link(project_id: UUID, link: Link = Depends(dep_link)) -> None: """ Delete a link. """ @@ -142,7 +142,7 @@ async def delete_link(project_id: UUID, link: Link = Depends(dep_link)): @router.post("/{link_id}/reset", response_model=schemas.Link) -async def reset_link(link: Link = Depends(dep_link)): +async def reset_link(link: Link = Depends(dep_link)) -> schemas.Link: """ Reset a link. """ @@ -152,7 +152,7 @@ async def reset_link(link: Link = Depends(dep_link)): @router.post("/{link_id}/capture/start", status_code=status.HTTP_201_CREATED, response_model=schemas.Link) -async def start_capture(capture_data: dict, link: Link = Depends(dep_link)): +async def start_capture(capture_data: dict, link: Link = Depends(dep_link)) -> schemas.Link: """ Start packet capture on the link. """ @@ -165,7 +165,7 @@ async def start_capture(capture_data: dict, link: Link = Depends(dep_link)): @router.post("/{link_id}/capture/stop", status_code=status.HTTP_204_NO_CONTENT) -async def stop_capture(link: Link = Depends(dep_link)): +async def stop_capture(link: Link = Depends(dep_link)) -> None: """ Stop packet capture on the link. """ @@ -174,7 +174,7 @@ async def stop_capture(link: Link = Depends(dep_link)): @router.get("/{link_id}/capture/stream") -async def stream_pcap(request: Request, link: Link = Depends(dep_link)): +async def stream_pcap(request: Request, link: Link = Depends(dep_link)) -> StreamingResponse: """ Stream the PCAP capture file from compute. """ diff --git a/gns3server/api/routes/controller/nodes.py b/gns3server/api/routes/controller/nodes.py index 58d0e114..899e9a31 100644 --- a/gns3server/api/routes/controller/nodes.py +++ b/gns3server/api/routes/controller/nodes.py @@ -81,7 +81,7 @@ responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find router = APIRouter(route_class=NodeConcurrency, responses=responses) -async def dep_project(project_id: UUID): +async def dep_project(project_id: UUID) -> Project: """ Dependency to retrieve a project. """ @@ -90,7 +90,7 @@ async def dep_project(project_id: UUID): return project -async def dep_node(node_id: UUID, project: Project = Depends(dep_project)): +async def dep_node(node_id: UUID, project: Project = Depends(dep_project)) -> None: """ Dependency to retrieve a node. """ @@ -108,7 +108,7 @@ async def dep_node(node_id: UUID, project: Project = Depends(dep_project)): 409: {"model": schemas.ErrorMessage, "description": "Could not create node"}, }, ) -async def create_node(node_data: schemas.NodeCreate, project: Project = Depends(dep_project)): +async def create_node(node_data: schemas.NodeCreate, project: Project = Depends(dep_project)) -> schemas.Node: """ Create a new node. """ @@ -121,7 +121,7 @@ async def create_node(node_data: schemas.NodeCreate, project: Project = Depends( @router.get("", response_model=List[schemas.Node], response_model_exclude_unset=True) -async def get_nodes(project: Project = Depends(dep_project)): +async def get_nodes(project: Project = Depends(dep_project)) -> List[schemas.Node]: """ Return all nodes belonging to a given project. """ @@ -130,7 +130,7 @@ async def get_nodes(project: Project = Depends(dep_project)): @router.post("/start", status_code=status.HTTP_204_NO_CONTENT) -async def start_all_nodes(project: Project = Depends(dep_project)): +async def start_all_nodes(project: Project = Depends(dep_project)) -> None: """ Start all nodes belonging to a given project. """ @@ -139,7 +139,7 @@ async def start_all_nodes(project: Project = Depends(dep_project)): @router.post("/stop", status_code=status.HTTP_204_NO_CONTENT) -async def stop_all_nodes(project: Project = Depends(dep_project)): +async def stop_all_nodes(project: Project = Depends(dep_project)) -> None: """ Stop all nodes belonging to a given project. """ @@ -148,7 +148,7 @@ async def stop_all_nodes(project: Project = Depends(dep_project)): @router.post("/suspend", status_code=status.HTTP_204_NO_CONTENT) -async def suspend_all_nodes(project: Project = Depends(dep_project)): +async def suspend_all_nodes(project: Project = Depends(dep_project)) -> None: """ Suspend all nodes belonging to a given project. """ @@ -157,7 +157,7 @@ async def suspend_all_nodes(project: Project = Depends(dep_project)): @router.post("/reload", status_code=status.HTTP_204_NO_CONTENT) -async def reload_all_nodes(project: Project = Depends(dep_project)): +async def reload_all_nodes(project: Project = Depends(dep_project)) -> None: """ Reload all nodes belonging to a given project. """ @@ -167,7 +167,7 @@ async def reload_all_nodes(project: Project = Depends(dep_project)): @router.get("/{node_id}", response_model=schemas.Node) -def get_node(node: Node = Depends(dep_node)): +def get_node(node: Node = Depends(dep_node)) -> schemas.Node: """ Return a node from a given project. """ @@ -176,7 +176,7 @@ def get_node(node: Node = Depends(dep_node)): @router.put("/{node_id}", response_model=schemas.Node, response_model_exclude_unset=True) -async def update_node(node_data: schemas.NodeUpdate, node: Node = Depends(dep_node)): +async def update_node(node_data: schemas.NodeUpdate, node: Node = Depends(dep_node)) -> schemas.Node: """ Update a node. """ @@ -197,7 +197,7 @@ async def update_node(node_data: schemas.NodeUpdate, node: Node = Depends(dep_no status_code=status.HTTP_204_NO_CONTENT, responses={**responses, 409: {"model": schemas.ErrorMessage, "description": "Cannot delete node"}}, ) -async def delete_node(node_id: UUID, project: Project = Depends(dep_project)): +async def delete_node(node_id: UUID, project: Project = Depends(dep_project)) -> None: """ Delete a node from a project. """ @@ -206,7 +206,7 @@ async def delete_node(node_id: UUID, project: Project = Depends(dep_project)): @router.post("/{node_id}/duplicate", response_model=schemas.Node, status_code=status.HTTP_201_CREATED) -async def duplicate_node(duplicate_data: schemas.NodeDuplicate, node: Node = Depends(dep_node)): +async def duplicate_node(duplicate_data: schemas.NodeDuplicate, node: Node = Depends(dep_node)) -> schemas.Node: """ Duplicate a node. """ @@ -216,7 +216,7 @@ async def duplicate_node(duplicate_data: schemas.NodeDuplicate, node: Node = Dep @router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT) -async def start_node(start_data: dict, node: Node = Depends(dep_node)): +async def start_node(start_data: dict, node: Node = Depends(dep_node)) -> None: """ Start a node. """ @@ -225,7 +225,7 @@ async def start_node(start_data: dict, node: Node = Depends(dep_node)): @router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT) -async def stop_node(node: Node = Depends(dep_node)): +async def stop_node(node: Node = Depends(dep_node)) -> None: """ Stop a node. """ @@ -234,7 +234,7 @@ async def stop_node(node: Node = Depends(dep_node)): @router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT) -async def suspend_node(node: Node = Depends(dep_node)): +async def suspend_node(node: Node = Depends(dep_node)) -> None: """ Suspend a node. """ @@ -243,7 +243,7 @@ async def suspend_node(node: Node = Depends(dep_node)): @router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT) -async def reload_node(node: Node = Depends(dep_node)): +async def reload_node(node: Node = Depends(dep_node)) -> None: """ Reload a node. """ @@ -252,7 +252,7 @@ async def reload_node(node: Node = Depends(dep_node)): @router.get("/{node_id}/links", response_model=List[schemas.Link], response_model_exclude_unset=True) -async def get_node_links(node: Node = Depends(dep_node)): +async def get_node_links(node: Node = Depends(dep_node)) -> List[schemas.Link]: """ Return all the links connected to a node. """ @@ -264,7 +264,7 @@ async def get_node_links(node: Node = Depends(dep_node)): @router.get("/{node_id}/dynamips/auto_idlepc") -async def auto_idlepc(node: Node = Depends(dep_node)): +async def auto_idlepc(node: Node = Depends(dep_node)) -> str: """ Compute an Idle-PC value for a Dynamips node """ @@ -273,7 +273,7 @@ async def auto_idlepc(node: Node = Depends(dep_node)): @router.get("/{node_id}/dynamips/idlepc_proposals") -async def idlepc_proposals(node: Node = Depends(dep_node)): +async def idlepc_proposals(node: Node = Depends(dep_node)) -> List[str]: """ Compute a list of potential idle-pc values for a Dynamips node """ @@ -281,8 +281,8 @@ async def idlepc_proposals(node: Node = Depends(dep_node)): return await node.dynamips_idlepc_proposals() -@router.post("/{node_id}/resize_disk", status_code=status.HTTP_201_CREATED) -async def resize_disk(resize_data: dict, node: Node = Depends(dep_node)): +@router.post("/{node_id}/resize_disk", status_code=status.HTTP_204_NO_CONTENT) +async def resize_disk(resize_data: dict, node: Node = Depends(dep_node)) -> None: """ Resize a disk image. """ @@ -290,7 +290,7 @@ async def resize_disk(resize_data: dict, node: Node = Depends(dep_node)): @router.get("/{node_id}/files/{file_path:path}") -async def get_file(file_path: str, node: Node = Depends(dep_node)): +async def get_file(file_path: str, node: Node = Depends(dep_node)) -> Response: """ Return a file in the node directory """ @@ -309,7 +309,7 @@ async def get_file(file_path: str, node: Node = Depends(dep_node)): @router.post("/{node_id}/files/{file_path:path}", status_code=status.HTTP_201_CREATED) -async def post_file(file_path: str, request: Request, node: Node = Depends(dep_node)): +async def post_file(file_path: str, request: Request, node: Node = Depends(dep_node)) -> dict: """ Write a file in the node directory. """ @@ -329,7 +329,7 @@ async def post_file(file_path: str, request: Request, node: Node = Depends(dep_n @router.websocket("/{node_id}/console/ws") -async def ws_console(websocket: WebSocket, node: Node = Depends(dep_node)): +async def ws_console(websocket: WebSocket, node: Node = Depends(dep_node)) -> None: """ WebSocket console. """ @@ -377,7 +377,7 @@ async def ws_console(websocket: WebSocket, node: Node = Depends(dep_node)): @router.post("/console/reset", status_code=status.HTTP_204_NO_CONTENT) -async def reset_console_all_nodes(project: Project = Depends(dep_project)): +async def reset_console_all_nodes(project: Project = Depends(dep_project)) -> None: """ Reset console for all nodes belonging to the project. """ @@ -386,6 +386,6 @@ async def reset_console_all_nodes(project: Project = Depends(dep_project)): @router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT) -async def console_reset(node: Node = Depends(dep_node)): +async def console_reset(node: Node = Depends(dep_node)) -> None: await node.post("/console/reset") # , request.json) diff --git a/gns3server/api/routes/controller/notifications.py b/gns3server/api/routes/controller/notifications.py index bb64fbb9..3634a7d4 100644 --- a/gns3server/api/routes/controller/notifications.py +++ b/gns3server/api/routes/controller/notifications.py @@ -32,7 +32,7 @@ router = APIRouter() @router.get("") -async def http_notification(): +async def http_notification() -> StreamingResponse: """ Receive controller notifications about the controller from HTTP stream. """ @@ -47,7 +47,7 @@ async def http_notification(): @router.websocket("/ws") -async def notification_ws(websocket: WebSocket): +async def notification_ws(websocket: WebSocket) -> None: """ Receive project notifications about the controller from WebSocket. """ diff --git a/gns3server/api/routes/controller/projects.py b/gns3server/api/routes/controller/projects.py index 7b5775a2..c8936f5f 100644 --- a/gns3server/api/routes/controller/projects.py +++ b/gns3server/api/routes/controller/projects.py @@ -51,7 +51,7 @@ responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find router = APIRouter(responses=responses) -def dep_project(project_id: UUID): +def dep_project(project_id: UUID) -> Project: """ Dependency to retrieve a project. """ @@ -64,7 +64,7 @@ CHUNK_SIZE = 1024 * 8 # 8KB @router.get("", response_model=List[schemas.Project], response_model_exclude_unset=True) -def get_projects(): +def get_projects() -> List[schemas.Project]: """ Return all projects. """ @@ -80,7 +80,7 @@ def get_projects(): response_model_exclude_unset=True, responses={409: {"model": schemas.ErrorMessage, "description": "Could not create project"}}, ) -async def create_project(project_data: schemas.ProjectCreate): +async def create_project(project_data: schemas.ProjectCreate) -> schemas.Project: """ Create a new project. """ @@ -91,7 +91,7 @@ async def create_project(project_data: schemas.ProjectCreate): @router.get("/{project_id}", response_model=schemas.Project) -def get_project(project: Project = Depends(dep_project)): +def get_project(project: Project = Depends(dep_project)) -> schemas.Project: """ Return a project. """ @@ -100,7 +100,10 @@ def get_project(project: Project = Depends(dep_project)): @router.put("/{project_id}", response_model=schemas.Project, response_model_exclude_unset=True) -async def update_project(project_data: schemas.ProjectUpdate, project: Project = Depends(dep_project)): +async def update_project( + project_data: schemas.ProjectUpdate, + project: Project = Depends(dep_project) +) -> schemas.Project: """ Update a project. """ @@ -110,7 +113,7 @@ async def update_project(project_data: schemas.ProjectUpdate, project: Project = @router.delete("/{project_id}", status_code=status.HTTP_204_NO_CONTENT) -async def delete_project(project: Project = Depends(dep_project)): +async def delete_project(project: Project = Depends(dep_project)) -> None: """ Delete a project. """ @@ -121,7 +124,7 @@ async def delete_project(project: Project = Depends(dep_project)): @router.get("/{project_id}/stats") -def get_project_stats(project: Project = Depends(dep_project)): +def get_project_stats(project: Project = Depends(dep_project)) -> dict: """ Return a project statistics. """ @@ -134,7 +137,7 @@ def get_project_stats(project: Project = Depends(dep_project)): status_code=status.HTTP_204_NO_CONTENT, responses={**responses, 409: {"model": schemas.ErrorMessage, "description": "Could not close project"}}, ) -async def close_project(project: Project = Depends(dep_project)): +async def close_project(project: Project = Depends(dep_project)) -> None: """ Close a project. """ @@ -148,7 +151,7 @@ async def close_project(project: Project = Depends(dep_project)): response_model=schemas.Project, responses={**responses, 409: {"model": schemas.ErrorMessage, "description": "Could not open project"}}, ) -async def open_project(project: Project = Depends(dep_project)): +async def open_project(project: Project = Depends(dep_project)) -> schemas.Project: """ Open a project. """ @@ -163,7 +166,7 @@ async def open_project(project: Project = Depends(dep_project)): response_model=schemas.Project, responses={**responses, 409: {"model": schemas.ErrorMessage, "description": "Could not load project"}}, ) -async def load_project(path: str = Body(..., embed=True)): +async def load_project(path: str = Body(..., embed=True)) -> schemas.Project: """ Load a project (local server only). """ @@ -180,7 +183,7 @@ async def load_project(path: str = Body(..., embed=True)): @router.get("/{project_id}/notifications") -async def notification(project_id: UUID): +async def notification(project_id: UUID) -> StreamingResponse: """ Receive project notifications about the controller from HTTP stream. """ @@ -211,7 +214,7 @@ async def notification(project_id: UUID): @router.websocket("/{project_id}/notifications/ws") -async def notification_ws(project_id: UUID, websocket: WebSocket): +async def notification_ws(project_id: UUID, websocket: WebSocket) -> None: """ Receive project notifications about the controller from WebSocket. """ @@ -248,7 +251,7 @@ async def export_project( include_images: bool = False, reset_mac_addresses: bool = False, compression: str = "zip", -): +) -> StreamingResponse: """ Export a project as a portable archive. """ @@ -294,7 +297,12 @@ async def export_project( @router.post("/{project_id}/import", status_code=status.HTTP_201_CREATED, response_model=schemas.Project) -async def import_project(project_id: UUID, request: Request, path: Optional[Path] = None, name: Optional[str] = None): +async def import_project( + project_id: UUID, + request: Request, + path: Optional[Path] = None, + name: Optional[str] = None +) -> schemas.Project: """ Import a project from a portable archive. """ @@ -332,7 +340,10 @@ async def import_project(project_id: UUID, request: Request, path: Optional[Path response_model=schemas.Project, responses={**responses, 409: {"model": schemas.ErrorMessage, "description": "Could not duplicate project"}}, ) -async def duplicate_project(project_data: schemas.ProjectDuplicate, project: Project = Depends(dep_project)): +async def duplicate_project( + project_data: schemas.ProjectDuplicate, + project: Project = Depends(dep_project) +) -> schemas.Project: """ Duplicate a project. """ @@ -352,7 +363,7 @@ async def duplicate_project(project_data: schemas.ProjectDuplicate, project: Pro @router.get("/{project_id}/files/{file_path:path}") -async def get_file(file_path: str, project: Project = Depends(dep_project)): +async def get_file(file_path: str, project: Project = Depends(dep_project)) -> FileResponse: """ Return a file from a project. """ @@ -371,7 +382,7 @@ async def get_file(file_path: str, project: Project = Depends(dep_project)): @router.post("/{project_id}/files/{file_path:path}", status_code=status.HTTP_204_NO_CONTENT) -async def write_file(file_path: str, request: Request, project: Project = Depends(dep_project)): +async def write_file(file_path: str, request: Request, project: Project = Depends(dep_project)) -> None: """ Write a file from a project. """ diff --git a/gns3server/api/routes/controller/snapshots.py b/gns3server/api/routes/controller/snapshots.py index 5cf2f067..d410e88a 100644 --- a/gns3server/api/routes/controller/snapshots.py +++ b/gns3server/api/routes/controller/snapshots.py @@ -36,7 +36,7 @@ responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find router = APIRouter(responses=responses) -def dep_project(project_id: UUID): +def dep_project(project_id: UUID) -> Project: """ Dependency to retrieve a project. """ @@ -46,7 +46,10 @@ def dep_project(project_id: UUID): @router.post("", status_code=status.HTTP_201_CREATED, response_model=schemas.Snapshot) -async def create_snapshot(snapshot_data: schemas.SnapshotCreate, project: Project = Depends(dep_project)): +async def create_snapshot( + snapshot_data: schemas.SnapshotCreate, + project: Project = Depends(dep_project) +) -> schemas.Snapshot: """ Create a new snapshot of a project. """ @@ -56,7 +59,7 @@ async def create_snapshot(snapshot_data: schemas.SnapshotCreate, project: Projec @router.get("", response_model=List[schemas.Snapshot], response_model_exclude_unset=True) -def get_snapshots(project: Project = Depends(dep_project)): +def get_snapshots(project: Project = Depends(dep_project)) -> List[schemas.Snapshot]: """ Return all snapshots belonging to a given project. """ @@ -66,7 +69,7 @@ def get_snapshots(project: Project = Depends(dep_project)): @router.delete("/{snapshot_id}", status_code=status.HTTP_204_NO_CONTENT) -async def delete_snapshot(snapshot_id: UUID, project: Project = Depends(dep_project)): +async def delete_snapshot(snapshot_id: UUID, project: Project = Depends(dep_project)) -> None: """ Delete a snapshot. """ @@ -75,7 +78,7 @@ async def delete_snapshot(snapshot_id: UUID, project: Project = Depends(dep_proj @router.post("/{snapshot_id}/restore", status_code=status.HTTP_201_CREATED, response_model=schemas.Project) -async def restore_snapshot(snapshot_id: UUID, project: Project = Depends(dep_project)): +async def restore_snapshot(snapshot_id: UUID, project: Project = Depends(dep_project)) -> schemas.Project: """ Restore a snapshot. """ diff --git a/gns3server/api/routes/controller/symbols.py b/gns3server/api/routes/controller/symbols.py index c4c1b392..127f2c14 100644 --- a/gns3server/api/routes/controller/symbols.py +++ b/gns3server/api/routes/controller/symbols.py @@ -23,6 +23,7 @@ import os from fastapi import APIRouter, Request, status from fastapi.responses import FileResponse +from typing import List from gns3server.controller import Controller from gns3server import schemas @@ -37,7 +38,7 @@ router = APIRouter() @router.get("") -def get_symbols(): +def get_symbols() -> List[str]: controller = Controller.instance() return controller.symbols.list() @@ -46,7 +47,7 @@ def get_symbols(): @router.get( "/{symbol_id:path}/raw", responses={404: {"model": schemas.ErrorMessage, "description": "Could not find symbol"}} ) -async def get_symbol(symbol_id: str): +async def get_symbol(symbol_id: str) -> FileResponse: """ Download a symbol file. """ @@ -63,7 +64,7 @@ async def get_symbol(symbol_id: str): "/{symbol_id:path}/dimensions", responses={404: {"model": schemas.ErrorMessage, "description": "Could not find symbol"}}, ) -async def get_symbol_dimensions(symbol_id: str): +async def get_symbol_dimensions(symbol_id: str) -> dict: """ Get a symbol dimensions. """ @@ -78,7 +79,7 @@ async def get_symbol_dimensions(symbol_id: str): @router.post("/{symbol_id:path}/raw", status_code=status.HTTP_204_NO_CONTENT) -async def upload_symbol(symbol_id: str, request: Request): +async def upload_symbol(symbol_id: str, request: Request) -> None: """ Upload a symbol file. """ @@ -97,7 +98,7 @@ async def upload_symbol(symbol_id: str, request: Request): @router.get("/default_symbols") -def get_default_symbols(): +def get_default_symbols() -> dict: """ Return all default symbols. """ diff --git a/gns3server/api/routes/controller/templates.py b/gns3server/api/routes/controller/templates.py index 0b1f50b2..bb2c5ae9 100644 --- a/gns3server/api/routes/controller/templates.py +++ b/gns3server/api/routes/controller/templates.py @@ -44,7 +44,7 @@ router = APIRouter(responses=responses) async def create_template( template_create: schemas.TemplateCreate, templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository)), -) -> dict: +) -> schemas.Template: """ Create a new template. """ @@ -58,7 +58,7 @@ async def get_template( request: Request, response: Response, templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository)), -) -> dict: +) -> schemas.Template: """ Return a template. """ @@ -79,7 +79,7 @@ async def update_template( template_id: UUID, template_update: schemas.TemplateUpdate, templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository)), -) -> dict: +) -> schemas.Template: """ Update a template. """ @@ -104,7 +104,7 @@ async def delete_template( @router.get("/templates", response_model=List[schemas.Template], response_model_exclude_unset=True) async def get_templates( templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository)), -) -> List[dict]: +) -> List[schemas.Template]: """ Return all templates. """ @@ -115,7 +115,7 @@ async def get_templates( @router.post("/templates/{template_id}/duplicate", response_model=schemas.Template, status_code=status.HTTP_201_CREATED) async def duplicate_template( template_id: UUID, templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository)) -) -> dict: +) -> schemas.Template: """ Duplicate a template. """ diff --git a/gns3server/compute/base_manager.py b/gns3server/compute/base_manager.py index b578706c..57c7e252 100644 --- a/gns3server/compute/base_manager.py +++ b/gns3server/compute/base_manager.py @@ -20,25 +20,25 @@ import struct import stat import asyncio import aiofiles - import socket import shutil import re - import logging -from gns3server.utils.asyncio import cancellable_wait_run_in_executor -from gns3server.compute.compute_error import ComputeError, ComputeForbiddenError, ComputeNotFoundError - log = logging.getLogger(__name__) -from uuid import UUID, uuid4 +from gns3server.utils.asyncio import cancellable_wait_run_in_executor +from gns3server.compute.compute_error import ComputeError, ComputeForbiddenError, ComputeNotFoundError from gns3server.utils.interfaces import is_interface_up + +from uuid import UUID, uuid4 +from typing import Type from ..config import Config from ..utils.asyncio import wait_run_in_executor from ..utils import force_unix_path from .project_manager import ProjectManager from .port_manager import PortManager +from .base_node import BaseNode from .nios.nio_udp import NIOUDP from .nios.nio_tap import NIOTAP @@ -150,7 +150,7 @@ class BaseManager: BaseManager._instance = None log.debug(f"Module {self.module_name} unloaded") - def get_node(self, node_id, project_id=None): + def get_node(self, node_id, project_id=None) -> Type[BaseNode]: """ Returns a Node instance. diff --git a/gns3server/db/repositories/users.py b/gns3server/db/repositories/users.py index 2a5ce4aa..4b531588 100644 --- a/gns3server/db/repositories/users.py +++ b/gns3server/db/repositories/users.py @@ -59,7 +59,7 @@ class UsersRepository(BaseRepository): async def create_user(self, user: schemas.UserCreate) -> models.User: - hashed_password = self._auth_service.hash_password(user.password) + hashed_password = self._auth_service.hash_password(user.password.get_secret_value()) db_user = models.User( username=user.username, email=user.email, full_name=user.full_name, hashed_password=hashed_password ) @@ -73,7 +73,7 @@ class UsersRepository(BaseRepository): update_values = user_update.dict(exclude_unset=True) password = update_values.pop("password", None) if password: - update_values["hashed_password"] = self._auth_service.hash_password(password=password) + update_values["hashed_password"] = self._auth_service.hash_password(password=password.get_secret_value()) query = update(models.User).where(models.User.user_id == user_id).values(update_values) diff --git a/gns3server/schemas/controller/computes.py b/gns3server/schemas/controller/computes.py index 46972810..f84bb527 100644 --- a/gns3server/schemas/controller/computes.py +++ b/gns3server/schemas/controller/computes.py @@ -54,7 +54,13 @@ class ComputeCreate(ComputeBase): class Config: schema_extra = { - "example": {"name": "My compute", "host": "127.0.0.1", "port": 3080, "user": "user", "password": "password"} + "example": { + "name": "My compute", + "host": "127.0.0.1", + "port": 3080, + "user": "user", + "password": "password" + } } @validator("name", always=True) diff --git a/gns3server/schemas/controller/links.py b/gns3server/schemas/controller/links.py index fbbf50a1..6a375cb9 100644 --- a/gns3server/schemas/controller/links.py +++ b/gns3server/schemas/controller/links.py @@ -30,7 +30,7 @@ class LinkNode(BaseModel): node_id: UUID adapter_number: int port_number: int - label: Optional[Label] + label: Optional[Label] = None class LinkType(str, Enum): diff --git a/gns3server/schemas/controller/users.py b/gns3server/schemas/controller/users.py index 445e1d7b..1073311c 100644 --- a/gns3server/schemas/controller/users.py +++ b/gns3server/schemas/controller/users.py @@ -15,7 +15,7 @@ # along with this program. If not, see . from typing import Optional -from pydantic import EmailStr, BaseModel, Field +from pydantic import EmailStr, BaseModel, Field, SecretStr from uuid import UUID from .base import DateTimeModelMixin @@ -37,7 +37,7 @@ class UserCreate(UserBase): """ username: str = Field(..., min_length=3, regex="[a-zA-Z0-9_-]+$") - password: str = Field(..., min_length=7, max_length=100) + password: SecretStr = Field(..., min_length=7, max_length=100) class UserUpdate(UserBase): @@ -45,7 +45,7 @@ class UserUpdate(UserBase): Properties to update an user. """ - password: Optional[str] = Field(None, min_length=7, max_length=100) + password: Optional[SecretStr] = Field(None, min_length=7, max_length=100) class User(DateTimeModelMixin, UserBase): From c03226e36884406f8d73810684dd22294b145ba0 Mon Sep 17 00:00:00 2001 From: grossmj Date: Sun, 18 Apr 2021 17:39:47 +0930 Subject: [PATCH 07/10] Add default super admin account in controller db. --- gns3server/api/routes/controller/users.py | 6 ++--- gns3server/db/models/users.py | 23 ++++++++++++++++-- gns3server/schemas/controller/users.py | 2 +- tests/api/routes/controller/test_users.py | 29 ++++++++++++++++++++++- tests/conftest.py | 18 ++++++++++++-- 5 files changed, 69 insertions(+), 9 deletions(-) diff --git a/gns3server/api/routes/controller/users.py b/gns3server/api/routes/controller/users.py index 89d14275..2092b251 100644 --- a/gns3server/api/routes/controller/users.py +++ b/gns3server/api/routes/controller/users.py @@ -28,7 +28,7 @@ from gns3server import schemas from gns3server.controller.controller_error import ( ControllerBadRequestError, ControllerNotFoundError, - ControllerUnauthorizedError, + ControllerForbiddenError, ) from gns3server.db.repositories.users import UsersRepository @@ -110,8 +110,8 @@ async def delete_user( Delete an user. """ - if current_user.is_superuser: - raise ControllerUnauthorizedError("The super user cannot be deleted") + if current_user.is_superadmin: + raise ControllerForbiddenError("The super user cannot be deleted") success = await users_repo.delete_user(user_id) if not success: diff --git a/gns3server/db/models/users.py b/gns3server/db/models/users.py index 2dca0894..99108975 100644 --- a/gns3server/db/models/users.py +++ b/gns3server/db/models/users.py @@ -15,9 +15,14 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from sqlalchemy import Boolean, Column, String +from sqlalchemy import Boolean, Column, String, event from .base import BaseTable, generate_uuid, GUID +from gns3server.services import auth_service + +import logging + +log = logging.getLogger(__name__) class User(BaseTable): @@ -30,4 +35,18 @@ class User(BaseTable): full_name = Column(String) hashed_password = Column(String) is_active = Column(Boolean, default=True) - is_superuser = Column(Boolean, default=False) + is_superadmin = Column(Boolean, default=False) + +@event.listens_for(User.__table__, 'after_create') +def create_default_super_admin(target, connection, **kw): + + hashed_password = auth_service.hash_password("admin") + stmt = target.insert().values( + username="admin", + full_name="Super Administrator", + hashed_password=hashed_password, + is_superadmin=True + ) + connection.execute(stmt) + connection.commit() + log.info("Default super admin account added") diff --git a/gns3server/schemas/controller/users.py b/gns3server/schemas/controller/users.py index 1073311c..11aa100e 100644 --- a/gns3server/schemas/controller/users.py +++ b/gns3server/schemas/controller/users.py @@ -52,7 +52,7 @@ class User(DateTimeModelMixin, UserBase): user_id: UUID is_active: bool = True - is_superuser: bool = False + is_superadmin: bool = False class Config: orm_mode = True diff --git a/tests/api/routes/controller/test_users.py b/tests/api/routes/controller/test_users.py index f8ad92f0..6fbfc7cf 100644 --- a/tests/api/routes/controller/test_users.py +++ b/tests/api/routes/controller/test_users.py @@ -118,7 +118,7 @@ class TestUserRoutes: response = await client.get(app.url_path_for("get_users")) assert response.status_code == status.HTTP_200_OK - assert len(response.json()) == 3 # user1, user2 and user3 should exist + assert len(response.json()) == 4 # admin, user1, user2 and user3 should exist class TestAuthTokens: @@ -265,3 +265,30 @@ class TestUserMe: res = await client.get(app.url_path_for("get_current_active_user")) assert res.status_code == status.HTTP_401_UNAUTHORIZED + + +class TestSuperAdmin: + + async def test_super_admin_exists( + self, + app: FastAPI, + client: AsyncClient, + db_session: AsyncSession + ) -> None: + + user_repo = UsersRepository(db_session) + admin_in_db = await user_repo.get_user_by_username("admin") + assert admin_in_db is not None + assert auth_service.verify_password("admin", admin_in_db.hashed_password) + + async def test_cannot_delete_super_admin( + self, + app: FastAPI, + admin_client: AsyncClient, + db_session: AsyncSession + ) -> None: + + user_repo = UsersRepository(db_session) + admin_in_db = await user_repo.get_user_by_username("admin") + res = await admin_client.delete(app.url_path_for("delete_user", user_id=admin_in_db.user_id)) + assert res.status_code == status.HTTP_403_FORBIDDEN diff --git a/tests/conftest.py b/tests/conftest.py index f54c1422..b75cd8b2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -79,8 +79,12 @@ async def db_session(db_engine): # preferred and faster way would be to rollback the session/transaction # but it doesn't work for some reason async with db_engine.begin() as conn: - await conn.run_sync(Base.metadata.drop_all) - await conn.run_sync(Base.metadata.create_all) + # Speed up tests by avoiding to hash the 'admin' password everytime the default super admin is added + # to the database using the "after_create" sqlalchemy event + hashed_password = "$2b$12$jPsNU9IS7.EWEqXahtDfo.26w6VLOLCuFEHKNvDpOjxs5e0WpqJfa" + with patch("gns3server.services.authentication.AuthService.hash_password", return_value=hashed_password): + await conn.run_sync(Base.metadata.drop_all) + await conn.run_sync(Base.metadata.create_all) session = AsyncSession(db_engine) try: @@ -152,6 +156,16 @@ def authorized_client(client: AsyncClient, test_user: User) -> AsyncClient: } return client +@pytest.fixture +async def admin_client(client: AsyncClient) -> AsyncClient: + + # user "admin" is automatically created when the users table is created + access_token = auth_service.create_access_token("admin") + client.headers = { + **client.headers, + "Authorization": f"Bearer {access_token}", + } + return client @pytest.fixture def controller_config_path(tmpdir): From e28452f09acd3fb1ae74f218b64441f86ec7e3e4 Mon Sep 17 00:00:00 2001 From: grossmj Date: Mon, 19 Apr 2021 09:40:04 +0930 Subject: [PATCH 08/10] Secure users API and handle manual password recovery. --- .../controller/dependencies/authentication.py | 4 ++ gns3server/api/routes/controller/users.py | 24 ++++++---- gns3server/db/models/users.py | 3 +- gns3server/db/repositories/users.py | 11 +++++ gns3server/schemas/controller/users.py | 4 +- tests/api/routes/controller/test_users.py | 45 ++++++++++++++----- tests/conftest.py | 40 ++++++++++------- 7 files changed, 92 insertions(+), 39 deletions(-) diff --git a/gns3server/api/routes/controller/dependencies/authentication.py b/gns3server/api/routes/controller/dependencies/authentication.py index c66b4b62..819daec9 100644 --- a/gns3server/api/routes/controller/dependencies/authentication.py +++ b/gns3server/api/routes/controller/dependencies/authentication.py @@ -44,6 +44,10 @@ async def get_user_from_token( async def get_current_active_user(current_user: schemas.User = Depends(get_user_from_token)) -> schemas.User: + # Super admin is always authorized + if current_user.is_superadmin: + return current_user + if not current_user.is_active: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, diff --git a/gns3server/api/routes/controller/users.py b/gns3server/api/routes/controller/users.py index 2092b251..9649e911 100644 --- a/gns3server/api/routes/controller/users.py +++ b/gns3server/api/routes/controller/users.py @@ -45,7 +45,10 @@ router = APIRouter() @router.get("", response_model=List[schemas.User]) -async def get_users(users_repo: UsersRepository = Depends(get_repository(UsersRepository))) -> List[schemas.User]: +async def get_users( + users_repo: UsersRepository = Depends(get_repository(UsersRepository)), + current_user: schemas.User = Depends(get_current_active_user) +) -> List[schemas.User]: """ Get all users. """ @@ -55,7 +58,9 @@ async def get_users(users_repo: UsersRepository = Depends(get_repository(UsersRe @router.post("", response_model=schemas.User, status_code=status.HTTP_201_CREATED) async def create_user( - user_create: schemas.UserCreate, users_repo: UsersRepository = Depends(get_repository(UsersRepository)) + user_create: schemas.UserCreate, + users_repo: UsersRepository = Depends(get_repository(UsersRepository)), + current_user: schemas.User = Depends(get_current_active_user) ) -> schemas.User: """ Create a new user. @@ -70,9 +75,11 @@ async def create_user( return await users_repo.create_user(user_create) -@router.get("/{user_id}", response_model=schemas.User) +@router.get("/{user_id}",response_model=schemas.User) async def get_user( - user_id: UUID, users_repo: UsersRepository = Depends(get_repository(UsersRepository)) + user_id: UUID, + users_repo: UsersRepository = Depends(get_repository(UsersRepository)), + current_user: schemas.User = Depends(get_current_active_user) ) -> schemas.User: """ Get an user. @@ -86,9 +93,10 @@ async def get_user( @router.put("/{user_id}", response_model=schemas.User) async def update_user( - user_id: UUID, - user_update: schemas.UserUpdate, - users_repo: UsersRepository = Depends(get_repository(UsersRepository)), + user_id: UUID, + user_update: schemas.UserUpdate, + users_repo: UsersRepository = Depends(get_repository(UsersRepository)), + current_user: schemas.User = Depends(get_current_active_user) ) -> schemas.User: """ Update an user. @@ -111,7 +119,7 @@ async def delete_user( """ if current_user.is_superadmin: - raise ControllerForbiddenError("The super user cannot be deleted") + raise ControllerForbiddenError("The super admin cannot be deleted") success = await users_repo.delete_user(user_id) if not success: diff --git a/gns3server/db/models/users.py b/gns3server/db/models/users.py index 99108975..6f9400ba 100644 --- a/gns3server/db/models/users.py +++ b/gns3server/db/models/users.py @@ -37,6 +37,7 @@ class User(BaseTable): is_active = Column(Boolean, default=True) is_superadmin = Column(Boolean, default=False) + @event.listens_for(User.__table__, 'after_create') def create_default_super_admin(target, connection, **kw): @@ -49,4 +50,4 @@ def create_default_super_admin(target, connection, **kw): ) connection.execute(stmt) connection.commit() - log.info("Default super admin account added") + log.info("The default super admin account has been created in the database") diff --git a/gns3server/db/repositories/users.py b/gns3server/db/repositories/users.py index 4b531588..96576cfc 100644 --- a/gns3server/db/repositories/users.py +++ b/gns3server/db/repositories/users.py @@ -26,6 +26,10 @@ import gns3server.db.models as models from gns3server import schemas from gns3server.services import auth_service +import logging + +log = logging.getLogger(__name__) + class UsersRepository(BaseRepository): def __init__(self, db_session: AsyncSession) -> None: @@ -93,6 +97,13 @@ class UsersRepository(BaseRepository): user = await self.get_user_by_username(username) if not user: return None + # Allow user to be authenticated if hashed password in the db is null + # this is useful for manual password recovery like: + # sqlite3 gns3_controller.db "UPDATE users SET hashed_password = null WHERE username = 'admin';" + if user.hashed_password is None: + log.warning(f"User '{username}' has been authenticated without a password " + f"configured. Please set a new password.") + return user if not self._auth_service.verify_password(password, user.hashed_password): return None return user diff --git a/gns3server/schemas/controller/users.py b/gns3server/schemas/controller/users.py index 11aa100e..62421179 100644 --- a/gns3server/schemas/controller/users.py +++ b/gns3server/schemas/controller/users.py @@ -37,7 +37,7 @@ class UserCreate(UserBase): """ username: str = Field(..., min_length=3, regex="[a-zA-Z0-9_-]+$") - password: SecretStr = Field(..., min_length=7, max_length=100) + password: SecretStr = Field(..., min_length=6, max_length=100) class UserUpdate(UserBase): @@ -45,7 +45,7 @@ class UserUpdate(UserBase): Properties to update an user. """ - password: Optional[SecretStr] = Field(None, min_length=7, max_length=100) + password: Optional[SecretStr] = Field(None, min_length=6, max_length=100) class User(DateTimeModelMixin, UserBase): diff --git a/tests/api/routes/controller/test_users.py b/tests/api/routes/controller/test_users.py index 6fbfc7cf..f02d77e5 100644 --- a/tests/api/routes/controller/test_users.py +++ b/tests/api/routes/controller/test_users.py @@ -19,15 +19,16 @@ import pytest from typing import Optional from fastapi import FastAPI, HTTPException, status +from sqlalchemy import update from httpx import AsyncClient from jose import jwt from sqlalchemy.ext.asyncio import AsyncSession from gns3server.db.repositories.users import UsersRepository from gns3server.services import auth_service -from gns3server.services.authentication import DEFAULT_JWT_SECRET_KEY from gns3server.config import Config from gns3server.schemas.controller.users import User +import gns3server.db.models as models pytestmark = pytest.mark.asyncio @@ -38,7 +39,7 @@ class TestUserRoutes: new_user = {"username": "user1", "email": "user1@email.com", "password": "test_password"} response = await client.post(app.url_path_for("create_user"), json=new_user) - assert response.status_code != status.HTTP_404_NOT_FOUND + assert response.status_code == status.HTTP_201_CREATED async def test_users_can_register_successfully( self, @@ -188,18 +189,18 @@ class TestUserLogin: async def test_user_can_login_successfully_and_receives_valid_token( self, app: FastAPI, - client: AsyncClient, + unauthorized_client: AsyncClient, test_user: User, config: Config ) -> None: jwt_secret = config.settings.Controller.jwt_secret_key - client.headers["content-type"] = "application/x-www-form-urlencoded" + unauthorized_client.headers["content-type"] = "application/x-www-form-urlencoded" login_data = { "username": test_user.username, "password": "user1_password", } - res = await client.post(app.url_path_for("login"), data=login_data) + res = await unauthorized_client.post(app.url_path_for("login"), data=login_data) assert res.status_code == status.HTTP_200_OK # check that token exists in response and has user encoded within it @@ -224,19 +225,19 @@ class TestUserLogin: async def test_user_with_wrong_creds_doesnt_receive_token( self, app: FastAPI, - client: AsyncClient, + unauthorized_client: AsyncClient, test_user: User, username: str, password: str, status_code: int, ) -> None: - client.headers["content-type"] = "application/x-www-form-urlencoded" + unauthorized_client.headers["content-type"] = "application/x-www-form-urlencoded" login_data = { "username": username, "password": password, } - res = await client.post(app.url_path_for("login"), data=login_data) + res = await unauthorized_client.post(app.url_path_for("login"), data=login_data) assert res.status_code == status_code assert "access_token" not in res.json() @@ -259,11 +260,11 @@ class TestUserMe: async def test_user_cannot_access_own_data_if_not_authenticated( self, app: FastAPI, - client: AsyncClient, + unauthorized_client: AsyncClient, test_user: User, ) -> None: - res = await client.get(app.url_path_for("get_current_active_user")) + res = await unauthorized_client.get(app.url_path_for("get_current_active_user")) assert res.status_code == status.HTTP_401_UNAUTHORIZED @@ -284,11 +285,31 @@ class TestSuperAdmin: async def test_cannot_delete_super_admin( self, app: FastAPI, - admin_client: AsyncClient, + client: AsyncClient, db_session: AsyncSession ) -> None: user_repo = UsersRepository(db_session) admin_in_db = await user_repo.get_user_by_username("admin") - res = await admin_client.delete(app.url_path_for("delete_user", user_id=admin_in_db.user_id)) + res = await client.delete(app.url_path_for("delete_user", user_id=admin_in_db.user_id)) assert res.status_code == status.HTTP_403_FORBIDDEN + + async def test_admin_can_login_after_password_recovery( + self, + app: FastAPI, + unauthorized_client: AsyncClient, + db_session: AsyncSession + ) -> None: + + # set the admin password to null in the database + query = update(models.User).where(models.User.username == "admin").values(hashed_password=None) + await db_session.execute(query) + await db_session.commit() + + unauthorized_client.headers["content-type"] = "application/x-www-form-urlencoded" + login_data = { + "username": "admin", + "password": "whatever", + } + res = await unauthorized_client.post(app.url_path_for("login"), data=login_data) + assert res.status_code == status.HTTP_200_OK diff --git a/tests/conftest.py b/tests/conftest.py index b75cd8b2..9645c85f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -94,7 +94,7 @@ async def db_session(db_engine): @pytest.fixture -async def client(app: FastAPI, db_session: AsyncSession) -> AsyncClient: +async def base_client(app: FastAPI, db_session: AsyncSession) -> AsyncClient: async def _get_test_db(): try: @@ -108,8 +108,8 @@ async def client(app: FastAPI, db_session: AsyncSession) -> AsyncClient: app=app, base_url="http://test-api", headers={"Content-Type": "application/json"} - ) as client: - yield client + ) as async_client: + yield async_client @pytest.fixture @@ -147,25 +147,33 @@ async def test_compute(db_session: AsyncSession) -> Compute: @pytest.fixture -def authorized_client(client: AsyncClient, test_user: User) -> AsyncClient: +def unauthorized_client(base_client: AsyncClient, test_user: User) -> AsyncClient: + return base_client - access_token = auth_service.create_access_token(test_user.username) - client.headers = { - **client.headers, - "Authorization": f"Bearer {access_token}", - } - return client @pytest.fixture -async def admin_client(client: AsyncClient) -> AsyncClient: +def authorized_client(base_client: AsyncClient, test_user: User) -> AsyncClient: - # user "admin" is automatically created when the users table is created - access_token = auth_service.create_access_token("admin") - client.headers = { - **client.headers, + access_token = auth_service.create_access_token(test_user.username) + base_client.headers = { + **base_client.headers, "Authorization": f"Bearer {access_token}", } - return client + return base_client + + +@pytest.fixture +async def client(base_client: AsyncClient) -> AsyncClient: + + # The super admin is automatically created when the users table is created + # this account that can access all endpoints without restrictions. + access_token = auth_service.create_access_token("admin") + base_client.headers = { + **base_client.headers, + "Authorization": f"Bearer {access_token}", + } + return base_client + @pytest.fixture def controller_config_path(tmpdir): From 0465cb87f6836a9dd1699f49712b98d211e023cc Mon Sep 17 00:00:00 2001 From: grossmj Date: Tue, 20 Apr 2021 11:59:02 +0930 Subject: [PATCH 09/10] Protect the API and add alternative authentication endpoint. --- gns3server/api/routes/controller/__init__.py | 87 +++++++++++++++--- .../api/routes/controller/controller.py | 78 +++++++++------- .../controller/dependencies/authentication.py | 2 +- gns3server/api/routes/controller/users.py | 88 ++++++++++++------- gns3server/schemas/__init__.py | 2 +- gns3server/schemas/controller/users.py | 6 ++ tests/api/routes/controller/test_users.py | 16 ++++ 7 files changed, 199 insertions(+), 80 deletions(-) diff --git a/gns3server/api/routes/controller/__init__.py b/gns3server/api/routes/controller/__init__.py index 287dc7bd..cb1e6ddc 100644 --- a/gns3server/api/routes/controller/__init__.py +++ b/gns3server/api/routes/controller/__init__.py @@ -14,7 +14,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from fastapi import APIRouter +from fastapi import APIRouter, Depends from . import controller from . import appliances @@ -30,17 +30,80 @@ from . import symbols from . import templates from . import users +from .dependencies.authentication import get_current_active_user + router = APIRouter() + router.include_router(controller.router, tags=["Controller"]) router.include_router(users.router, prefix="/users", tags=["Users"]) -router.include_router(appliances.router, prefix="/appliances", tags=["Appliances"]) -router.include_router(computes.router, prefix="/computes", tags=["Computes"]) -router.include_router(drawings.router, prefix="/projects/{project_id}/drawings", tags=["Drawings"]) -router.include_router(gns3vm.router, prefix="/gns3vm", tags=["GNS3 VM"]) -router.include_router(links.router, prefix="/projects/{project_id}/links", tags=["Links"]) -router.include_router(nodes.router, prefix="/projects/{project_id}/nodes", tags=["Nodes"]) -router.include_router(notifications.router, prefix="/notifications", tags=["Notifications"]) -router.include_router(projects.router, prefix="/projects", tags=["Projects"]) -router.include_router(snapshots.router, prefix="/projects/{project_id}/snapshots", tags=["Snapshots"]) -router.include_router(symbols.router, prefix="/symbols", tags=["Symbols"]) -router.include_router(templates.router, tags=["Templates"]) + +router.include_router( + appliances.router, + dependencies=[Depends(get_current_active_user)], + prefix="/appliances", + tags=["Appliances"] +) + +router.include_router( + computes.router, + dependencies=[Depends(get_current_active_user)], + prefix="/computes", + tags=["Computes"] +) + +router.include_router( + drawings.router, + dependencies=[Depends(get_current_active_user)], + prefix="/projects/{project_id}/drawings", + tags=["Drawings"]) + +router.include_router( + gns3vm.router, + dependencies=[Depends(get_current_active_user)], + prefix="/gns3vm", + tags=["GNS3 VM"] +) + +router.include_router( + links.router, + dependencies=[Depends(get_current_active_user)], + prefix="/projects/{project_id}/links", + tags=["Links"] +) + +router.include_router( + nodes.router, + dependencies=[Depends(get_current_active_user)], + prefix="/projects/{project_id}/nodes", + tags=["Nodes"] +) + +router.include_router( + notifications.router, + dependencies=[Depends(get_current_active_user)], + prefix="/notifications", + tags=["Notifications"]) + +router.include_router( + projects.router, + dependencies=[Depends(get_current_active_user)], + prefix="/projects", + tags=["Projects"]) + +router.include_router( + snapshots.router, + dependencies=[Depends(get_current_active_user)], + prefix="/projects/{project_id}/snapshots", + tags=["Snapshots"]) + +router.include_router( + symbols.router, + dependencies=[Depends(get_current_active_user)], + prefix="/symbols", tags=["Symbols"] +) + +router.include_router( + templates.router, + dependencies=[Depends(get_current_active_user)], + tags=["Templates"] +) diff --git a/gns3server/api/routes/controller/controller.py b/gns3server/api/routes/controller/controller.py index d26e5137..9fa2cbb8 100644 --- a/gns3server/api/routes/controller/controller.py +++ b/gns3server/api/routes/controller/controller.py @@ -18,7 +18,7 @@ import asyncio import signal import os -from fastapi import APIRouter, status +from fastapi import APIRouter, Depends, status from fastapi.encoders import jsonable_encoder from typing import List @@ -28,6 +28,7 @@ from gns3server.version import __version__ from gns3server.controller.controller_error import ControllerError, ControllerForbiddenError from gns3server import schemas +from .dependencies.authentication import get_current_active_user import logging @@ -36,8 +37,39 @@ log = logging.getLogger(__name__) router = APIRouter() +@router.get( + "/version", + response_model=schemas.Version, +) +def get_version() -> dict: + """ + Return the server version number. + """ + + local_server = Config.instance().settings.Server.local + return {"version": __version__, "local": local_server} + + +@router.post( + "/version", + response_model=schemas.Version, + response_model_exclude_defaults=True, + responses={409: {"model": schemas.ErrorMessage, "description": "Invalid version"}}, +) +def check_version(version: schemas.Version) -> dict: + """ + Check if version is the same as the server. + """ + + print(version.version) + if version.version != __version__: + raise ControllerError(f"Client version {version.version} is not the same as server version {__version__}") + return {"version": __version__} + + @router.post( "/shutdown", + dependencies=[Depends(get_current_active_user)], status_code=status.HTTP_204_NO_CONTENT, responses={403: {"model": schemas.ErrorMessage, "description": "Server shutdown not allowed"}}, ) @@ -71,38 +103,11 @@ async def shutdown() -> None: os.kill(os.getpid(), signal.SIGTERM) -@router.get("/version", response_model=schemas.Version) -def get_version() -> dict: - """ - Return the server version number. - """ - - local_server = Config.instance().settings.Server.local - return {"version": __version__, "local": local_server} - - -@router.post( - "/version", - response_model=schemas.Version, - response_model_exclude_defaults=True, - responses={409: {"model": schemas.ErrorMessage, "description": "Invalid version"}}, +@router.get( + "/iou_license", + dependencies=[Depends(get_current_active_user)], + response_model=schemas.IOULicense ) -def check_version(version: schemas.Version) -> dict: - """ - Check if version is the same as the server. - - :param request: - :param response: - :return: - """ - - print(version.version) - if version.version != __version__: - raise ControllerError(f"Client version {version.version} is not the same as server version {__version__}") - return {"version": __version__} - - -@router.get("/iou_license", response_model=schemas.IOULicense) def get_iou_license() -> schemas.IOULicense: """ Return the IOU license settings @@ -111,7 +116,12 @@ def get_iou_license() -> schemas.IOULicense: return Controller.instance().iou_license -@router.put("/iou_license", status_code=status.HTTP_201_CREATED, response_model=schemas.IOULicense) +@router.put( + "/iou_license", + dependencies=[Depends(get_current_active_user)], + status_code=status.HTTP_201_CREATED, + response_model=schemas.IOULicense +) async def update_iou_license(iou_license: schemas.IOULicense) -> schemas.IOULicense: """ Update the IOU license settings. @@ -124,7 +134,7 @@ async def update_iou_license(iou_license: schemas.IOULicense) -> schemas.IOULice return current_iou_license -@router.get("/statistics") +@router.get("/statistics", dependencies=[Depends(get_current_active_user)]) async def statistics() -> List[dict]: """ Return server statistics. diff --git a/gns3server/api/routes/controller/dependencies/authentication.py b/gns3server/api/routes/controller/dependencies/authentication.py index 819daec9..60ee5de2 100644 --- a/gns3server/api/routes/controller/dependencies/authentication.py +++ b/gns3server/api/routes/controller/dependencies/authentication.py @@ -24,7 +24,7 @@ from gns3server.services import auth_service from .database import get_repository -oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/v3/users/login") # FIXME: URL prefix +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/v3/users/login") async def get_user_from_token( diff --git a/gns3server/api/routes/controller/users.py b/gns3server/api/routes/controller/users.py index 9649e911..84dff464 100644 --- a/gns3server/api/routes/controller/users.py +++ b/gns3server/api/routes/controller/users.py @@ -44,10 +44,53 @@ log = logging.getLogger(__name__) router = APIRouter() -@router.get("", response_model=List[schemas.User]) +@router.post("/login", response_model=schemas.Token) +async def login( + users_repo: UsersRepository = Depends(get_repository(UsersRepository)), + form_data: OAuth2PasswordRequestForm = Depends(), +) -> schemas.Token: + """ + Default user login method using forms (x-www-form-urlencoded). + Example: curl http://host:port/v3/users/login -H "Content-Type: application/x-www-form-urlencoded" -d "username=admin&password=admin" + """ + + user = await users_repo.authenticate_user(username=form_data.username, password=form_data.password) + if not user: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Authentication was unsuccessful.", + headers={"WWW-Authenticate": "Bearer"}, + ) + + token = schemas.Token(access_token=auth_service.create_access_token(user.username), token_type="bearer") + return token + + +@router.post("/authenticate", response_model=schemas.Token) +async def authenticate( + user_credentials: schemas.Credentials, + users_repo: UsersRepository = Depends(get_repository(UsersRepository)), +) -> schemas.Token: + """ + Alternative authentication method using json. + Example: curl http://host:port/v3/users/authenticate -d '{"username": "admin", "password": "admin"}' + """ + + user = await users_repo.authenticate_user(username=user_credentials.username, password=user_credentials.password) + if not user: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Authentication was unsuccessful.", + headers={"WWW-Authenticate": "Bearer"}, + ) + + token = schemas.Token(access_token=auth_service.create_access_token(user.username), token_type="bearer") + return token + + +@router.get("", response_model=List[schemas.User], dependencies=[Depends(get_current_active_user)]) async def get_users( - users_repo: UsersRepository = Depends(get_repository(UsersRepository)), - current_user: schemas.User = Depends(get_current_active_user) + users_repo: UsersRepository = Depends(get_repository(UsersRepository)) ) -> List[schemas.User]: """ Get all users. @@ -56,11 +99,15 @@ async def get_users( return await users_repo.get_users() -@router.post("", response_model=schemas.User, status_code=status.HTTP_201_CREATED) +@router.post( + "", + response_model=schemas.User, + dependencies=[Depends(get_current_active_user)], + status_code=status.HTTP_201_CREATED +) async def create_user( user_create: schemas.UserCreate, - users_repo: UsersRepository = Depends(get_repository(UsersRepository)), - current_user: schemas.User = Depends(get_current_active_user) + users_repo: UsersRepository = Depends(get_repository(UsersRepository)) ) -> schemas.User: """ Create a new user. @@ -75,11 +122,10 @@ async def create_user( return await users_repo.create_user(user_create) -@router.get("/{user_id}",response_model=schemas.User) +@router.get("/{user_id}", dependencies=[Depends(get_current_active_user)], response_model=schemas.User) async def get_user( user_id: UUID, users_repo: UsersRepository = Depends(get_repository(UsersRepository)), - current_user: schemas.User = Depends(get_current_active_user) ) -> schemas.User: """ Get an user. @@ -91,12 +137,11 @@ async def get_user( return user -@router.put("/{user_id}", response_model=schemas.User) +@router.put("/{user_id}", dependencies=[Depends(get_current_active_user)], response_model=schemas.User) async def update_user( user_id: UUID, user_update: schemas.UserUpdate, - users_repo: UsersRepository = Depends(get_repository(UsersRepository)), - current_user: schemas.User = Depends(get_current_active_user) + users_repo: UsersRepository = Depends(get_repository(UsersRepository)) ) -> schemas.User: """ Update an user. @@ -126,27 +171,6 @@ async def delete_user( raise ControllerNotFoundError(f"User '{user_id}' not found") -@router.post("/login", response_model=schemas.Token) -async def login( - users_repo: UsersRepository = Depends(get_repository(UsersRepository)), - form_data: OAuth2PasswordRequestForm = Depends(), -) -> schemas.Token: - """ - User login. - """ - - user = await users_repo.authenticate_user(username=form_data.username, password=form_data.password) - if not user: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Authentication was unsuccessful.", - headers={"WWW-Authenticate": "Bearer"}, - ) - - token = schemas.Token(access_token=auth_service.create_access_token(user.username), token_type="bearer") - return token - - @router.get("/users/me/", response_model=schemas.User) async def get_current_active_user(current_user: schemas.User = Depends(get_current_active_user)) -> schemas.User: """ diff --git a/gns3server/schemas/__init__.py b/gns3server/schemas/__init__.py index 46446f82..838141f8 100644 --- a/gns3server/schemas/__init__.py +++ b/gns3server/schemas/__init__.py @@ -27,7 +27,7 @@ from .controller.drawings import Drawing from .controller.gns3vm import GNS3VM from .controller.nodes import NodeCreate, NodeUpdate, NodeDuplicate, NodeCapture, Node from .controller.projects import ProjectCreate, ProjectUpdate, ProjectDuplicate, Project, ProjectFile -from .controller.users import UserCreate, UserUpdate, User +from .controller.users import UserCreate, UserUpdate, User, Credentials from .controller.tokens import Token from .controller.snapshots import SnapshotCreate, Snapshot from .controller.iou_license import IOULicense diff --git a/gns3server/schemas/controller/users.py b/gns3server/schemas/controller/users.py index 62421179..3fa0a551 100644 --- a/gns3server/schemas/controller/users.py +++ b/gns3server/schemas/controller/users.py @@ -56,3 +56,9 @@ class User(DateTimeModelMixin, UserBase): class Config: orm_mode = True + + +class Credentials(BaseModel): + + username: str + password: str diff --git a/tests/api/routes/controller/test_users.py b/tests/api/routes/controller/test_users.py index f02d77e5..27c37b87 100644 --- a/tests/api/routes/controller/test_users.py +++ b/tests/api/routes/controller/test_users.py @@ -214,6 +214,22 @@ class TestUserLogin: assert "token_type" in res.json() assert res.json().get("token_type") == "bearer" + async def test_user_can_authenticate_using_json( + self, + app: FastAPI, + unauthorized_client: AsyncClient, + test_user: User, + config: Config + ) -> None: + + credentials = { + "username": test_user.username, + "password": "user1_password", + } + res = await unauthorized_client.post(app.url_path_for("authenticate"), json=credentials) + assert res.status_code == status.HTTP_200_OK + assert res.json().get("access_token") + @pytest.mark.parametrize( "username, password, status_code", ( From dff1ec9bc6c51701cea80f006d2a9779db7eca79 Mon Sep 17 00:00:00 2001 From: grossmj Date: Tue, 20 Apr 2021 19:54:55 +0930 Subject: [PATCH 10/10] Fix /users/me endpoint. --- gns3server/api/routes/controller/users.py | 2 +- gns3server/schemas/config.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gns3server/api/routes/controller/users.py b/gns3server/api/routes/controller/users.py index 84dff464..c830c8b5 100644 --- a/gns3server/api/routes/controller/users.py +++ b/gns3server/api/routes/controller/users.py @@ -171,7 +171,7 @@ async def delete_user( raise ControllerNotFoundError(f"User '{user_id}' not found") -@router.get("/users/me/", response_model=schemas.User) +@router.get("/me/", response_model=schemas.User) async def get_current_active_user(current_user: schemas.User = Depends(get_current_active_user)) -> schemas.User: """ Get the current active user. diff --git a/gns3server/schemas/config.py b/gns3server/schemas/config.py index d692cf9e..80f52add 100644 --- a/gns3server/schemas/config.py +++ b/gns3server/schemas/config.py @@ -23,7 +23,7 @@ class ControllerSettings(BaseModel): jwt_secret_key: str = None jwt_algorithm: str = "HS256" - jwt_access_token_expire_minutes: int = 1440 + jwt_access_token_expire_minutes: int = 1440 # 24 hours class Config: validate_assignment = True