diff --git a/gns3server/api/routes/compute/__init__.py b/gns3server/api/routes/compute/__init__.py
index b60dfb7cd..57b7225df 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/atm_switch_nodes.py b/gns3server/api/routes/compute/atm_switch_nodes.py
index 23a45ef18..44dbc57a8 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,36 +59,42 @@ 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",
mappings=node_data.get("mappings"),
)
- return node.__json__()
+ return node.asdict()
@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.
"""
- return node.__json__()
+ return node.asdict()
@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.
"""
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)
-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.
"""
@@ -99,11 +105,11 @@ 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)
-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.
@@ -156,11 +166,11 @@ 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)
-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 0931aa5c4..8e8653483 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 0c710a2b9..65ad686f7 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.
"""
@@ -72,20 +72,20 @@ 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)
-def get_cloud(node: Cloud = Depends(dep_node)):
+def get_cloud(node: Cloud = Depends(dep_node)) -> schemas.Cloud:
"""
Return a cloud node.
"""
- return node.__json__()
+ return node.asdict()
@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.
"""
@@ -95,11 +95,11 @@ 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)
-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.
@@ -154,7 +155,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(
@@ -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.
@@ -177,11 +179,16 @@ 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)
-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 81d8e8ecb..d97ab3f20 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")
@@ -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 8663d82ba..042f2312c 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.
"""
@@ -82,20 +82,20 @@ 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)
-def get_docker_node(node: DockerVM = Depends(dep_node)):
+def get_docker_node(node: DockerVM = Depends(dep_node)) -> schemas.Docker:
"""
Return a Docker node.
"""
- return node.__json__()
+ return node.asdict()
@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.
"""
@@ -128,11 +128,11 @@ 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)
-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,13 +195,16 @@ 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.
"""
new_node = await Docker.instance().duplicate_node(node.id, str(destination_node_id))
- return new_node.__json__()
+ return new_node.asdict()
@router.post(
@@ -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.
@@ -219,7 +222,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(
@@ -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.
@@ -239,11 +242,15 @@ 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)
-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 63e0bde99..81317fd68 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.
"""
@@ -81,31 +81,31 @@ 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)
-def get_router(node: Router = Depends(dep_node)):
+def get_router(node: Router = Depends(dep_node)) -> schemas.Dynamips:
"""
Return Dynamips router.
"""
- return node.__json__()
+ return node.asdict()
@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.
"""
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)
-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,14 +164,19 @@ 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.
"""
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(
@@ -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.
"""
@@ -188,11 +198,11 @@ 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)
-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,18 +283,18 @@ 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.
"""
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")
-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 316bf2294..783cd397f 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.
"""
@@ -65,32 +65,35 @@ 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)
-def get_ethernet_hub(node: EthernetHub = Depends(dep_node)):
+def get_ethernet_hub(node: EthernetHub = Depends(dep_node)) -> schemas.EthernetHub:
"""
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)
async def duplicate_ethernet_hub(
destination_node_id: UUID = Body(..., embed=True), node: EthernetHub = Depends(dep_node)
-):
+) -> schemas.EthernetHub:
"""
Duplicate an 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)
-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.
"""
@@ -101,11 +104,11 @@ 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)
-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.
@@ -158,11 +165,16 @@ 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)
-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 277dfcbbe..3375dd196 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.
"""
@@ -68,29 +68,33 @@ 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)):
+def get_ethernet_switch(node: EthernetSwitch = Depends(dep_node)) -> schemas.EthernetSwitch:
- return node.__json__()
+ 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.
"""
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)
-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.
"""
@@ -104,11 +108,11 @@ 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)
-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,16 +156,25 @@ 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)
- return nio.__json__()
+ return nio.asdict()
@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 14dd490be..d0ea5537b 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.
"""
@@ -65,34 +68,36 @@ 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)
-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.
"""
- return node.__json__()
+ return node.asdict()
@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.
"""
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)
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.
"""
@@ -103,11 +108,11 @@ 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)
-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.
@@ -160,11 +169,16 @@ 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)
-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 6f5fe522c..b5103aafa 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 399bdc555..1234c10d6 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.
"""
@@ -79,20 +79,20 @@ 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)
-def get_iou_node(node: IOUVM = Depends(dep_node)):
+def get_iou_node(node: IOUVM = Depends(dep_node)) -> schemas.IOU:
"""
Return an IOU node.
"""
- return node.__json__()
+ return node.asdict()
@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.
"""
@@ -109,11 +109,11 @@ 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)
-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,17 +122,20 @@ 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.
"""
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)
-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.
"""
@@ -143,11 +146,11 @@ 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)
-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,14 +187,14 @@ 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.
"""
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(
@@ -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.
"""
@@ -213,11 +216,11 @@ 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)
-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 3af083094..c15f92256 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.
"""
@@ -67,20 +67,20 @@ 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)
-def get_nat_node(node: Nat = Depends(dep_node)):
+def get_nat_node(node: Nat = Depends(dep_node)) -> schemas.NAT:
"""
Return a NAT node.
"""
- return node.__json__()
+ return node.asdict()
@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.
"""
@@ -90,11 +90,11 @@ 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)
-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.
@@ -149,7 +150,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(
@@ -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.
@@ -172,11 +174,16 @@ 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)
-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 830488e5d..99d9ed31e 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 4637f5e1d..8d9fe3b89 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,17 +52,17 @@ 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.
"""
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)
-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.
"""
@@ -75,30 +75,33 @@ 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)
-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.
"""
await project.update(variables=project_data.variables)
- return project.__json__()
+ return project.asdict()
@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.
"""
- return project.__json__()
+ return project.asdict()
@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.
"""
@@ -152,8 +155,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)
@@ -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 ed8d4aecb..16a860911 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.
"""
@@ -76,20 +75,20 @@ 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)
-def get_qemu_node(node: QemuVM = Depends(dep_node)):
+def get_qemu_node(node: QemuVM = Depends(dep_node)) -> schemas.Qemu:
"""
Return a Qemu node.
"""
- return node.__json__()
+ return node.asdict()
@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.
"""
@@ -101,11 +100,11 @@ 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)
-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,23 +113,26 @@ 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.
"""
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)
-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.
@@ -196,7 +202,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(
@@ -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.
@@ -218,11 +228,15 @@ 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)
-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 f558a02e7..93b548a0a 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.
"""
@@ -80,20 +79,23 @@ 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)
-def get_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)):
+def get_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> schemas.VirtualBox:
"""
Return a VirtualBox node.
"""
- return node.__json__()
+ return node.asdict()
@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.
"""
@@ -131,11 +133,11 @@ 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)
-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,21 +146,16 @@ 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.
"""
- 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()
@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.
"""
@@ -167,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.
"""
@@ -176,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.
"""
@@ -185,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.
"""
@@ -199,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.
@@ -208,7 +209,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(
@@ -217,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.
@@ -230,11 +235,15 @@ 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)
-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.
@@ -245,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.
@@ -263,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.
@@ -273,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.
@@ -285,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.
"""
@@ -294,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 1d857f1e7..976b749e1 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.
"""
@@ -73,20 +73,20 @@ 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)
-def get_vmware_node(node: VMwareVM = Depends(dep_node)):
+def get_vmware_node(node: VMwareVM = Depends(dep_node)) -> schemas.VMware:
"""
Return a VMware node.
"""
- return node.__json__()
+ return node.asdict()
@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.
"""
@@ -99,11 +99,11 @@ 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)
-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,21 +112,16 @@ 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.
"""
- 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()
@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.
"""
@@ -135,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.
"""
@@ -144,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.
"""
@@ -153,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.
"""
@@ -167,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.
@@ -176,7 +175,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(
@@ -185,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.
@@ -196,11 +199,15 @@ 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)
-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.
@@ -211,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.
@@ -226,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.
@@ -236,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.
@@ -261,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.
"""
@@ -270,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 0ebfe7215..7d9ffab91 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.
"""
@@ -66,20 +66,20 @@ 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)
-def get_vpcs_node(node: VPCSVM = Depends(dep_node)):
+def get_vpcs_node(node: VPCSVM = Depends(dep_node)) -> schemas.VPCS:
"""
Return a VPCS node.
"""
- return node.__json__()
+ return node.asdict()
@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.
"""
@@ -89,11 +89,11 @@ 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)
-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,17 +102,19 @@ 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.
"""
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)
-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.
@@ -163,7 +169,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(
@@ -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.
@@ -183,11 +193,16 @@ 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)
-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/__init__.py b/gns3server/api/routes/controller/__init__.py
index 287dc7bda..cb1e6ddc1 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/appliances.py b/gns3server/api/routes/controller/appliances.py
index f307e67c7..0a2dfc7e9 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.
"""
@@ -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/computes.py b/gns3server/api/routes/controller/computes.py
index ff0c13e39..2cc8727c2 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 8dfa29985..9fa2cbb81 100644
--- a/gns3server/api/routes/controller/controller.py
+++ b/gns3server/api/routes/controller/controller.py
@@ -18,8 +18,9 @@ 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
from gns3server.config import Config
from gns3server.controller import Controller
@@ -27,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
@@ -35,12 +37,43 @@ 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"}},
)
-async def shutdown():
+async def shutdown() -> None:
"""
Shutdown the local server
"""
@@ -70,39 +103,12 @@ async def shutdown():
os.kill(os.getpid(), signal.SIGTERM)
-@router.get("/version", response_model=schemas.Version)
-def get_version():
- """
- 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):
- """
- 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():
+def get_iou_license() -> schemas.IOULicense:
"""
Return the IOU license settings
"""
@@ -110,8 +116,13 @@ def get_iou_license():
return Controller.instance().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):
+@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.
"""
@@ -123,8 +134,8 @@ async def update_iou_license(iou_license: schemas.IOULicense):
return current_iou_license
-@router.get("/statistics")
-async def 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 c66b4b621..60ee5de21 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(
@@ -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/drawings.py b/gns3server/api/routes/controller/drawings.py
index ca6e2b950..bce40ca77 100644
--- a/gns3server/api/routes/controller/drawings.py
+++ b/gns3server/api/routes/controller/drawings.py
@@ -32,39 +32,39 @@ 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.
"""
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)
-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.
"""
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)
-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.
"""
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)
-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.
"""
@@ -72,11 +72,11 @@ 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)
-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 0a42c88af..d019189bd 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,16 +50,16 @@ 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.
"""
- return Controller.instance().gns3vm.__json__()
+ return Controller.instance().gns3vm.asdict()
@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.
"""
@@ -67,4 +68,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 7b91460d7..31ecff6e6 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,13 +53,13 @@ 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.
"""
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(
@@ -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) -> schemas.Link:
"""
Create a new link.
"""
@@ -94,11 +94,11 @@ async def create_link(project_id: UUID, link_data: schemas.Link):
except ControllerError as e:
await project.delete_link(link.id)
raise e
- return link.__json__()
+ return link.asdict()
@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,16 +107,16 @@ 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.
"""
- return link.__json__()
+ return link.asdict()
@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)) -> schemas.Link:
"""
Update a link.
"""
@@ -128,11 +128,11 @@ async def update_link(link_data: schemas.Link, link: Link = Depends(dep_link)):
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)
-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,17 +142,17 @@ 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.
"""
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)
-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.
"""
@@ -161,11 +161,11 @@ 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)
-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 e14dd334f..899e9a31e 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.Node, project: Project = Depends(dep_project)):
+async def create_node(node_data: schemas.NodeCreate, project: Project = Depends(dep_project)) -> schemas.Node:
"""
Create a new node.
"""
@@ -117,20 +117,20 @@ async def create_node(node_data: schemas.Node, project: Project = Depends(dep_pr
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)
-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.
"""
- 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)
-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,16 +167,16 @@ 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.
"""
- return node.__json__()
+ return node.asdict()
@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.
"""
@@ -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(
@@ -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,17 +206,17 @@ 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.
"""
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)
-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,19 +252,19 @@ 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.
"""
links = []
for link in node.links:
- links.append(link.__json__())
+ links.append(link.asdict())
return links
@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 bb64fbb98..3634a7d4a 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 601684128..c8936f5fc 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,13 +64,13 @@ 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.
"""
controller = Controller.instance()
- return [p.__json__() for p in controller.projects.values()]
+ return [p.asdict() for p in controller.projects.values()]
@router.post(
@@ -80,37 +80,40 @@ 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.
"""
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)
-def get_project(project: Project = Depends(dep_project)):
+def get_project(project: Project = Depends(dep_project)) -> schemas.Project:
"""
Return a project.
"""
- return project.__json__()
+ return project.asdict()
@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.
"""
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)
-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,13 +151,13 @@ 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.
"""
await project.open()
- return project.__json__()
+ return project.asdict()
@router.post(
@@ -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).
"""
@@ -176,11 +179,11 @@ 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")
-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.
"""
@@ -323,7 +331,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(
@@ -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.
"""
@@ -348,11 +359,11 @@ 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}")
-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 54284f666..d410e88a1 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,27 +46,30 @@ 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.
"""
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)
-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.
"""
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)
-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,11 +78,11 @@ 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.
"""
snapshot = project.get_snapshot(str(snapshot_id))
project = await snapshot.restore()
- return project.__json__()
+ return project.asdict()
diff --git a/gns3server/api/routes/controller/symbols.py b/gns3server/api/routes/controller/symbols.py
index c4c1b3929..127f2c145 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 725f3776c..bb2c5ae9a 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.
"""
@@ -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/api/routes/controller/users.py b/gns3server/api/routes/controller/users.py
index 89d14275e..c830c8b55 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
@@ -44,8 +44,54 @@ log = logging.getLogger(__name__)
router = APIRouter()
-@router.get("", response_model=List[schemas.User])
-async def get_users(users_repo: UsersRepository = Depends(get_repository(UsersRepository))) -> 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))
+) -> List[schemas.User]:
"""
Get all users.
"""
@@ -53,9 +99,15 @@ async def get_users(users_repo: UsersRepository = Depends(get_repository(UsersRe
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))
+ user_create: schemas.UserCreate,
+ users_repo: UsersRepository = Depends(get_repository(UsersRepository))
) -> schemas.User:
"""
Create a new user.
@@ -70,9 +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))
+ user_id: UUID,
+ users_repo: UsersRepository = Depends(get_repository(UsersRepository)),
) -> schemas.User:
"""
Get an user.
@@ -84,11 +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)),
+ user_id: UUID,
+ user_update: schemas.UserUpdate,
+ users_repo: UsersRepository = Depends(get_repository(UsersRepository))
) -> schemas.User:
"""
Update an user.
@@ -110,36 +163,15 @@ 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 admin cannot be deleted")
success = await users_repo.delete_user(user_id)
if not success:
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)
+@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/api/server.py b/gns3server/api/server.py
index de2310eb2..da948299e 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/compute/base_manager.py b/gns3server/compute/base_manager.py
index b578706cf..57c7e2528 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/compute/builtin/nodes/cloud.py b/gns3server/compute/builtin/nodes/cloud.py
index dcfdd5c72..dc7b1de0d 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 ca6d316ad..fc601ff01 100644
--- a/gns3server/compute/builtin/nodes/ethernet_hub.py
+++ b/gns3server/compute/builtin/nodes/ethernet_hub.py
@@ -38,9 +38,14 @@ class EthernetHub(BaseNode):
super().__init__(name, node_id, project, manager)
- def __json__(self):
+ def asdict(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 a16c64b31..d4a4863a3 100644
--- a/gns3server/compute/builtin/nodes/ethernet_switch.py
+++ b/gns3server/compute/builtin/nodes/ethernet_switch.py
@@ -38,9 +38,14 @@ class EthernetSwitch(BaseNode):
super().__init__(name, node_id, project, manager)
- def __json__(self):
+ def asdict(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/nat.py b/gns3server/compute/builtin/nodes/nat.py
index a3da907fa..21deca929 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 a10d3ec22..55a38e0ce 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 519ac7dc2..cf2cc7509 100644
--- a/gns3server/compute/dynamips/nios/nio_generic_ethernet.py
+++ b/gns3server/compute/dynamips/nios/nio_generic_ethernet.py
@@ -65,6 +65,9 @@ class NIOGenericEthernet(NIO):
return self._ethernet_device
- def __json__(self):
+ def asdict(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 2ced9d836..64f7e9385 100644
--- a/gns3server/compute/dynamips/nios/nio_linux_ethernet.py
+++ b/gns3server/compute/dynamips/nios/nio_linux_ethernet.py
@@ -64,6 +64,9 @@ class NIOLinuxEthernet(NIO):
return self._ethernet_device
- def __json__(self):
+ def asdict(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_null.py b/gns3server/compute/dynamips/nios/nio_null.py
index 6f83f11d3..08144646c 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 4e27dccec..50efd5534 100644
--- a/gns3server/compute/dynamips/nios/nio_tap.py
+++ b/gns3server/compute/dynamips/nios/nio_tap.py
@@ -58,6 +58,9 @@ class NIOTAP(NIO):
return self._tap_device
- def __json__(self):
+ def asdict(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 2988e8586..47faacc43 100644
--- a/gns3server/compute/dynamips/nios/nio_udp.py
+++ b/gns3server/compute/dynamips/nios/nio_udp.py
@@ -126,6 +126,11 @@ class NIOUDP(NIO):
return self._rport
- def __json__(self):
+ def asdict(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 dfc9065b6..e6edaa4ba 100644
--- a/gns3server/compute/dynamips/nios/nio_unix.py
+++ b/gns3server/compute/dynamips/nios/nio_unix.py
@@ -79,6 +79,10 @@ class NIOUNIX(NIO):
return self._remote_file
- def __json__(self):
+ def asdict(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 ef2d921fe..d65687739 100644
--- a/gns3server/compute/dynamips/nios/nio_vde.py
+++ b/gns3server/compute/dynamips/nios/nio_vde.py
@@ -79,6 +79,10 @@ class NIOVDE(NIO):
return self._local_file
- def __json__(self):
+ def asdict(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/dynamips/nodes/atm_switch.py b/gns3server/compute/dynamips/nodes/atm_switch.py
index e47b8599e..c97b3ad35 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 641a5fadd..55b8dc4dc 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 65240202f..8c39f43b2 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 cc4255ab9..3ef3f73ff 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 14f875da1..fc10a1629 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 443ac1be4..c20cdbecf 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 98e8efc37..9da5de8d5 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 991c87b75..fe2f2b039 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 bad1d20f9..46f8cc5a4 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 906df0197..1ca67f79a 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 91058c1cf..32c920898 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 f144d673e..a2bbe2356 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 cd2f1c1ce..3ca8f371c 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 a80ab5e2e..0f7b48d31 100644
--- a/gns3server/compute/nios/nio_ethernet.py
+++ b/gns3server/compute/nios/nio_ethernet.py
@@ -48,6 +48,9 @@ class NIOEthernet(NIO):
return "NIO Ethernet"
- def __json__(self):
+ def asdict(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 51deefe2a..a81b0906f 100644
--- a/gns3server/compute/nios/nio_tap.py
+++ b/gns3server/compute/nios/nio_tap.py
@@ -48,6 +48,9 @@ class NIOTAP(NIO):
return "NIO TAP"
- def __json__(self):
+ def asdict(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 68fd2a8ab..32fe16b86 100644
--- a/gns3server/compute/nios/nio_udp.py
+++ b/gns3server/compute/nios/nio_udp.py
@@ -72,6 +72,11 @@ class NIOUDP(NIO):
return "NIO UDP"
- def __json__(self):
+ def asdict(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/port_manager.py b/gns3server/compute/port_manager.py
index cdfaa72ae..1dfa58e25 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 c1a44225e..d2538d16b 100644
--- a/gns3server/compute/project.py
+++ b/gns3server/compute/project.py
@@ -77,9 +77,13 @@ class Project:
log.info(f"Project {self._id} with path '{self._path}' created")
- def __json__(self):
+ def asdict(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/compute/project_manager.py b/gns3server/compute/project_manager.py
index b657007c6..d1f77cb82 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 7e6182102..ed0cbefa4 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
@@ -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 0174b32a2..88719dd42 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 f2262e4cc..6478f2a4e 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 a5acb49fa..3ca0e2970 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 46b3c966f..2774dd0f9 100644
--- a/gns3server/controller/__init__.py
+++ b/gns3server/controller/__init__.py
@@ -353,13 +353,14 @@ 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__())
+ 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):
@@ -398,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 d96d436b2..7913f0ab6 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):
@@ -59,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 e98274e6e..375c778a5 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 04c739506..ebe969b7d 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
@@ -163,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):
@@ -291,10 +290,11 @@ 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
"""
+
if topology_dump:
return {
"compute_id": self._id,
@@ -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
@@ -486,14 +486,15 @@ 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()))
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
@@ -522,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 a1a898094..ce8581475 100644
--- a/gns3server/controller/drawing.py
+++ b/gns3server/controller/drawing.py
@@ -193,16 +193,17 @@ 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
"""
+
if topology_dump:
return {
"drawing_id": self._id,
diff --git a/gns3server/controller/gns3vm/__init__.py b/gns3server/controller/gns3vm/__init__.py
index e23f69994..0c3c95122 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 b2ab38195..d5a1bc41c 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 80e8c5d64..ca086455a 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
@@ -420,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
@@ -450,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):
@@ -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}"
@@ -778,13 +777,12 @@ 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 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.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 9b9c3c5a0..48d5a3d2a 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, {}))
@@ -110,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":
@@ -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/ports/port.py b/gns3server/controller/ports/port.py
index 9db29c451..e0a0406ba 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 8a5c7d3c9..511461944 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):
@@ -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
@@ -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
@@ -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,10 +1176,11 @@ 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")
- 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",
@@ -1219,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 e0af79af2..5b389d740 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()
@@ -137,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 687f2b29b..80f631295 100644
--- a/gns3server/controller/topology.py
+++ b/gns3server/controller/topology.py
@@ -30,9 +30,13 @@ 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.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
@@ -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",
@@ -121,7 +125,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/core/tasks.py b/gns3server/core/tasks.py
index 4623c487d..f1a95556f 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 exiting.
+ """
+
async def shutdown_handler() -> None:
await HTTPClient.close_session()
await Controller.instance().stop()
diff --git a/gns3server/db/models/users.py b/gns3server/db/models/users.py
index 2dca08947..6f9400ba4 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,19 @@ 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("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 2a5ce4aaa..96576cfc0 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:
@@ -59,7 +63,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 +77,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)
@@ -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/handlers/api/controller/symbol_handler.py b/gns3server/handlers/api/controller/symbol_handler.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/gns3server/schemas/__init__.py b/gns3server/schemas/__init__.py
index 022d428e4..838141f80 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 LinkCreate, LinkUpdate, 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, Credentials
+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 85ae01d36..e524384fc 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 6f9e55b19..836fbfa37 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 7ef0fe026..92d8aadd7 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 4f033c858..7129aaae3 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 1289c0134..64bc07c2c 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 e559a6d44..803be8f3b 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 9b1050eca..0edd7f1b1 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 fb6e580c2..6b759eb43 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 f237fdd9b..aa09323bd 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 d2edabf2b..755ac6647 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 10514a3b1..75ab4b5b7 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 6433f5d90..de464fef3 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 72fc11449..53f93975c 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 4a3f2fc95..a2fb26f22 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 324be1a76..6373182c0 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 c30dada7a..80f52adde 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
@@ -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
@@ -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 95%
rename from gns3server/schemas/computes.py
rename to gns3server/schemas/controller/computes.py
index 469728109..f84bb5271 100644
--- a/gns3server/schemas/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/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 52bafdd00..2eb770265 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 60%
rename from gns3server/schemas/links.py
rename to gns3server/schemas/controller/links.py
index 98efba28f..6a375cb98 100644
--- a/gns3server/schemas/links.py
+++ b/gns3server/schemas/controller/links.py
@@ -17,9 +17,9 @@
from pydantic import BaseModel, Field
from typing import List, Optional
from enum import Enum
-from uuid import UUID
+from uuid import UUID, uuid4
-from .common import Label
+from .labels import Label
class LinkNode(BaseModel):
@@ -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):
@@ -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/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 3a6f2802e..1e59fef14 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 f9de0b03c..74866b2ee 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 6ae199f18..37fd19425 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 48f16e1a4..ceb050401 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 308a90f52..9598505e3 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 947d41dbb..7b61cd01a 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 51373e310..2e42aaa2c 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 0c7100b99..f7bba8d41 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 8ab0d4521..f69eb23a2 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 967bea3d0..82cd63f4c 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 e0d6cef08..a8d604d88 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 18291c89b..f75009ce2 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 81%
rename from gns3server/schemas/users.py
rename to gns3server/schemas/controller/users.py
index 445e1d7b0..3fa0a5515 100644
--- a/gns3server/schemas/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=6, max_length=100)
class UserUpdate(UserBase):
@@ -45,14 +45,20 @@ 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=6, max_length=100)
class User(DateTimeModelMixin, UserBase):
user_id: UUID
is_active: bool = True
- is_superuser: bool = False
+ is_superadmin: bool = False
class Config:
orm_mode = True
+
+
+class Credentials(BaseModel):
+
+ username: str
+ password: str
diff --git a/gns3server/server.py b/gns3server/server.py
index 409d35cbf..3c7dedb19 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
diff --git a/gns3server/services/authentication.py b/gns3server/services/authentication.py
index e2a4b64fd..021db36e7 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/gns3server/utils/cpu_percent.py b/gns3server/utils/cpu_percent.py
index 59463bb9e..016c633b6 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
diff --git a/gns3server/utils/notification_queue.py b/gns3server/utils/notification_queue.py
index e5371b0a4..e198ffc9a 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/api/routes/compute/test_virtualbox_nodes.py b/tests/api/routes/compute/test_virtualbox_nodes.py
index bff6aa95b..5ff06722c 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 f3e8783ae..a32d43747 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 6cb7040b2..0e2c0e69d 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 32041cb0c..27c37b87e 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.users import User
+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,
@@ -118,7 +119,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:
@@ -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
@@ -213,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",
(
@@ -224,19 +241,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,9 +276,56 @@ 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
+
+
+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,
+ client: AsyncClient,
+ db_session: AsyncSession
+ ) -> None:
+
+ user_repo = UsersRepository(db_session)
+ admin_in_db = await user_repo.get_user_by_username("admin")
+ 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/compute/builtin/nodes/test_cloud.py b/tests/compute/builtin/nodes/test_cloud.py
index fd0684e83..a45718fe8 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 c26a87f7b..60f2da958 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 5226c802c..8bc99ff39 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 0ef3f6bb0..4ca8a7662 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 dd6927842..f7b643d1b 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 80714f268..a69660dbf 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 e9bc12bc2..6b797c7bb 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/conftest.py b/tests/conftest.py
index 072c124e8..9645c85f9 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
@@ -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:
@@ -90,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:
@@ -104,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
@@ -143,14 +147,32 @@ 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
+
+
+@pytest.fixture
+def authorized_client(base_client: AsyncClient, test_user: User) -> AsyncClient:
access_token = auth_service.create_access_token(test_user.username)
- client.headers = {
- **client.headers,
+ 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
@@ -344,13 +366,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
diff --git a/tests/controller/test_compute.py b/tests/controller/test_compute.py
index 610e5aa2c..fc229cff6 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 b39b8bca5..4b26b0546 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 2a422d48f..f8ea464a4 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 6e818b557..ed8862508 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 450847e2e..b9cf8f755 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 2118e7b60..2192444b2 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 03b4c4d87..0e254f9a8 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 b526143a9..17be9d798 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 0e4b81c69..d196a1eba 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 fa6d2237b..6223e1880 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 a07073357..c56886480 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