1
0
mirror of https://github.com/GNS3/gns3-server synced 2025-02-02 03:11:15 +00:00

Merge remote-tracking branch 'origin/3.0' into gh-pages

This commit is contained in:
github-actions 2021-04-21 04:10:36 +00:00
commit 148dc8e692
159 changed files with 1834 additions and 1138 deletions

View File

@ -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( 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( 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( 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( 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( compute_api.include_router(
frame_relay_switch_nodes.router, frame_relay_switch_nodes.router,
prefix="/projects/{project_id}/frame_relay_switch/nodes", 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( 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"])

View File

@ -20,7 +20,7 @@ API routes for ATM switch nodes.
import os 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.encoders import jsonable_encoder
from fastapi.responses import StreamingResponse from fastapi.responses import StreamingResponse
from uuid import UUID from uuid import UUID
@ -34,7 +34,7 @@ responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find
router = APIRouter(responses=responses) 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. 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, status_code=status.HTTP_201_CREATED,
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create ATM switch node"}}, 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. 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() dynamips_manager = Dynamips.instance()
node_data = jsonable_encoder(node_data, exclude_unset=True) node_data = jsonable_encoder(node_data, exclude_unset=True)
node = await dynamips_manager.create_node( node = await dynamips_manager.create_node(
node_data.pop("name"), node_data.get("name"),
str(project_id), str(project_id),
node_data.get("node_id"), node_data.get("node_id"),
node_type="atm_switch", node_type="atm_switch",
mappings=node_data.get("mappings"), mappings=node_data.get("mappings"),
) )
return node.__json__() return node.asdict()
@router.get("/{node_id}", response_model=schemas.ATMSwitch) @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 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) @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. Duplicate an ATM switch node.
""" """
new_node = await Dynamips.instance().duplicate_node(node.id, str(destination_node_id)) 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) @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. 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: if "mappings" in node_data:
node.mappings = node_data["mappings"] node.mappings = node_data["mappings"]
node.updated() node.updated()
return node.__json__() return node.asdict()
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT) @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. 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) @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. Stop an ATM switch node.
This endpoint results in no action since ATM switch nodes are always on. 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) @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. Suspend an ATM switch node.
This endpoint results in no action since ATM switch nodes are always on. 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, response_model=schemas.UDPNIO,
) )
async def create_nio( 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. Add a NIO (Network Input/Output) to the node.
The adapter number on the switch is always 0. 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)) nio = await Dynamips.instance().create_nio(node, jsonable_encoder(nio_data, exclude_unset=True))
await node.add_nio(nio, port_number) 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) @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. Remove a NIO (Network Input/Output) from the node.
The adapter number on the switch is always 0. 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") @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
async def start_capture( 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. Start a packet capture on the node.
The adapter number on the switch is always 0. 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", "/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop_capture",
status_code=status.HTTP_204_NO_CONTENT, 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. Stop a packet capture on the node.
The adapter number on the switch is always 0. 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") @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. Stream the pcap capture file.
The adapter number on the switch is always 0. The adapter number on the switch is always 0.

View File

@ -32,7 +32,7 @@ router = APIRouter()
@router.get("/capabilities", response_model=schemas.Capabilities) @router.get("/capabilities", response_model=schemas.Capabilities)
def get_capabilities(): def get_capabilities() -> dict:
node_types = [] node_types = []
for module in MODULES: for module in MODULES:

View File

@ -20,7 +20,7 @@ API routes for cloud nodes.
import os import os
from fastapi import APIRouter, Depends, status from fastapi import APIRouter, Depends, Path, status
from fastapi.encoders import jsonable_encoder from fastapi.encoders import jsonable_encoder
from fastapi.responses import StreamingResponse from fastapi.responses import StreamingResponse
from typing import Union from typing import Union
@ -35,7 +35,7 @@ responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find
router = APIRouter(responses=responses) 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. Dependency to retrieve a node.
""" """
@ -51,7 +51,7 @@ def dep_node(project_id: UUID, node_id: UUID):
status_code=status.HTTP_201_CREATED, status_code=status.HTTP_201_CREATED,
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create cloud node"}}, 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. 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_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.remote_console_http_path = node_data.get("remote_console_http_path", node.remote_console_http_path)
node.usage = node_data.get("usage", "") node.usage = node_data.get("usage", "")
return node.__json__() return node.asdict()
@router.get("/{node_id}", response_model=schemas.Cloud) @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 a cloud node.
""" """
return node.__json__() return node.asdict()
@router.put("/{node_id}", response_model=schemas.Cloud) @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. 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: if hasattr(node, name) and getattr(node, name) != value:
setattr(node, name, value) setattr(node, name, value)
node.updated() node.updated()
return node.__json__() return node.asdict()
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT) @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. 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) @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. 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) @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. Stop a cloud node.
This endpoint results in no action since cloud nodes cannot be stopped. 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) @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. Suspend a cloud node.
This endpoint results in no action since cloud nodes cannot be suspended. 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], response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
) )
async def create_cloud_nio( async def create_cloud_nio(
adapter_number: int, *,
port_number: int, adapter_number: int = Path(..., ge=0, le=0),
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO], port_number: int,
node: Cloud = Depends(dep_node), 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. Add a NIO (Network Input/Output) to the node.
The adapter number on the cloud is always 0. 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)) nio = Builtin.instance().create_nio(jsonable_encoder(nio_data, exclude_unset=True))
await node.add_nio(nio, port_number) await node.add_nio(nio, port_number)
return nio.__json__() return nio.asdict()
@router.put( @router.put(
@ -163,11 +164,12 @@ async def create_cloud_nio(
response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO], response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
) )
async def update_cloud_nio( async def update_cloud_nio(
adapter_number: int, *,
port_number: int, adapter_number: int = Path(..., ge=0, le=0),
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO], port_number: int,
node: Cloud = Depends(dep_node), 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. Update a NIO (Network Input/Output) to the node.
The adapter number on the cloud is always 0. The adapter number on the cloud is always 0.
@ -177,11 +179,16 @@ async def update_cloud_nio(
if nio_data.filters: if nio_data.filters:
nio.filters = nio_data.filters nio.filters = nio_data.filters
await node.update_nio(port_number, nio) 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) @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. Remove a NIO (Network Input/Output) from the node.
The adapter number on the cloud is always 0. 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") @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
async def start_cloud_capture( 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. Start a packet capture on the node.
The adapter number on the cloud is always 0. The adapter number on the cloud is always 0.
@ -207,7 +218,12 @@ async def start_cloud_capture(
@router.post( @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_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. Stop a packet capture on the node.
The adapter number on the cloud is always 0. 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") @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. Stream the pcap capture file.
The adapter number on the cloud is always 0. The adapter number on the cloud is always 0.

View File

@ -72,7 +72,7 @@ def network_ports() -> dict:
""" """
m = PortManager.instance() m = PortManager.instance()
return m.__json__() return m.asdict()
@router.get("/version") @router.get("/version")
@ -123,13 +123,15 @@ def compute_statistics() -> dict:
@router.get("/qemu/binaries") @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) return await Qemu.binary_list(archs)
@router.get("/qemu/img-binaries") @router.get("/qemu/img-binaries")
async def get_image_binaries(): async def get_image_binaries() -> List[str]:
return await Qemu.img_binary_list() return await Qemu.img_binary_list()
@ -148,7 +150,7 @@ async def get_qemu_capabilities() -> dict:
status_code=status.HTTP_204_NO_CONTENT, status_code=status.HTTP_204_NO_CONTENT,
responses={403: {"model": schemas.ErrorMessage, "description": "Forbidden to create Qemu image"}}, 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. Create a Qemu image.
""" """
@ -167,7 +169,7 @@ async def create_qemu_image(image_data: schemas.QemuImageCreate):
status_code=status.HTTP_204_NO_CONTENT, status_code=status.HTTP_204_NO_CONTENT,
responses={403: {"model": schemas.ErrorMessage, "description": "Forbidden to update Qemu image"}}, 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. 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]) @router.get("/virtualbox/vms", response_model=List[dict])
async def get_virtualbox_vms(): async def get_virtualbox_vms() -> List[dict]:
vbox_manager = VirtualBox.instance() vbox_manager = VirtualBox.instance()
return await vbox_manager.list_vms() return await vbox_manager.list_vms()
@router.get("/vmware/vms", response_model=List[dict]) @router.get("/vmware/vms", response_model=List[dict])
async def get_vms(): async def get_vms() -> List[dict]:
vmware_manager = VMware.instance() vmware_manager = VMware.instance()
return await vmware_manager.list_vms() return await vmware_manager.list_vms()

View File

@ -34,7 +34,7 @@ responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find
router = APIRouter(responses=responses) 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. Dependency to retrieve a node.
""" """
@ -50,7 +50,7 @@ def dep_node(project_id: UUID, node_id: UUID):
status_code=status.HTTP_201_CREATED, status_code=status.HTTP_201_CREATED,
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Docker node"}}, 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. 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: if hasattr(container, name) and getattr(container, name) != value:
setattr(container, name, value) setattr(container, name, value)
return container.__json__() return container.asdict()
@router.get("/{node_id}", response_model=schemas.Docker) @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 a Docker node.
""" """
return node.__json__() return node.asdict()
@router.put("/{node_id}", response_model=schemas.Docker) @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. Update a Docker node.
""" """
@ -128,11 +128,11 @@ async def update_docker_node(node_data: schemas.DockerUpdate, node: DockerVM = D
if changed: if changed:
await node.update() await node.update()
node.updated() node.updated()
return node.__json__() return node.asdict()
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT) @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. 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) @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. 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) @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. 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) @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. 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) @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. 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) @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. 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) @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. 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) @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. Duplicate a Docker node.
""" """
new_node = await Docker.instance().duplicate_node(node.id, str(destination_node_id)) new_node = await Docker.instance().duplicate_node(node.id, str(destination_node_id))
return new_node.__json__() return new_node.asdict()
@router.post( @router.post(
@ -211,7 +214,7 @@ async def duplicate_docker_node(destination_node_id: UUID = Body(..., embed=True
) )
async def create_docker_node_nio( async def create_docker_node_nio(
adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: DockerVM = Depends(dep_node) 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. Add a NIO (Network Input/Output) to the node.
The port number on the Docker node is always 0. 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)) nio = Docker.instance().create_nio(jsonable_encoder(nio_data, exclude_unset=True))
await node.adapter_add_nio_binding(adapter_number, nio) await node.adapter_add_nio_binding(adapter_number, nio)
return nio.__json__() return nio.asdict()
@router.put( @router.put(
@ -229,7 +232,7 @@ async def create_docker_node_nio(
) )
async def update_docker_node_nio( async def update_docker_node_nio(
adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: DockerVM = Depends(dep_node) 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. Update a NIO (Network Input/Output) on the node.
The port number on the Docker node is always 0. The port number on the Docker node is always 0.
@ -239,11 +242,15 @@ async def update_docker_node_nio(
if nio_data.filters: if nio_data.filters:
nio.filters = nio_data.filters nio.filters = nio_data.filters
await node.adapter_update_nio_binding(adapter_number, nio) 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) @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. Delete a NIO (Network Input/Output) from the node.
The port number on the Docker node is always 0. 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") @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
async def start_docker_node_capture( 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. Start a packet capture on the node.
The port number on the Docker node is always 0. The port number on the Docker node is always 0.
@ -267,9 +277,14 @@ async def start_docker_node_capture(
@router.post( @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. Stop a packet capture on the node.
The port number on the Docker node is always 0. 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") @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. Stream the pcap capture file.
The port number on the Docker node is always 0. 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") @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. 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) @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() await node.reset_console()

View File

@ -40,7 +40,7 @@ router = APIRouter(responses=responses)
DEFAULT_CHASSIS = {"c1700": "1720", "c2600": "2610", "c3600": "3640"} 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. Dependency to retrieve a node.
""" """
@ -56,7 +56,7 @@ def dep_node(project_id: UUID, node_id: UUID):
status_code=status.HTTP_201_CREATED, status_code=status.HTTP_201_CREATED,
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Dynamips node"}}, 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. Create a new Dynamips router.
""" """
@ -81,31 +81,31 @@ async def create_router(project_id: UUID, node_data: schemas.DynamipsCreate):
node_type="dynamips", node_type="dynamips",
) )
await dynamips_manager.update_vm_settings(vm, node_data) await dynamips_manager.update_vm_settings(vm, node_data)
return vm.__json__() return vm.asdict()
@router.get("/{node_id}", response_model=schemas.Dynamips) @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 Dynamips router.
""" """
return node.__json__() return node.asdict()
@router.put("/{node_id}", response_model=schemas.Dynamips) @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. Update a Dynamips router.
""" """
await Dynamips.instance().update_vm_settings(node, jsonable_encoder(node_data, exclude_unset=True)) await Dynamips.instance().update_vm_settings(node, jsonable_encoder(node_data, exclude_unset=True))
node.updated() node.updated()
return node.__json__() return node.asdict()
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT) @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. 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) @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. 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) @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. 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) @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() await node.suspend()
@router.post("/{node_id}/resume", status_code=status.HTTP_204_NO_CONTENT) @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. 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) @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. Reload a suspended Dynamips router.
""" """
@ -164,14 +164,19 @@ async def reload_router(node: Router = Depends(dep_node)):
status_code=status.HTTP_201_CREATED, status_code=status.HTTP_201_CREATED,
response_model=schemas.UDPNIO, 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. Add a NIO (Network Input/Output) to the node.
""" """
nio = await Dynamips.instance().create_nio(node, jsonable_encoder(nio_data, exclude_unset=True)) 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) await node.slot_add_nio_binding(adapter_number, port_number, nio)
return nio.__json__() return nio.asdict()
@router.put( @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, status_code=status.HTTP_201_CREATED,
response_model=schemas.UDPNIO, 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. 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: if nio_data.filters:
nio.filters = nio_data.filters nio.filters = nio_data.filters
await node.slot_update_nio_binding(adapter_number, port_number, nio) 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) @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. 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") @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
async def start_capture( 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. Start a packet capture on the node.
""" """
@ -227,7 +240,7 @@ async def start_capture(
@router.post( @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_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. 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") @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. Stream the pcap capture file.
""" """
@ -266,18 +283,18 @@ async def get_auto_idlepc(node: Router = Depends(dep_node)) -> dict:
return {"idlepc": idlepc} return {"idlepc": idlepc}
@router.post("/{node_id}/duplicate", status_code=status.HTTP_201_CREATED) @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)): async def duplicate_router(destination_node_id: UUID, node: Router = Depends(dep_node)) -> schemas.Dynamips:
""" """
Duplicate a router. Duplicate a router.
""" """
new_node = await Dynamips.instance().duplicate_node(node.id, str(destination_node_id)) 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") @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. 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) @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() await node.reset_console()

View File

@ -20,7 +20,7 @@ API routes for Ethernet hub nodes.
import os 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.encoders import jsonable_encoder
from fastapi.responses import StreamingResponse from fastapi.responses import StreamingResponse
from uuid import UUID from uuid import UUID
@ -34,7 +34,7 @@ responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find
router = APIRouter(responses=responses) 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. Dependency to retrieve a node.
""" """
@ -50,7 +50,7 @@ def dep_node(project_id: UUID, node_id: UUID):
status_code=status.HTTP_201_CREATED, status_code=status.HTTP_201_CREATED,
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Ethernet hub node"}}, 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. 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", node_type="ethernet_hub",
ports=node_data.get("ports_mapping"), ports=node_data.get("ports_mapping"),
) )
return node.__json__() return node.asdict()
@router.get("/{node_id}", response_model=schemas.EthernetHub) @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 an Ethernet hub.
""" """
return node.__json__() return node.asdict()
@router.post("/{node_id}/duplicate", response_model=schemas.EthernetHub, status_code=status.HTTP_201_CREATED) @router.post("/{node_id}/duplicate", response_model=schemas.EthernetHub, status_code=status.HTTP_201_CREATED)
async def duplicate_ethernet_hub( async def duplicate_ethernet_hub(
destination_node_id: UUID = Body(..., embed=True), node: EthernetHub = Depends(dep_node) destination_node_id: UUID = Body(..., embed=True), node: EthernetHub = Depends(dep_node)
): ) -> schemas.EthernetHub:
""" """
Duplicate an Ethernet hub. Duplicate an Ethernet hub.
""" """
new_node = await Dynamips.instance().duplicate_node(node.id, str(destination_node_id)) 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) @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. 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: if "ports_mapping" in node_data:
node.ports_mapping = node_data["ports_mapping"] node.ports_mapping = node_data["ports_mapping"]
node.updated() node.updated()
return node.__json__() return node.asdict()
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT) @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. 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) @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. Start an Ethernet hub.
This endpoint results in no action since Ethernet hub nodes are always on. 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) @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. Stop an Ethernet hub.
This endpoint results in no action since Ethernet hub nodes are always on. 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) @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. Suspend an Ethernet hub.
This endpoint results in no action since Ethernet hub nodes are always on. 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, response_model=schemas.UDPNIO,
) )
async def create_nio( 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. Add a NIO (Network Input/Output) to the node.
The adapter number on the hub is always 0. 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)) nio = await Dynamips.instance().create_nio(node, jsonable_encoder(nio_data, exclude_unset=True))
await node.add_nio(nio, port_number) 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) @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. Delete a NIO (Network Input/Output) from the node.
The adapter number on the hub is always 0. 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") @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
async def start_capture( 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. Start a packet capture on the node.
The adapter number on the hub is always 0. The adapter number on the hub is always 0.
@ -189,7 +205,12 @@ async def start_capture(
@router.post( @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_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. Stop a packet capture on the node.
The adapter number on the hub is always 0. 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") @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. Stream the pcap capture file.
The adapter number on the hub is always 0. The adapter number on the hub is always 0.

View File

@ -20,7 +20,7 @@ API routes for Ethernet switch nodes.
import os 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.encoders import jsonable_encoder
from fastapi.responses import StreamingResponse from fastapi.responses import StreamingResponse
from uuid import UUID from uuid import UUID
@ -34,7 +34,7 @@ responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find
router = APIRouter(responses=responses) 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. Dependency to retrieve a node.
""" """
@ -50,7 +50,7 @@ def dep_node(project_id: UUID, node_id: UUID):
status_code=status.HTTP_201_CREATED, status_code=status.HTTP_201_CREATED,
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Ethernet switch node"}}, 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. 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"), ports=node_data.get("ports_mapping"),
) )
return node.__json__() return node.asdict()
@router.get("/{node_id}", response_model=schemas.EthernetSwitch) @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) @router.post("/{node_id}/duplicate", response_model=schemas.EthernetSwitch, status_code=status.HTTP_201_CREATED)
async def duplicate_ethernet_switch( 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. Duplicate an Ethernet switch.
""" """
new_node = await Dynamips.instance().duplicate_node(node.id, str(destination_node_id)) 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) @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. Update an Ethernet switch.
""" """
@ -104,11 +108,11 @@ async def update_ethernet_switch(node_data: schemas.EthernetSwitchUpdate, node:
if "console_type" in node_data: if "console_type" in node_data:
node.console_type = node_data["console_type"] node.console_type = node_data["console_type"]
node.updated() node.updated()
return node.__json__() return node.asdict()
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT) @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. 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) @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. Start an Ethernet switch.
This endpoint results in no action since Ethernet switch nodes are always on. 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) @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. Stop an Ethernet switch.
This endpoint results in no action since Ethernet switch nodes are always on. 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) @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. Suspend an Ethernet switch.
This endpoint results in no action since Ethernet switch nodes are always on. 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, response_model=schemas.UDPNIO,
) )
async def create_nio( 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)) nio = await Dynamips.instance().create_nio(node, jsonable_encoder(nio_data, exclude_unset=True))
await node.add_nio(nio, port_number) 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) @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. Delete a NIO (Network Input/Output) from the node.
The adapter number on the switch is always 0. 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") @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
async def start_capture( async def start_capture(
adapter_number: int, *,
port_number: int, adapter_number: int = Path(..., ge=0, le=0),
node_capture_data: schemas.NodeCapture, port_number: int,
node: EthernetSwitch = Depends(dep_node), node_capture_data: schemas.NodeCapture,
): node: EthernetSwitch = Depends(dep_node),
) -> dict:
""" """
Start a packet capture on the node. Start a packet capture on the node.
The adapter number on the switch is always 0. The adapter number on the switch is always 0.
@ -191,7 +205,12 @@ async def start_capture(
@router.post( @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_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. Stop a packet capture on the node.
The adapter number on the switch is always 0. 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") @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. Stream the pcap capture file.
The adapter number on the switch is always 0. The adapter number on the switch is always 0.

View File

@ -20,7 +20,7 @@ API routes for Frame Relay switch nodes.
import os 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.encoders import jsonable_encoder
from fastapi.responses import StreamingResponse from fastapi.responses import StreamingResponse
from uuid import UUID from uuid import UUID
@ -34,7 +34,7 @@ responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find
router = APIRouter(responses=responses) 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. Dependency to retrieve a node.
""" """
@ -50,7 +50,10 @@ def dep_node(project_id: UUID, node_id: UUID):
status_code=status.HTTP_201_CREATED, status_code=status.HTTP_201_CREATED,
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Frame Relay switch node"}}, 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. 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", node_type="frame_relay_switch",
mappings=node_data.get("mappings"), mappings=node_data.get("mappings"),
) )
return node.__json__() return node.asdict()
@router.get("/{node_id}", response_model=schemas.FrameRelaySwitch) @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 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) @router.post("/{node_id}/duplicate", response_model=schemas.FrameRelaySwitch, status_code=status.HTTP_201_CREATED)
async def duplicate_frame_relay_switch( 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. Duplicate a Frame Relay switch node.
""" """
new_node = await Dynamips.instance().duplicate_node(node.id, str(destination_node_id)) 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) @router.put("/{node_id}", response_model=schemas.FrameRelaySwitch)
async def update_frame_relay_switch( 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. Update an Frame Relay switch node.
""" """
@ -103,11 +108,11 @@ async def update_frame_relay_switch(
if "mappings" in node_data: if "mappings" in node_data:
node.mappings = node_data["mappings"] node.mappings = node_data["mappings"]
node.updated() node.updated()
return node.__json__() return node.asdict()
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT) @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. 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) @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. Start a Frame Relay switch node.
This endpoint results in no action since Frame Relay switch nodes are always on. 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) @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. Stop a Frame Relay switch node.
This endpoint results in no action since Frame Relay switch nodes are always on. 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) @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. Suspend a Frame Relay switch node.
This endpoint results in no action since Frame Relay switch nodes are always on. 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, response_model=schemas.UDPNIO,
) )
async def create_nio( 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. Add a NIO (Network Input/Output) to the node.
The adapter number on the switch is always 0. 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)) nio = await Dynamips.instance().create_nio(node, jsonable_encoder(nio_data, exclude_unset=True))
await node.add_nio(nio, port_number) 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) @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. Remove a NIO (Network Input/Output) from the node.
The adapter number on the switch is always 0. 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") @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
async def start_capture( async def start_capture(
adapter_number: int, *,
port_number: int, adapter_number: int = Path(..., ge=0, le=0),
node_capture_data: schemas.NodeCapture, port_number: int,
node: FrameRelaySwitch = Depends(dep_node), node_capture_data: schemas.NodeCapture,
): node: FrameRelaySwitch = Depends(dep_node),
) -> dict:
""" """
Start a packet capture on the node. Start a packet capture on the node.
The adapter number on the switch is always 0. The adapter number on the switch is always 0.
@ -194,7 +209,12 @@ async def start_capture(
@router.post( @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_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. Stop a packet capture on the node.
The adapter number on the switch is always 0. 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") @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. Stream the pcap capture file.
The adapter number on the hub is always 0. The adapter number on the hub is always 0.

View File

@ -54,7 +54,7 @@ async def get_dynamips_images() -> List[str]:
@router.post("/dynamips/images/{filename:path}", status_code=status.HTTP_204_NO_CONTENT) @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. Upload a Dynamips IOS image.
""" """
@ -64,7 +64,7 @@ async def upload_dynamips_image(filename: str, request: Request):
@router.get("/dynamips/images/{filename:path}") @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. 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) @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. Upload an IOU image.
""" """
@ -103,7 +103,7 @@ async def upload_iou_image(filename: str, request: Request):
@router.get("/iou/images/{filename:path}") @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. 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) @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() qemu_manager = Qemu.instance()
await qemu_manager.write_image(urllib.parse.unquote(filename), request.stream()) await qemu_manager.write_image(urllib.parse.unquote(filename), request.stream())
@router.get("/qemu/images/{filename:path}") @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() qemu_manager = Qemu.instance()
filename = urllib.parse.unquote(filename) filename = urllib.parse.unquote(filename)

View File

@ -35,7 +35,7 @@ responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find
router = APIRouter(responses=responses) 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. Dependency to retrieve a node.
""" """
@ -51,7 +51,7 @@ def dep_node(project_id: UUID, node_id: UUID):
status_code=status.HTTP_201_CREATED, status_code=status.HTTP_201_CREATED,
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create IOU node"}}, 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. 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"): if node_data.get("use_default_iou_values") and (name == "ram" or name == "nvram"):
continue continue
setattr(vm, name, value) setattr(vm, name, value)
return vm.__json__() return vm.asdict()
@router.get("/{node_id}", response_model=schemas.IOU) @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 an IOU node.
""" """
return node.__json__() return node.asdict()
@router.put("/{node_id}", response_model=schemas.IOU) @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. 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 # 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() await node.update_default_iou_values()
node.updated() node.updated()
return node.__json__() return node.asdict()
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT) @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. 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) @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. Duplicate an IOU node.
""" """
new_node = await IOU.instance().duplicate_node(node.id, str(destination_node_id)) 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) @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. 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) setattr(node, name, value)
await node.start() await node.start()
return node.__json__() return node.asdict()
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT) @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. 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) @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. Suspend an IOU node.
Does nothing since IOU doesn't support being suspended. 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) @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. Reload an IOU node.
""" """
@ -184,14 +187,14 @@ async def create_iou_node_nio(
port_number: int, port_number: int,
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO], nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
node: IOUVM = Depends(dep_node), node: IOUVM = Depends(dep_node),
): ) -> Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO]:
""" """
Add a NIO (Network Input/Output) to the node. Add a NIO (Network Input/Output) to the node.
""" """
nio = IOU.instance().create_nio(jsonable_encoder(nio_data, exclude_unset=True)) nio = IOU.instance().create_nio(jsonable_encoder(nio_data, exclude_unset=True))
await node.adapter_add_nio_binding(adapter_number, port_number, nio) await node.adapter_add_nio_binding(adapter_number, port_number, nio)
return nio.__json__() return nio.asdict()
@router.put( @router.put(
@ -204,7 +207,7 @@ async def update_iou_node_nio(
port_number: int, port_number: int,
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO], nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
node: IOUVM = Depends(dep_node), node: IOUVM = Depends(dep_node),
): ) -> Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO]:
""" """
Update a NIO (Network Input/Output) on the node. Update a NIO (Network Input/Output) on the node.
""" """
@ -213,11 +216,11 @@ async def update_iou_node_nio(
if nio_data.filters: if nio_data.filters:
nio.filters = nio_data.filters nio.filters = nio_data.filters
await node.adapter_update_nio_binding(adapter_number, port_number, nio) 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) @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. 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") @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
async def start_iou_node_capture( 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. Start a packet capture on the node.
""" """
@ -241,7 +247,7 @@ async def start_iou_node_capture(
@router.post( @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_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. 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") @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. 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") @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. 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) @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() await node.reset_console()

View File

@ -20,7 +20,7 @@ API routes for NAT nodes.
import os import os
from fastapi import APIRouter, Depends, status from fastapi import APIRouter, Depends, Path, status
from fastapi.encoders import jsonable_encoder from fastapi.encoders import jsonable_encoder
from fastapi.responses import StreamingResponse from fastapi.responses import StreamingResponse
from typing import Union from typing import Union
@ -35,7 +35,7 @@ responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find
router = APIRouter(responses=responses) 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. Dependency to retrieve a node.
""" """
@ -51,7 +51,7 @@ def dep_node(project_id: UUID, node_id: UUID):
status_code=status.HTTP_201_CREATED, status_code=status.HTTP_201_CREATED,
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create NAT node"}}, 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. 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", "") node.usage = node_data.get("usage", "")
return node.__json__() return node.asdict()
@router.get("/{node_id}", response_model=schemas.NAT) @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 a NAT node.
""" """
return node.__json__() return node.asdict()
@router.put("/{node_id}", response_model=schemas.NAT) @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. 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: if hasattr(node, name) and getattr(node, name) != value:
setattr(node, name, value) setattr(node, name, value)
node.updated() node.updated()
return node.__json__() return node.asdict()
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT) @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. 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) @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. 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) @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. Stop a NAT node.
This endpoint results in no action since cloud nodes cannot be stopped. 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) @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. Suspend a NAT node.
This endpoint results in no action since NAT nodes cannot be suspended. 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], response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
) )
async def create_nat_node_nio( async def create_nat_node_nio(
adapter_number: int, *,
port_number: int, adapter_number: int = Path(..., ge=0, le=0),
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO], port_number: int,
node: Nat = Depends(dep_node), 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. Add a NIO (Network Input/Output) to the node.
The adapter number on the cloud is always 0. 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)) nio = Builtin.instance().create_nio(jsonable_encoder(nio_data, exclude_unset=True))
await node.add_nio(nio, port_number) await node.add_nio(nio, port_number)
return nio.__json__() return nio.asdict()
@router.put( @router.put(
@ -158,11 +159,12 @@ async def create_nat_node_nio(
response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO], response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
) )
async def update_nat_node_nio( async def update_nat_node_nio(
adapter_number: int, *,
port_number: int, adapter_number: int = Path(..., ge=0, le=0),
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO], port_number: int,
node: Nat = Depends(dep_node), 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. Update a NIO (Network Input/Output) to the node.
The adapter number on the cloud is always 0. The adapter number on the cloud is always 0.
@ -172,11 +174,16 @@ async def update_nat_node_nio(
if nio_data.filters: if nio_data.filters:
nio.filters = nio_data.filters nio.filters = nio_data.filters
await node.update_nio(port_number, nio) 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) @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. Remove a NIO (Network Input/Output) from the node.
The adapter number on the cloud is always 0. 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") @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
async def start_nat_node_capture( 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. Start a packet capture on the node.
The adapter number on the cloud is always 0. The adapter number on the cloud is always 0.
@ -202,7 +213,12 @@ async def start_nat_node_capture(
@router.post( @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_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. Stop a packet capture on the node.
The adapter number on the cloud is always 0. 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") @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. Stream the pcap capture file.
The adapter number on the cloud is always 0. The adapter number on the cloud is always 0.

View File

@ -31,7 +31,7 @@ router = APIRouter()
@router.websocket("/notifications/ws") @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. Receive project notifications about the project from WebSocket.
""" """

View File

@ -41,7 +41,7 @@ router = APIRouter()
_notifications_listening = {} _notifications_listening = {}
def dep_project(project_id: UUID): def dep_project(project_id: UUID) -> Project:
""" """
Dependency to retrieve a project. Dependency to retrieve a project.
""" """
@ -52,17 +52,17 @@ def dep_project(project_id: UUID):
@router.get("/projects", response_model=List[schemas.Project]) @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. Get all projects opened on the compute.
""" """
pm = ProjectManager.instance() 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) @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. 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"), project_id=project_data.get("project_id"),
variables=project_data.get("variables", None), variables=project_data.get("variables", None),
) )
return project.__json__() return project.asdict()
@router.put("/projects/{project_id}", response_model=schemas.Project) @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. Update project on the compute.
""" """
await project.update(variables=project_data.variables) await project.update(variables=project_data.variables)
return project.__json__() return project.asdict()
@router.get("/projects/{project_id}", response_model=schemas.Project) @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 a project from the compute.
""" """
return project.__json__() return project.asdict()
@router.post("/projects/{project_id}/close", status_code=status.HTTP_204_NO_CONTENT) @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. 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) @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. Delete project from the compute.
""" """
@ -152,8 +155,8 @@ async def delete_compute_project(project: Project = Depends(dep_project)):
# while True: # while True:
# try: # try:
# (action, msg) = await asyncio.wait_for(queue.get(), 5) # (action, msg) = await asyncio.wait_for(queue.get(), 5)
# if hasattr(msg, "__json__"): # if hasattr(msg, "asdict"):
# msg = json.dumps({"action": action, "event": msg.__json__()}, sort_keys=True) # msg = json.dumps({"action": action, "event": msg.asdict()}, sort_keys=True)
# else: # else:
# msg = json.dumps({"action": action, "event": msg}, sort_keys=True) # msg = json.dumps({"action": action, "event": msg}, sort_keys=True)
# log.debug("Send notification: %s", msg) # 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]) @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. 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}") @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. 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) @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) path = os.path.normpath(file_path)

View File

@ -19,9 +19,8 @@ API routes for Qemu nodes.
""" """
import os 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.encoders import jsonable_encoder
from fastapi.responses import StreamingResponse from fastapi.responses import StreamingResponse
from uuid import UUID from uuid import UUID
@ -36,7 +35,7 @@ responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find
router = APIRouter(responses=responses) 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. Dependency to retrieve a node.
""" """
@ -52,7 +51,7 @@ def dep_node(project_id: UUID, node_id: UUID):
status_code=status.HTTP_201_CREATED, status_code=status.HTTP_201_CREATED,
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Qemu node"}}, 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. 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: if hasattr(vm, name) and getattr(vm, name) != value:
setattr(vm, name, value) setattr(vm, name, value)
return vm.__json__() return vm.asdict()
@router.get("/{node_id}", response_model=schemas.Qemu) @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 a Qemu node.
""" """
return node.__json__() return node.asdict()
@router.put("/{node_id}", response_model=schemas.Qemu) @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. 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: if hasattr(node, name) and getattr(node, name) != value:
await node.update_property(name, value) await node.update_property(name, value)
node.updated() node.updated()
return node.__json__() return node.asdict()
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT) @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. 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) @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. Duplicate a Qemu node.
""" """
new_node = await Qemu.instance().duplicate_node(node.id, str(destination_node_id)) 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) @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) await node.resize_disk(node_data.drive_name, node_data.extend)
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT) @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. 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) @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. 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) @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. 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) @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. 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) @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. Resume a Qemu node.
""" """
@ -187,8 +189,12 @@ async def resume_qemu_node(node: QemuVM = Depends(dep_node)):
response_model=schemas.UDPNIO, response_model=schemas.UDPNIO,
) )
async def create_qemu_node_nio( 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. Add a NIO (Network Input/Output) to the node.
The port number on the Qemu node is always 0. 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)) nio = Qemu.instance().create_nio(jsonable_encoder(nio_data, exclude_unset=True))
await node.adapter_add_nio_binding(adapter_number, nio) await node.adapter_add_nio_binding(adapter_number, nio)
return nio.__json__() return nio.asdict()
@router.put( @router.put(
@ -205,8 +211,12 @@ async def create_qemu_node_nio(
response_model=schemas.UDPNIO, response_model=schemas.UDPNIO,
) )
async def update_qemu_node_nio( 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. Update a NIO (Network Input/Output) on the node.
The port number on the Qemu node is always 0. The port number on the Qemu node is always 0.
@ -218,11 +228,15 @@ async def update_qemu_node_nio(
if nio_data.suspend: if nio_data.suspend:
nio.suspend = nio_data.suspend nio.suspend = nio_data.suspend
await node.adapter_update_nio_binding(adapter_number, nio) 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) @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. Delete a NIO (Network Input/Output) from the node.
The port number on the Qemu node is always 0. 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") @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
async def start_qemu_node_capture( 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. Start a packet capture on the node.
The port number on the Qemu node is always 0. The port number on the Qemu node is always 0.
@ -248,7 +266,11 @@ async def start_qemu_node_capture(
@router.post( @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_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. Stop a packet capture on the node.
The port number on the Qemu node is always 0. 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") @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. Stream the pcap capture file.
The port number on the Qemu node is always 0. 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") @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. 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) @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() await node.reset_console()

View File

@ -20,7 +20,7 @@ API routes for VirtualBox nodes.
import os 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.encoders import jsonable_encoder
from fastapi.responses import StreamingResponse from fastapi.responses import StreamingResponse
from uuid import UUID from uuid import UUID
@ -28,7 +28,6 @@ from uuid import UUID
from gns3server import schemas from gns3server import schemas
from gns3server.compute.virtualbox import VirtualBox from gns3server.compute.virtualbox import VirtualBox
from gns3server.compute.virtualbox.virtualbox_error import VirtualBoxError from gns3server.compute.virtualbox.virtualbox_error import VirtualBoxError
from gns3server.compute.project_manager import ProjectManager
from gns3server.compute.virtualbox.virtualbox_vm import VirtualBoxVM from gns3server.compute.virtualbox.virtualbox_vm import VirtualBoxVM
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or VirtualBox node"}} 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) 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. Dependency to retrieve a node.
""" """
@ -52,7 +51,7 @@ def dep_node(project_id: UUID, node_id: UUID):
status_code=status.HTTP_201_CREATED, status_code=status.HTTP_201_CREATED,
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create VirtualBox node"}}, 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. 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: if hasattr(vm, name) and getattr(vm, name) != value:
setattr(vm, name, value) setattr(vm, name, value)
return vm.__json__() return vm.asdict()
@router.get("/{node_id}", response_model=schemas.VirtualBox) @router.get("/{node_id}", response_model=schemas.VirtualBox)
def get_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)): def get_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> schemas.VirtualBox:
""" """
Return a VirtualBox node. Return a VirtualBox node.
""" """
return node.__json__() return node.asdict()
@router.put("/{node_id}", response_model=schemas.VirtualBox) @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. Update a VirtualBox node.
""" """
@ -131,11 +133,11 @@ async def update_virtualbox_node(node_data: schemas.VirtualBoxUpdate, node: Virt
setattr(node, name, value) setattr(node, name, value)
node.updated() node.updated()
return node.__json__() return node.asdict()
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT) @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. 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) @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. 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() await node.start()
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT) @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. 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) @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. 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) @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. 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) @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. Reload a VirtualBox node.
""" """
@ -199,8 +196,12 @@ async def reload_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)):
response_model=schemas.UDPNIO, response_model=schemas.UDPNIO,
) )
async def create_virtualbox_node_nio( 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. Add a NIO (Network Input/Output) to the node.
The port number on the VirtualBox node is always 0. 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)) nio = VirtualBox.instance().create_nio(jsonable_encoder(nio_data, exclude_unset=True))
await node.adapter_add_nio_binding(adapter_number, nio) await node.adapter_add_nio_binding(adapter_number, nio)
return nio.__json__() return nio.asdict()
@router.put( @router.put(
@ -217,8 +218,12 @@ async def create_virtualbox_node_nio(
response_model=schemas.UDPNIO, response_model=schemas.UDPNIO,
) )
async def update_virtualbox_node_nio( 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. Update a NIO (Network Input/Output) on the node.
The port number on the VirtualBox node is always 0. The port number on the VirtualBox node is always 0.
@ -230,11 +235,15 @@ async def update_virtualbox_node_nio(
if nio_data.suspend: if nio_data.suspend:
nio.suspend = nio_data.suspend nio.suspend = nio_data.suspend
await node.adapter_update_nio_binding(adapter_number, nio) 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) @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. Delete a NIO (Network Input/Output) from the node.
The port number on the VirtualBox node is always 0. 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") @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
async def start_virtualbox_node_capture( async def start_virtualbox_node_capture(
adapter_number: int, *,
port_number: int, adapter_number: int,
node_capture_data: schemas.NodeCapture, port_number: int = Path(..., ge=0, le=0),
node: VirtualBoxVM = Depends(dep_node), node_capture_data: schemas.NodeCapture,
): node: VirtualBoxVM = Depends(dep_node),
) -> dict:
""" """
Start a packet capture on the node. Start a packet capture on the node.
The port number on the VirtualBox node is always 0. The port number on the VirtualBox node is always 0.
@ -263,7 +273,11 @@ async def start_virtualbox_node_capture(
@router.post( @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_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. Stop a packet capture on the node.
The port number on the VirtualBox node is always 0. 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") @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. Stream the pcap capture file.
The port number on the VirtualBox node is always 0. 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") @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. 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) @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() await node.reset_console()

View File

@ -20,7 +20,7 @@ API routes for VMware nodes.
import os 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.encoders import jsonable_encoder
from fastapi.responses import StreamingResponse from fastapi.responses import StreamingResponse
from uuid import UUID from uuid import UUID
@ -35,7 +35,7 @@ responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find
router = APIRouter(responses=responses) 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. Dependency to retrieve a node.
""" """
@ -51,7 +51,7 @@ def dep_node(project_id: UUID, node_id: UUID):
status_code=status.HTTP_201_CREATED, status_code=status.HTTP_201_CREATED,
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create VMware node"}}, 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. 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: if hasattr(vm, name) and getattr(vm, name) != value:
setattr(vm, name, value) setattr(vm, name, value)
return vm.__json__() return vm.asdict()
@router.get("/{node_id}", response_model=schemas.VMware) @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 a VMware node.
""" """
return node.__json__() return node.asdict()
@router.put("/{node_id}", response_model=schemas.VMware) @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. Update a VMware node.
""" """
@ -99,11 +99,11 @@ def update_vmware_node(node_data: schemas.VMwareUpdate, node: VMwareVM = Depends
setattr(node, name, value) setattr(node, name, value)
node.updated() node.updated()
return node.__json__() return node.asdict()
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT) @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. 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) @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. 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() await node.start()
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT) @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. 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) @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. 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) @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. 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) @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. Reload a VMware node.
""" """
@ -167,8 +162,12 @@ async def reload_vmware_node(node: VMwareVM = Depends(dep_node)):
response_model=schemas.UDPNIO, response_model=schemas.UDPNIO,
) )
async def create_vmware_node_nio( 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. Add a NIO (Network Input/Output) to the node.
The port number on the VMware node is always 0. 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)) nio = VMware.instance().create_nio(jsonable_encoder(nio_data, exclude_unset=True))
await node.adapter_add_nio_binding(adapter_number, nio) await node.adapter_add_nio_binding(adapter_number, nio)
return nio.__json__() return nio.asdict()
@router.put( @router.put(
@ -185,8 +184,12 @@ async def create_vmware_node_nio(
response_model=schemas.UDPNIO, response_model=schemas.UDPNIO,
) )
async def update_vmware_node_nio( 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. Update a NIO (Network Input/Output) on the node.
The port number on the VMware node is always 0. The port number on the VMware node is always 0.
@ -196,11 +199,15 @@ async def update_vmware_node_nio(
if nio_data.filters: if nio_data.filters:
nio.filters = nio_data.filters nio.filters = nio_data.filters
await node.adapter_update_nio_binding(adapter_number, nio) 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) @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. Delete a NIO (Network Input/Output) from the node.
The port number on the VMware node is always 0. 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") @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
async def start_vmware_node_capture( 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. Start a packet capture on the node.
The port number on the VMware node is always 0. The port number on the VMware node is always 0.
@ -226,7 +237,11 @@ async def start_vmware_node_capture(
@router.post( @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_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. Stop a packet capture on the node.
The port number on the VMware node is always 0. 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") @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. Stream the pcap capture file.
The port number on the VMware node is always 0. 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") @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. 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) @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() await node.reset_console()

View File

@ -20,7 +20,7 @@ API routes for VPCS nodes.
import os 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.encoders import jsonable_encoder
from fastapi.responses import StreamingResponse from fastapi.responses import StreamingResponse
from uuid import UUID from uuid import UUID
@ -34,7 +34,7 @@ responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find
router = APIRouter(responses=responses) 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. Dependency to retrieve a node.
""" """
@ -50,7 +50,7 @@ def dep_node(project_id: UUID, node_id: UUID):
status_code=status.HTTP_201_CREATED, status_code=status.HTTP_201_CREATED,
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create VMware node"}}, 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. 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"), startup_script=node_data.get("startup_script"),
) )
return vm.__json__() return vm.asdict()
@router.get("/{node_id}", response_model=schemas.VPCS) @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 a VPCS node.
""" """
return node.__json__() return node.asdict()
@router.put("/{node_id}", response_model=schemas.VPCS) @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. 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 = node_data.get("console", node.console)
node.console_type = node_data.get("console_type", node.console_type) node.console_type = node_data.get("console_type", node.console_type)
node.updated() node.updated()
return node.__json__() return node.asdict()
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT) @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. 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) @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. Duplicate a VPCS node.
""" """
new_node = await VPCS.instance().duplicate_node(node.id, str(destination_node_id)) 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) @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. 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) @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. 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) @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. Suspend a VPCS node.
Does nothing, suspend is not supported by VPCS. 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) @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. Reload a VPCS node.
""" """
@ -154,8 +156,12 @@ async def reload_vpcs_node(node: VPCSVM = Depends(dep_node)):
response_model=schemas.UDPNIO, response_model=schemas.UDPNIO,
) )
async def create_vpcs_node_nio( 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. Add a NIO (Network Input/Output) to the node.
The adapter number on the VPCS node is always 0. 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)) nio = VPCS.instance().create_nio(jsonable_encoder(nio_data, exclude_unset=True))
await node.port_add_nio_binding(port_number, nio) await node.port_add_nio_binding(port_number, nio)
return nio.__json__() return nio.asdict()
@router.put( @router.put(
@ -172,8 +178,12 @@ async def create_vpcs_node_nio(
response_model=schemas.UDPNIO, response_model=schemas.UDPNIO,
) )
async def update_vpcs_node_nio( 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. Update a NIO (Network Input/Output) on the node.
The adapter number on the VPCS node is always 0. The adapter number on the VPCS node is always 0.
@ -183,11 +193,16 @@ async def update_vpcs_node_nio(
if nio_data.filters: if nio_data.filters:
nio.filters = nio_data.filters nio.filters = nio_data.filters
await node.port_update_nio_binding(port_number, nio) 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) @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. Delete a NIO (Network Input/Output) from the node.
The adapter number on the VPCS node is always 0. 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") @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
async def start_vpcs_node_capture( 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. Start a packet capture on the node.
The adapter number on the VPCS node is always 0. The adapter number on the VPCS node is always 0.
@ -213,7 +232,12 @@ async def start_vpcs_node_capture(
@router.post( @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_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. Stop a packet capture on the node.
The adapter number on the VPCS node is always 0. 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) @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() await node.reset_console()
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream") @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. Stream the pcap capture file.
The adapter number on the VPCS node is always 0. 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") @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. Console WebSocket.
""" """

View File

@ -14,7 +14,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from fastapi import APIRouter from fastapi import APIRouter, Depends
from . import controller from . import controller
from . import appliances from . import appliances
@ -30,17 +30,80 @@ from . import symbols
from . import templates from . import templates
from . import users from . import users
from .dependencies.authentication import get_current_active_user
router = APIRouter() router = APIRouter()
router.include_router(controller.router, tags=["Controller"]) router.include_router(controller.router, tags=["Controller"])
router.include_router(users.router, prefix="/users", tags=["Users"]) 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(
router.include_router(drawings.router, prefix="/projects/{project_id}/drawings", tags=["Drawings"]) appliances.router,
router.include_router(gns3vm.router, prefix="/gns3vm", tags=["GNS3 VM"]) dependencies=[Depends(get_current_active_user)],
router.include_router(links.router, prefix="/projects/{project_id}/links", tags=["Links"]) prefix="/appliances",
router.include_router(nodes.router, prefix="/projects/{project_id}/nodes", tags=["Nodes"]) tags=["Appliances"]
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(
router.include_router(symbols.router, prefix="/symbols", tags=["Symbols"]) computes.router,
router.include_router(templates.router, tags=["Templates"]) 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"]
)

View File

@ -19,13 +19,13 @@ API routes for appliances.
""" """
from fastapi import APIRouter from fastapi import APIRouter
from typing import Optional from typing import Optional, List
router = APIRouter() router = APIRouter()
@router.get("") @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. 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: if update:
await controller.appliance_manager.download_appliances() await controller.appliance_manager.download_appliances()
controller.appliance_manager.load_appliances(symbol_theme=symbol_theme) 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()]

View File

@ -93,7 +93,7 @@ async def update_compute(
@router.delete("/{compute_id}", status_code=status.HTTP_204_NO_CONTENT) @router.delete("/{compute_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_compute( async def delete_compute(
compute_id: Union[str, UUID], computes_repo: ComputesRepository = Depends(get_repository(ComputesRepository)) compute_id: Union[str, UUID], computes_repo: ComputesRepository = Depends(get_repository(ComputesRepository))
): ) -> None:
""" """
Delete a compute from the controller. Delete a compute from the controller.
""" """
@ -102,7 +102,7 @@ async def delete_compute(
@router.get("/{compute_id}/{emulator}/images") @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. 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}") @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. Forward a GET request to a compute.
Read the full compute API documentation for available routes. 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}") @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. Forward a POST request to a compute.
Read the full compute API documentation for available routes. 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}") @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. Forward a PUT request to a compute.
Read the full compute API documentation for available routes. 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") @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. Find a suitable Idle-PC value for a given IOS image. This may take a few minutes.
""" """

View File

@ -18,8 +18,9 @@ import asyncio
import signal import signal
import os import os
from fastapi import APIRouter, status from fastapi import APIRouter, Depends, status
from fastapi.encoders import jsonable_encoder from fastapi.encoders import jsonable_encoder
from typing import List
from gns3server.config import Config from gns3server.config import Config
from gns3server.controller import Controller from gns3server.controller import Controller
@ -27,6 +28,7 @@ from gns3server.version import __version__
from gns3server.controller.controller_error import ControllerError, ControllerForbiddenError from gns3server.controller.controller_error import ControllerError, ControllerForbiddenError
from gns3server import schemas from gns3server import schemas
from .dependencies.authentication import get_current_active_user
import logging import logging
@ -35,12 +37,43 @@ log = logging.getLogger(__name__)
router = APIRouter() 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( @router.post(
"/shutdown", "/shutdown",
dependencies=[Depends(get_current_active_user)],
status_code=status.HTTP_204_NO_CONTENT, status_code=status.HTTP_204_NO_CONTENT,
responses={403: {"model": schemas.ErrorMessage, "description": "Server shutdown not allowed"}}, responses={403: {"model": schemas.ErrorMessage, "description": "Server shutdown not allowed"}},
) )
async def shutdown(): async def shutdown() -> None:
""" """
Shutdown the local server Shutdown the local server
""" """
@ -70,39 +103,12 @@ async def shutdown():
os.kill(os.getpid(), signal.SIGTERM) os.kill(os.getpid(), signal.SIGTERM)
@router.get("/version", response_model=schemas.Version) @router.get(
def get_version(): "/iou_license",
""" dependencies=[Depends(get_current_active_user)],
Return the server version number. response_model=schemas.IOULicense
"""
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): def get_iou_license() -> schemas.IOULicense:
"""
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():
""" """
Return the IOU license settings Return the IOU license settings
""" """
@ -110,8 +116,13 @@ def get_iou_license():
return Controller.instance().iou_license return Controller.instance().iou_license
@router.put("/iou_license", status_code=status.HTTP_201_CREATED, response_model=schemas.IOULicense) @router.put(
async def update_iou_license(iou_license: schemas.IOULicense): "/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. Update the IOU license settings.
""" """
@ -123,8 +134,8 @@ async def update_iou_license(iou_license: schemas.IOULicense):
return current_iou_license return current_iou_license
@router.get("/statistics") @router.get("/statistics", dependencies=[Depends(get_current_active_user)])
async def statistics(): async def statistics() -> List[dict]:
""" """
Return server statistics. Return server statistics.
""" """

View File

@ -24,7 +24,7 @@ from gns3server.services import auth_service
from .database import get_repository 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( 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: 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: if not current_user.is_active:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, status_code=status.HTTP_401_UNAUTHORIZED,

View File

@ -32,39 +32,39 @@ router = APIRouter(responses=responses)
@router.get("", response_model=List[schemas.Drawing], response_model_exclude_unset=True) @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. Return the list of all drawings for a given project.
""" """
project = await Controller.instance().get_loaded_project(str(project_id)) 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) @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. Create a new drawing.
""" """
project = await Controller.instance().get_loaded_project(str(project_id)) project = await Controller.instance().get_loaded_project(str(project_id))
drawing = await project.add_drawing(**jsonable_encoder(drawing_data, exclude_unset=True)) 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) @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. Return a drawing.
""" """
project = await Controller.instance().get_loaded_project(str(project_id)) project = await Controller.instance().get_loaded_project(str(project_id))
drawing = project.get_drawing(str(drawing_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) @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. 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)) project = await Controller.instance().get_loaded_project(str(project_id))
drawing = project.get_drawing(str(drawing_id)) drawing = project.get_drawing(str(drawing_id))
await drawing.update(**jsonable_encoder(drawing_data, exclude_unset=True)) 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) @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. Delete a drawing.
""" """

View File

@ -21,6 +21,7 @@ API routes for managing the GNS3 VM.
from fastapi import APIRouter from fastapi import APIRouter
from fastapi.encoders import jsonable_encoder from fastapi.encoders import jsonable_encoder
from typing import List
from gns3server.controller import Controller from gns3server.controller import Controller
from gns3server import schemas from gns3server import schemas
@ -29,7 +30,7 @@ router = APIRouter()
@router.get("/engines") @router.get("/engines")
async def get_engines(): async def get_engines() -> List[dict]:
""" """
Return the list of supported engines for the GNS3VM. Return the list of supported engines for the GNS3VM.
""" """
@ -39,7 +40,7 @@ async def get_engines():
@router.get("/engines/{engine}/vms") @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. 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) @router.get("", response_model=schemas.GNS3VM)
async def get_gns3vm_settings(): async def get_gns3vm_settings() -> schemas.GNS3VM:
""" """
Return the GNS3 VM settings. Return the GNS3 VM settings.
""" """
return Controller.instance().gns3vm.__json__() return Controller.instance().gns3vm.asdict()
@router.put("", response_model=schemas.GNS3VM, response_model_exclude_unset=True) @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. Update the GNS3 VM settings.
""" """
@ -67,4 +68,4 @@ async def update_gns3vm_settings(gns3vm_data: schemas.GNS3VM):
gns3_vm = controller.gns3vm gns3_vm = controller.gns3vm
await gns3_vm.update_settings(jsonable_encoder(gns3vm_data, exclude_unset=True)) await gns3_vm.update_settings(jsonable_encoder(gns3vm_data, exclude_unset=True))
controller.save() controller.save()
return gns3_vm.__json__() return gns3_vm.asdict()

View File

@ -42,7 +42,7 @@ responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find
router = APIRouter(responses=responses) 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. 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) @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. Return all links for a given project.
""" """
project = await Controller.instance().get_loaded_project(str(project_id)) 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( @router.post(
@ -71,7 +71,7 @@ async def get_links(project_id: UUID):
409: {"model": schemas.ErrorMessage, "description": "Could not create link"}, 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. Create a new link.
""" """
@ -94,11 +94,11 @@ async def create_link(project_id: UUID, link_data: schemas.Link):
except ControllerError as e: except ControllerError as e:
await project.delete_link(link.id) await project.delete_link(link.id)
raise e raise e
return link.__json__() return link.asdict()
@router.get("/{link_id}/available_filters") @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. 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) @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 a link.
""" """
return link.__json__() return link.asdict()
@router.put("/{link_id}", response_model=schemas.Link, response_model_exclude_unset=True) @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. 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"]) await link.update_suspend(link_data["suspend"])
if "nodes" in link_data: if "nodes" in link_data:
await link.update_nodes(link_data["nodes"]) await link.update_nodes(link_data["nodes"])
return link.__json__() return link.asdict()
@router.delete("/{link_id}", status_code=status.HTTP_204_NO_CONTENT) @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. 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) @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. Reset a link.
""" """
await link.reset() 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) @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. 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"), data_link_type=capture_data.get("data_link_type", "DLT_EN10MB"),
capture_file_name=capture_data.get("capture_file_name"), 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) @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. 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") @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. Stream the PCAP capture file from compute.
""" """

View File

@ -81,7 +81,7 @@ responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find
router = APIRouter(route_class=NodeConcurrency, responses=responses) 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. Dependency to retrieve a project.
""" """
@ -90,7 +90,7 @@ async def dep_project(project_id: UUID):
return project 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. 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"}, 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. 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)) compute = controller.get_compute(str(node_data.compute_id))
node_data = jsonable_encoder(node_data, exclude_unset=True) 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) 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) @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 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) @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. 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) @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. 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) @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. 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) @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. 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) @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 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) @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. 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) node_data.pop("compute_id", None)
await node.update(**node_data) await node.update(**node_data)
return node.__json__() return node.asdict()
@router.delete( @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, status_code=status.HTTP_204_NO_CONTENT,
responses={**responses, 409: {"model": schemas.ErrorMessage, "description": "Cannot delete node"}}, 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. 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) @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. Duplicate a node.
""" """
new_node = await node.project.duplicate_node(node, duplicate_data.x, duplicate_data.y, duplicate_data.z) 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) @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. 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) @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. 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) @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. 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) @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. 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) @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. Return all the links connected to a node.
""" """
links = [] links = []
for link in node.links: for link in node.links:
links.append(link.__json__()) links.append(link.asdict())
return links return links
@router.get("/{node_id}/dynamips/auto_idlepc") @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 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") @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 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() return await node.dynamips_idlepc_proposals()
@router.post("/{node_id}/resize_disk", status_code=status.HTTP_201_CREATED) @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)): async def resize_disk(resize_data: dict, node: Node = Depends(dep_node)) -> None:
""" """
Resize a disk image. 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}") @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 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) @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. 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") @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. 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) @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. 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) @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) await node.post("/console/reset") # , request.json)

View File

@ -32,7 +32,7 @@ router = APIRouter()
@router.get("") @router.get("")
async def http_notification(): async def http_notification() -> StreamingResponse:
""" """
Receive controller notifications about the controller from HTTP stream. Receive controller notifications about the controller from HTTP stream.
""" """
@ -47,7 +47,7 @@ async def http_notification():
@router.websocket("/ws") @router.websocket("/ws")
async def notification_ws(websocket: WebSocket): async def notification_ws(websocket: WebSocket) -> None:
""" """
Receive project notifications about the controller from WebSocket. Receive project notifications about the controller from WebSocket.
""" """

View File

@ -51,7 +51,7 @@ responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find
router = APIRouter(responses=responses) router = APIRouter(responses=responses)
def dep_project(project_id: UUID): def dep_project(project_id: UUID) -> Project:
""" """
Dependency to retrieve a 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) @router.get("", response_model=List[schemas.Project], response_model_exclude_unset=True)
def get_projects(): def get_projects() -> List[schemas.Project]:
""" """
Return all projects. Return all projects.
""" """
controller = Controller.instance() controller = Controller.instance()
return [p.__json__() for p in controller.projects.values()] return [p.asdict() for p in controller.projects.values()]
@router.post( @router.post(
@ -80,37 +80,40 @@ def get_projects():
response_model_exclude_unset=True, response_model_exclude_unset=True,
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create project"}}, 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. Create a new project.
""" """
controller = Controller.instance() controller = Controller.instance()
project = await controller.add_project(**jsonable_encoder(project_data, exclude_unset=True)) 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) @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 a project.
""" """
return project.__json__() return project.asdict()
@router.put("/{project_id}", response_model=schemas.Project, response_model_exclude_unset=True) @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. Update a project.
""" """
await project.update(**jsonable_encoder(project_data, exclude_unset=True)) 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) @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. Delete a project.
""" """
@ -121,7 +124,7 @@ async def delete_project(project: Project = Depends(dep_project)):
@router.get("/{project_id}/stats") @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. Return a project statistics.
""" """
@ -134,7 +137,7 @@ def get_project_stats(project: Project = Depends(dep_project)):
status_code=status.HTTP_204_NO_CONTENT, status_code=status.HTTP_204_NO_CONTENT,
responses={**responses, 409: {"model": schemas.ErrorMessage, "description": "Could not close project"}}, 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. Close a project.
""" """
@ -148,13 +151,13 @@ async def close_project(project: Project = Depends(dep_project)):
response_model=schemas.Project, response_model=schemas.Project,
responses={**responses, 409: {"model": schemas.ErrorMessage, "description": "Could not open 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. Open a project.
""" """
await project.open() await project.open()
return project.__json__() return project.asdict()
@router.post( @router.post(
@ -163,7 +166,7 @@ async def open_project(project: Project = Depends(dep_project)):
response_model=schemas.Project, response_model=schemas.Project,
responses={**responses, 409: {"model": schemas.ErrorMessage, "description": "Could not load 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). Load a project (local server only).
""" """
@ -176,11 +179,11 @@ async def load_project(path: str = Body(..., embed=True)):
project = await controller.load_project( project = await controller.load_project(
dot_gns3_file, dot_gns3_file,
) )
return project.__json__() return project.asdict()
@router.get("/{project_id}/notifications") @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. 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") @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. Receive project notifications about the controller from WebSocket.
""" """
@ -248,7 +251,7 @@ async def export_project(
include_images: bool = False, include_images: bool = False,
reset_mac_addresses: bool = False, reset_mac_addresses: bool = False,
compression: str = "zip", compression: str = "zip",
): ) -> StreamingResponse:
""" """
Export a project as a portable archive. 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) @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. 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") log.info(f"Project '{project.name}' imported in {time.time() - begin:.4f} seconds")
except OSError as e: except OSError as e:
raise ControllerError(f"Could not import the project: {e}") raise ControllerError(f"Could not import the project: {e}")
return project.__json__() return project.asdict()
@router.post( @router.post(
@ -332,7 +340,10 @@ async def import_project(project_id: UUID, request: Request, path: Optional[Path
response_model=schemas.Project, response_model=schemas.Project,
responses={**responses, 409: {"model": schemas.ErrorMessage, "description": "Could not duplicate 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. Duplicate a project.
""" """
@ -348,11 +359,11 @@ async def duplicate_project(project_data: schemas.ProjectDuplicate, project: Pro
new_project = await project.duplicate( new_project = await project.duplicate(
name=project_data.name, location=location, reset_mac_addresses=reset_mac_addresses 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}") @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. 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) @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. Write a file from a project.
""" """

View File

@ -36,7 +36,7 @@ responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find
router = APIRouter(responses=responses) router = APIRouter(responses=responses)
def dep_project(project_id: UUID): def dep_project(project_id: UUID) -> Project:
""" """
Dependency to retrieve a 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) @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. Create a new snapshot of a project.
""" """
snapshot = await project.snapshot(snapshot_data.name) 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) @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. Return all snapshots belonging to a given project.
""" """
snapshots = [s for s in project.snapshots.values()] 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) @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. 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) @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. Restore a snapshot.
""" """
snapshot = project.get_snapshot(str(snapshot_id)) snapshot = project.get_snapshot(str(snapshot_id))
project = await snapshot.restore() project = await snapshot.restore()
return project.__json__() return project.asdict()

View File

@ -23,6 +23,7 @@ import os
from fastapi import APIRouter, Request, status from fastapi import APIRouter, Request, status
from fastapi.responses import FileResponse from fastapi.responses import FileResponse
from typing import List
from gns3server.controller import Controller from gns3server.controller import Controller
from gns3server import schemas from gns3server import schemas
@ -37,7 +38,7 @@ router = APIRouter()
@router.get("") @router.get("")
def get_symbols(): def get_symbols() -> List[str]:
controller = Controller.instance() controller = Controller.instance()
return controller.symbols.list() return controller.symbols.list()
@ -46,7 +47,7 @@ def get_symbols():
@router.get( @router.get(
"/{symbol_id:path}/raw", responses={404: {"model": schemas.ErrorMessage, "description": "Could not find symbol"}} "/{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. Download a symbol file.
""" """
@ -63,7 +64,7 @@ async def get_symbol(symbol_id: str):
"/{symbol_id:path}/dimensions", "/{symbol_id:path}/dimensions",
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find symbol"}}, 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. 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) @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. Upload a symbol file.
""" """
@ -97,7 +98,7 @@ async def upload_symbol(symbol_id: str, request: Request):
@router.get("/default_symbols") @router.get("/default_symbols")
def get_default_symbols(): def get_default_symbols() -> dict:
""" """
Return all default symbols. Return all default symbols.
""" """

View File

@ -44,7 +44,7 @@ router = APIRouter(responses=responses)
async def create_template( async def create_template(
template_create: schemas.TemplateCreate, template_create: schemas.TemplateCreate,
templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository)), templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository)),
) -> dict: ) -> schemas.Template:
""" """
Create a new template. Create a new template.
""" """
@ -58,7 +58,7 @@ async def get_template(
request: Request, request: Request,
response: Response, response: Response,
templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository)), templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository)),
) -> dict: ) -> schemas.Template:
""" """
Return a template. Return a template.
""" """
@ -79,7 +79,7 @@ async def update_template(
template_id: UUID, template_id: UUID,
template_update: schemas.TemplateUpdate, template_update: schemas.TemplateUpdate,
templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository)), templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository)),
) -> dict: ) -> schemas.Template:
""" """
Update a 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) @router.get("/templates", response_model=List[schemas.Template], response_model_exclude_unset=True)
async def get_templates( async def get_templates(
templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository)), templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository)),
) -> List[dict]: ) -> List[schemas.Template]:
""" """
Return all templates. 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) @router.post("/templates/{template_id}/duplicate", response_model=schemas.Template, status_code=status.HTTP_201_CREATED)
async def duplicate_template( async def duplicate_template(
template_id: UUID, templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository)) template_id: UUID, templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository))
) -> dict: ) -> schemas.Template:
""" """
Duplicate a template. Duplicate a template.
""" """
@ -145,4 +145,4 @@ async def create_node_from_template(
node = await project.add_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 template, x=template_usage.x, y=template_usage.y, compute_id=template_usage.compute_id
) )
return node.__json__() return node.asdict()

View File

@ -28,7 +28,7 @@ from gns3server import schemas
from gns3server.controller.controller_error import ( from gns3server.controller.controller_error import (
ControllerBadRequestError, ControllerBadRequestError,
ControllerNotFoundError, ControllerNotFoundError,
ControllerUnauthorizedError, ControllerForbiddenError,
) )
from gns3server.db.repositories.users import UsersRepository from gns3server.db.repositories.users import UsersRepository
@ -44,8 +44,54 @@ log = logging.getLogger(__name__)
router = APIRouter() router = APIRouter()
@router.get("", response_model=List[schemas.User]) @router.post("/login", response_model=schemas.Token)
async def get_users(users_repo: UsersRepository = Depends(get_repository(UsersRepository))) -> List[schemas.User]: 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. Get all users.
""" """
@ -53,9 +99,15 @@ async def get_users(users_repo: UsersRepository = Depends(get_repository(UsersRe
return await users_repo.get_users() return await users_repo.get_users()
@router.post("", response_model=schemas.User, status_code=status.HTTP_201_CREATED) @router.post(
"",
response_model=schemas.User,
dependencies=[Depends(get_current_active_user)],
status_code=status.HTTP_201_CREATED
)
async def create_user( 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: ) -> schemas.User:
""" """
Create a new user. Create a new user.
@ -70,9 +122,10 @@ async def create_user(
return await users_repo.create_user(user_create) 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( 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: ) -> schemas.User:
""" """
Get an user. Get an user.
@ -84,11 +137,11 @@ async def get_user(
return 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( async def update_user(
user_id: UUID, user_id: UUID,
user_update: schemas.UserUpdate, user_update: schemas.UserUpdate,
users_repo: UsersRepository = Depends(get_repository(UsersRepository)), users_repo: UsersRepository = Depends(get_repository(UsersRepository))
) -> schemas.User: ) -> schemas.User:
""" """
Update an user. Update an user.
@ -110,36 +163,15 @@ async def delete_user(
Delete an user. Delete an user.
""" """
if current_user.is_superuser: if current_user.is_superadmin:
raise ControllerUnauthorizedError("The super user cannot be deleted") raise ControllerForbiddenError("The super admin cannot be deleted")
success = await users_repo.delete_user(user_id) success = await users_repo.delete_user(user_id)
if not success: if not success:
raise ControllerNotFoundError(f"User '{user_id}' not found") raise ControllerNotFoundError(f"User '{user_id}' not found")
@router.post("/login", response_model=schemas.Token) @router.get("/me/", response_model=schemas.User)
async def login(
users_repo: UsersRepository = Depends(get_repository(UsersRepository)),
form_data: OAuth2PasswordRequestForm = Depends(),
) -> schemas.Token:
"""
User login.
"""
user = await users_repo.authenticate_user(username=form_data.username, password=form_data.password)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Authentication was unsuccessful.",
headers={"WWW-Authenticate": "Bearer"},
)
token = schemas.Token(access_token=auth_service.create_access_token(user.username), token_type="bearer")
return token
@router.get("/users/me/", response_model=schemas.User)
async def get_current_active_user(current_user: schemas.User = Depends(get_current_active_user)) -> schemas.User: async def get_current_active_user(current_user: schemas.User = Depends(get_current_active_user)) -> schemas.User:
""" """
Get the current active user. Get the current active user.

View File

@ -25,7 +25,7 @@ from fastapi import FastAPI, Request
from starlette.exceptions import HTTPException as StarletteHTTPException from starlette.exceptions import HTTPException as StarletteHTTPException
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from uvicorn.main import Server as UvicornServer
from gns3server.controller.controller_error import ( from gns3server.controller.controller_error import (
ControllerError, ControllerError,
@ -82,6 +82,18 @@ def get_application() -> FastAPI:
app = get_application() 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) @app.exception_handler(ControllerError)
async def controller_error_handler(request: Request, exc: ControllerError): async def controller_error_handler(request: Request, exc: ControllerError):

View File

@ -20,25 +20,25 @@ import struct
import stat import stat
import asyncio import asyncio
import aiofiles import aiofiles
import socket import socket
import shutil import shutil
import re import re
import logging 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__) 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 gns3server.utils.interfaces import is_interface_up
from uuid import UUID, uuid4
from typing import Type
from ..config import Config from ..config import Config
from ..utils.asyncio import wait_run_in_executor from ..utils.asyncio import wait_run_in_executor
from ..utils import force_unix_path from ..utils import force_unix_path
from .project_manager import ProjectManager from .project_manager import ProjectManager
from .port_manager import PortManager from .port_manager import PortManager
from .base_node import BaseNode
from .nios.nio_udp import NIOUDP from .nios.nio_udp import NIOUDP
from .nios.nio_tap import NIOTAP from .nios.nio_tap import NIOTAP
@ -150,7 +150,7 @@ class BaseManager:
BaseManager._instance = None BaseManager._instance = None
log.debug(f"Module {self.module_name} unloaded") 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. Returns a Node instance.

View File

@ -77,7 +77,7 @@ class Cloud(BaseNode):
def _interfaces(self): def _interfaces(self):
return gns3server.utils.interfaces.interfaces() return gns3server.utils.interfaces.interfaces()
def __json__(self): def asdict(self):
host_interfaces = [] host_interfaces = []
network_interfaces = gns3server.utils.interfaces.interfaces() network_interfaces = gns3server.utils.interfaces.interfaces()

View File

@ -38,9 +38,14 @@ class EthernetHub(BaseNode):
super().__init__(name, node_id, project, manager) 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): async def create(self):
""" """

View File

@ -38,9 +38,14 @@ class EthernetSwitch(BaseNode):
super().__init__(name, node_id, project, manager) 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): async def create(self):
""" """

View File

@ -77,7 +77,7 @@ class Nat(Cloud):
def is_supported(self): def is_supported(self):
return True return True
def __json__(self): def asdict(self):
return { return {
"name": self.name, "name": self.name,
"usage": self.usage, "usage": self.usage,

View File

@ -133,7 +133,7 @@ class DockerVM(BaseNode):
) )
) )
def __json__(self): def asdict(self):
return { return {
"name": self._name, "name": self._name,
"usage": self.usage, "usage": self.usage,

View File

@ -65,6 +65,9 @@ class NIOGenericEthernet(NIO):
return self._ethernet_device 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
}

View File

@ -64,6 +64,9 @@ class NIOLinuxEthernet(NIO):
return self._ethernet_device 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
}

View File

@ -46,6 +46,6 @@ class NIONull(NIO):
await self._hypervisor.send(f"nio create_null {self._name}") await self._hypervisor.send(f"nio create_null {self._name}")
log.info(f"NIO NULL {self._name} created.") log.info(f"NIO NULL {self._name} created.")
def __json__(self): def asdict(self):
return {"type": "nio_null"} return {"type": "nio_null"}

View File

@ -58,6 +58,9 @@ class NIOTAP(NIO):
return self._tap_device 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
}

View File

@ -126,6 +126,11 @@ class NIOUDP(NIO):
return self._rport 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
}

View File

@ -79,6 +79,10 @@ class NIOUNIX(NIO):
return self._remote_file 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
}

View File

@ -79,6 +79,10 @@ class NIOVDE(NIO):
return self._local_file 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
}

View File

@ -52,7 +52,7 @@ class ATMSwitch(Device):
if mappings: if mappings:
self._mappings = mappings self._mappings = mappings
def __json__(self): def asdict(self):
mappings = {} mappings = {}
for source, destination in self._mappings.items(): for source, destination in self._mappings.items():

View File

@ -76,11 +76,11 @@ class C1700(Router):
self._clock_divisor = 8 self._clock_divisor = 8
self._sparsemem = False # never activate sparsemem for c1700 (unstable) 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} 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) router_info.update(c1700_router_info)
return router_info return router_info

View File

@ -93,11 +93,11 @@ class C2600(Router):
self._clock_divisor = 8 self._clock_divisor = 8
self._sparsemem = False # never activate sparsemem for c2600 (unstable) 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} 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) router_info.update(c2600_router_info)
return router_info return router_info

View File

@ -77,11 +77,11 @@ class C2691(Router):
if chassis is not None: if chassis is not None:
raise DynamipsError("c2691 routers do not have chassis") raise DynamipsError("c2691 routers do not have chassis")
def __json__(self): def asdict(self):
c2691_router_info = {"iomem": self._iomem} c2691_router_info = {"iomem": self._iomem}
router_info = Router.__json__(self) router_info = Router.asdict(self)
router_info.update(c2691_router_info) router_info.update(c2691_router_info)
return router_info return router_info

View File

@ -73,11 +73,11 @@ class C3600(Router):
self._chassis = chassis self._chassis = chassis
self._clock_divisor = 4 self._clock_divisor = 4
def __json__(self): def asdict(self):
c3600_router_info = {"iomem": self._iomem, "chassis": self._chassis} 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) router_info.update(c3600_router_info)
return router_info return router_info

View File

@ -77,11 +77,11 @@ class C3725(Router):
if chassis is not None: if chassis is not None:
raise DynamipsError("c3725 routers do not have chassis") raise DynamipsError("c3725 routers do not have chassis")
def __json__(self): def asdict(self):
c3725_router_info = {"iomem": self._iomem} c3725_router_info = {"iomem": self._iomem}
router_info = Router.__json__(self) router_info = Router.asdict(self)
router_info.update(c3725_router_info) router_info.update(c3725_router_info)
return router_info return router_info

View File

@ -77,11 +77,11 @@ class C3745(Router):
if chassis is not None: if chassis is not None:
raise DynamipsError("c3745 routers do not have chassis") raise DynamipsError("c3745 routers do not have chassis")
def __json__(self): def asdict(self):
c3745_router_info = {"iomem": self._iomem} c3745_router_info = {"iomem": self._iomem}
router_info = Router.__json__(self) router_info = Router.asdict(self)
router_info.update(c3745_router_info) router_info.update(c3745_router_info)
return router_info return router_info

View File

@ -92,7 +92,7 @@ class C7200(Router):
if chassis is not None: if chassis is not None:
raise DynamipsError("c7200 routers do not have chassis") raise DynamipsError("c7200 routers do not have chassis")
def __json__(self): def asdict(self):
c7200_router_info = { c7200_router_info = {
"npe": self._npe, "npe": self._npe,
@ -101,7 +101,7 @@ class C7200(Router):
"power_supplies": self._power_supplies, "power_supplies": self._power_supplies,
} }
router_info = Router.__json__(self) router_info = Router.asdict(self)
router_info.update(c7200_router_info) router_info.update(c7200_router_info)
return router_info return router_info

View File

@ -53,7 +53,7 @@ class EthernetHub(Bridge):
else: else:
self._ports = ports self._ports = ports
def __json__(self): def asdict(self):
return { return {
"name": self.name, "name": self.name,

View File

@ -109,7 +109,7 @@ class EthernetSwitch(Device):
else: else:
self._ports = ports self._ports = ports
def __json__(self): def asdict(self):
ethernet_switch_info = { ethernet_switch_info = {
"name": self.name, "name": self.name,

View File

@ -51,7 +51,7 @@ class FrameRelaySwitch(Device):
if mappings: if mappings:
self._mappings = mappings self._mappings = mappings
def __json__(self): def asdict(self):
mappings = {} mappings = {}
for source, destination in self._mappings.items(): for source, destination in self._mappings.items():

View File

@ -155,7 +155,7 @@ class Router(BaseNode):
log.error(f"Can't move {path}: {str(e)}") log.error(f"Can't move {path}: {str(e)}")
continue continue
def __json__(self): def asdict(self):
router_info = { router_info = {
"name": self.name, "name": self.name,

View File

@ -219,7 +219,7 @@ class IOUVM(BaseNode):
if not os.access(self._path, os.X_OK): if not os.access(self._path, os.X_OK):
raise IOUError(f"IOU image '{self._path}' is not executable") raise IOUError(f"IOU image '{self._path}' is not executable")
def __json__(self): def asdict(self):
iou_vm_info = { iou_vm_info = {
"name": self.name, "name": self.name,

View File

@ -48,6 +48,9 @@ class NIOEthernet(NIO):
return "NIO Ethernet" 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
}

View File

@ -48,6 +48,9 @@ class NIOTAP(NIO):
return "NIO TAP" 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
}

View File

@ -72,6 +72,11 @@ class NIOUDP(NIO):
return "NIO UDP" 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
}

View File

@ -127,7 +127,7 @@ class PortManager:
cls._instance = cls() cls._instance = cls()
return cls._instance return cls._instance
def __json__(self): def asdict(self):
return { return {
"console_port_range": self._console_port_range, "console_port_range": self._console_port_range,

View File

@ -77,9 +77,13 @@ class Project:
log.info(f"Project {self._id} with path '{self._path}' created") 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): def is_local(self):

View File

@ -120,18 +120,3 @@ class ProjectManager:
if project_id not in self._projects: if project_id not in self._projects:
raise ComputeNotFoundError(f"Project ID {project_id} doesn't exist") raise ComputeNotFoundError(f"Project ID {project_id} doesn't exist")
del self._projects[project_id] 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

View File

@ -47,7 +47,7 @@ from ...utils.asyncio import monitor_process
from ...utils.images import md5sum from ...utils.images import md5sum
from ...utils import macaddress_to_int, int_to_macaddress 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 import logging
@ -1112,7 +1112,7 @@ class QemuVM(BaseNode):
# In case user upload image manually we don't have md5 sums. # In case user upload image manually we don't have md5 sums.
# We need generate hashes at this point, otherwise they will be generated # 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._hda_disk_image)
await cancellable_wait_run_in_executor(md5sum, self._hdb_disk_image) await cancellable_wait_run_in_executor(md5sum, self._hdb_disk_image)
await cancellable_wait_run_in_executor(md5sum, self._hdc_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}") raise QemuError(f"Invalid additional options: {additional_options} error {e}")
return command return command
def __json__(self): def asdict(self):
answer = {"project_id": self.project.id, "node_id": self.id, "node_directory": self.working_path} 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 # Qemu has a long list of options. The JSON schema is the single source of information
for field in Qemu.schema()["properties"]: for field in Qemu.schema()["properties"]:

View File

@ -86,7 +86,7 @@ class VirtualBoxVM(BaseNode):
self._ram = 0 self._ram = 0
self._adapter_type = "Intel PRO/1000 MT Desktop (82540EM)" self._adapter_type = "Intel PRO/1000 MT Desktop (82540EM)"
def __json__(self): def asdict(self):
json = { json = {
"name": self.name, "name": self.name,

View File

@ -75,7 +75,7 @@ class VMwareVM(BaseNode):
def ethernet_adapters(self): def ethernet_adapters(self):
return self._ethernet_adapters return self._ethernet_adapters
def __json__(self): def asdict(self):
json = { json = {
"name": self.name, "name": self.name,

View File

@ -120,7 +120,7 @@ class VPCSVM(BaseNode):
await self._check_vpcs_version() await self._check_vpcs_version()
def __json__(self): def asdict(self):
return { return {
"name": self.name, "name": self.name,

View File

@ -353,13 +353,14 @@ class Controller:
self._computes[compute.id] = compute self._computes[compute.id] = compute
# self.save() # self.save()
if connect: 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())) 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 return compute
else: else:
if connect: if connect:
await self._computes[compute_id].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] return self._computes[compute_id]
async def close_compute_projects(self, compute): async def close_compute_projects(self, compute):
@ -398,7 +399,7 @@ class Controller:
await compute.close() await compute.close()
del self._computes[compute_id] del self._computes[compute_id]
# self.save() # self.save()
self.notification.controller_emit("compute.deleted", compute.__json__()) self.notification.controller_emit("compute.deleted", compute.asdict())
@property @property
def notification(self): def notification(self):

View File

@ -17,14 +17,15 @@
import copy import copy
import uuid import uuid
import logging import logging
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class Appliance: class Appliance:
def __init__(self, appliance_id, data, builtin=True): def __init__(self, appliance_id, data, builtin=True):
if appliance_id is None: if appliance_id is None:
self._id = str(uuid.uuid4()) self._id = str(uuid.uuid4())
elif isinstance(appliance_id, uuid.UUID): elif isinstance(appliance_id, uuid.UUID):
@ -59,7 +60,7 @@ class Appliance:
def symbol(self, new_symbol): def symbol(self, new_symbol):
self._data["symbol"] = new_symbol self._data["symbol"] = new_symbol
def __json__(self): def asdict(self):
""" """
Appliance data (a hash) Appliance data (a hash)
""" """

View File

@ -104,7 +104,7 @@ class ApplianceManager:
try: try:
with open(path, encoding="utf-8") as f: with open(path, encoding="utf-8") as f:
appliance = Appliance(appliance_id, json.load(f), builtin=builtin) 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": if appliance.status != "broken":
self._appliances[appliance.id] = appliance self._appliances[appliance.id] = appliance
if not appliance.symbol or appliance.symbol.startswith(":/symbols/"): if not appliance.symbol or appliance.symbol.startswith(":/symbols/"):

View File

@ -21,7 +21,6 @@ import asyncio
import async_timeout import async_timeout
import socket import socket
import json import json
import uuid
import sys import sys
import io import io
from operator import itemgetter from operator import itemgetter
@ -163,7 +162,7 @@ class Compute:
if self._http_session and not self._http_session.closed: if self._http_session and not self._http_session.closed:
await self._http_session.close() await self._http_session.close()
self._connected = False self._connected = False
self._controller.notification.controller_emit("compute.updated", self.__json__()) self._controller.notification.controller_emit("compute.updated", self.asdict())
self._controller.save() self._controller.save()
async def close(self): async def close(self):
@ -291,10 +290,11 @@ class Compute:
def disk_usage_percent(self): def disk_usage_percent(self):
return self._disk_usage_percent 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 :param topology_dump: Filter to keep only properties require for saving on disk
""" """
if topology_dump: if topology_dump:
return { return {
"compute_id": self._id, "compute_id": self._id,
@ -444,7 +444,7 @@ class Compute:
self._connected = True self._connected = True
self._connection_failure = 0 self._connection_failure = 0
self._last_error = None 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): async def _connect_notification(self):
""" """
@ -466,7 +466,7 @@ class Compute:
self._memory_usage_percent = event["memory_usage_percent"] self._memory_usage_percent = event["memory_usage_percent"]
self._disk_usage_percent = event["disk_usage_percent"] self._disk_usage_percent = event["disk_usage_percent"]
# FIXME: slow down number of compute events # 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: else:
await self._controller.notification.dispatch( await self._controller.notification.dispatch(
action, event, project_id=project_id, compute_id=self.id 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}'") 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) # 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}'") 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())) asyncio.get_event_loop().call_later(1, lambda: asyncio.ensure_future(self.connect()))
self._cpu_usage_percent = None self._cpu_usage_percent = None
self._memory_usage_percent = None self._memory_usage_percent = None
self._disk_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): def _getUrl(self, path):
host = self._host host = self._host
@ -522,8 +523,8 @@ class Compute:
if data == {}: if data == {}:
data = None data = None
elif data is not None: elif data is not None:
if hasattr(data, "__json__"): if hasattr(data, "asdict"):
data = json.dumps(data.__json__()) data = json.dumps(data.asdict())
elif isinstance(data, aiohttp.streams.EmptyStreamReader): elif isinstance(data, aiohttp.streams.EmptyStreamReader):
data = None data = None
# Stream the request # Stream the request

View File

@ -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 # To avoid spamming client with large data we don't send the svg if the SVG didn't change
svg_changed = True svg_changed = True
setattr(self, prop, kwargs[prop]) setattr(self, prop, kwargs[prop])
data = self.__json__() data = self.asdict()
if not svg_changed: if not svg_changed:
del data["svg"] del data["svg"]
self._project.emit_notification("drawing.updated", data) self._project.emit_notification("drawing.updated", data)
self._project.dump() 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 :param topology_dump: Filter to keep only properties require for saving on disk
""" """
if topology_dump: if topology_dump:
return { return {
"drawing_id": self._id, "drawing_id": self._id,

View File

@ -257,7 +257,7 @@ class GNS3VM:
return self._engines["remote"] return self._engines["remote"]
raise NotImplementedError(f"The engine {engine} for the GNS3 VM is not supported") raise NotImplementedError(f"The engine {engine} for the GNS3 VM is not supported")
def __json__(self): def asdict(self):
return self._settings return self._settings
@locking @locking

View File

@ -160,14 +160,14 @@ class Link:
self._filters = new_filters self._filters = new_filters
if self._created: if self._created:
await self.update() await self.update()
self._project.emit_notification("link.updated", self.__json__()) self._project.emit_notification("link.updated", self.asdict())
self._project.dump() self._project.dump()
async def update_suspend(self, value): async def update_suspend(self, value):
if value != self._suspended: if value != self._suspended:
self._suspended = value self._suspended = value
await self.update() await self.update()
self._project.emit_notification("link.updated", self.__json__()) self._project.emit_notification("link.updated", self.asdict())
self._project.dump() self._project.dump()
@property @property
@ -231,7 +231,7 @@ class Link:
n["node"].add_link(self) n["node"].add_link(self)
n["port"].link = self n["port"].link = self
self._created = True self._created = True
self._project.emit_notification("link.created", self.__json__()) self._project.emit_notification("link.created", self.asdict())
if dump: if dump:
self._project.dump() self._project.dump()
@ -244,7 +244,7 @@ class Link:
label = node_data.get("label") label = node_data.get("label")
if label: if label:
port["label"] = label port["label"] = label
self._project.emit_notification("link.updated", self.__json__()) self._project.emit_notification("link.updated", self.asdict())
self._project.dump() self._project.dump()
async def create(self): async def create(self):
@ -286,7 +286,7 @@ class Link:
self._capturing = True self._capturing = True
self._capture_file_name = capture_file_name 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): async def stop_capture(self):
""" """
@ -294,7 +294,7 @@ class Link:
""" """
self._capturing = False self._capturing = False
self._project.emit_notification("link.updated", self.__json__()) self._project.emit_notification("link.updated", self.asdict())
def pcap_streaming_url(self): def pcap_streaming_url(self):
""" """
@ -417,7 +417,7 @@ class Link:
def __hash__(self): def __hash__(self):
return hash(self._id) 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 :param topology_dump: Filter to keep only properties require for saving on disk
""" """

View File

@ -390,7 +390,6 @@ class Node:
data["node_id"] = self._id data["node_id"] = self._id
if self._node_type == "docker": if self._node_type == "docker":
timeout = None timeout = None
else: else:
timeout = 1200 timeout = 1200
trial = 0 trial = 0
@ -420,7 +419,7 @@ class Node:
# When updating properties used only on controller we don't need to call the compute # When updating properties used only on controller we don't need to call the compute
update_compute = False update_compute = False
old_json = self.__json__() old_json = self.asdict()
compute_properties = None compute_properties = None
# Update node properties with additional elements # Update node properties with additional elements
@ -450,9 +449,9 @@ class Node:
data = self._node_data(properties=compute_properties) data = self._node_data(properties=compute_properties)
response = await self.put(None, data=data) response = await self.put(None, data=data)
await self.parse_node_response(response.json) 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 # 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() self.project.dump()
async def parse_node_response(self, response): async def parse_node_response(self, response):
@ -543,7 +542,7 @@ class Node:
if self.custom_adapters: if self.custom_adapters:
data["custom_adapters"] = 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()): for key in list(data.keys()):
if data[key] is None or data[key] is {} or key in self.CONTROLLER_ONLY_PROPERTIES: if data[key] is None or data[key] is {} or key in self.CONTROLLER_ONLY_PROPERTIES:
del data[key] del data[key]
@ -628,7 +627,7 @@ class Node:
async def put(self, path, data=None, **kwargs): async def put(self, path, data=None, **kwargs):
""" """
HTTP post on the node HTTP put on the node
""" """
if path is None: if path is None:
path = f"/projects/{self._project.id}/{self._node_type}/nodes/{self._id}" path = f"/projects/{self._project.id}/{self._node_type}/nodes/{self._id}"
@ -778,13 +777,12 @@ class Node:
return False return False
return self.id == other.id and other.project.id == self.project.id 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: topology = {
return {
"compute_id": str(self._compute.id), "compute_id": str(self._compute.id),
"node_id": self._id, "node_id": self._id,
"node_type": self._node_type, "node_type": self._node_type,
@ -808,35 +806,18 @@ class Node:
"port_segment_size": self._port_segment_size, "port_segment_size": self._port_segment_size,
"first_port_name": self._first_port_name, "first_port_name": self._first_port_name,
"custom_adapters": self._custom_adapters, "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

View File

@ -28,6 +28,7 @@ class Notification:
""" """
def __init__(self, controller): def __init__(self, controller):
self._controller = controller self._controller = controller
self._project_listeners = {} self._project_listeners = {}
self._controller_listeners = set() self._controller_listeners = set()
@ -71,19 +72,6 @@ class Notification:
:param event: Event to send :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: for controller_listener in self._controller_listeners:
controller_listener.put_nowait((action, event, {})) controller_listener.put_nowait((action, event, {}))
@ -110,7 +98,7 @@ class Notification:
project = self._controller.get_project(event["project_id"]) project = self._controller.get_project(event["project_id"])
node = project.get_node(event["node_id"]) node = project.get_node(event["node_id"])
await node.parse_node_response(event) await node.parse_node_response(event)
self.project_emit("node.updated", node.__json__()) self.project_emit("node.updated", node.asdict())
except ControllerError: # Project closing except ControllerError: # Project closing
return return
elif action == "ping": elif action == "ping":
@ -127,19 +115,6 @@ class Notification:
:param event: Event to send :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: if "project_id" in event or project_id:
self._send_event_to_project(event.get("project_id", project_id), action, event) self._send_event_to_project(event.get("project_id", project_id), action, event)
else: else:

View File

@ -93,7 +93,7 @@ class Port:
def short_name(self, val): def short_name(self, val):
self._short_name = val self._short_name = val
def __json__(self): def asdict(self):
info = { info = {
"name": self._name, "name": self._name,
"short_name": self.short_name, "short_name": self.short_name,

View File

@ -169,14 +169,14 @@ class Project:
:param kwargs: Project properties :param kwargs: Project properties
""" """
old_json = self.__json__() old_json = self.asdict()
for prop in kwargs: for prop in kwargs:
setattr(self, prop, kwargs[prop]) setattr(self, prop, kwargs[prop])
# We send notif only if object has changed # We send notif only if object has changed
if old_json != self.__json__(): if old_json != self.asdict():
self.emit_notification("project.updated", self.__json__()) self.emit_notification("project.updated", self.asdict())
self.dump() self.dump()
# update on computes # update on computes
@ -589,7 +589,7 @@ class Project:
node = await self._create_node(compute, name, node_id, node_type, **kwargs) node = await self._create_node(compute, name, node_id, node_type, **kwargs)
else: else:
node = await self._create_node(compute, name, node_id, node_type, **kwargs) 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: if dump:
self.dump() self.dump()
return node return node
@ -618,7 +618,7 @@ class Project:
# refresh the compute IDs list # refresh the compute IDs list
self._computes = [n.compute.id for n in self.nodes.values()] self._computes = [n.compute.id for n in self.nodes.values()]
self.dump() self.dump()
self.emit_notification("node.deleted", node.__json__()) self.emit_notification("node.deleted", node.asdict())
@open_required @open_required
def get_node(self, node_id): def get_node(self, node_id):
@ -633,7 +633,7 @@ class Project:
def _get_closed_data(self, section, id_key): def _get_closed_data(self, section, id_key):
""" """
Get the data for a project from the .gns3 when 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 section: The section name in the .gns3
:param id_key: The key for the element unique id :param id_key: The key for the element unique id
@ -683,7 +683,7 @@ class Project:
if drawing_id not in self._drawings: if drawing_id not in self._drawings:
drawing = Drawing(self, drawing_id=drawing_id, **kwargs) drawing = Drawing(self, drawing_id=drawing_id, **kwargs)
self._drawings[drawing.id] = drawing self._drawings[drawing.id] = drawing
self.emit_notification("drawing.created", drawing.__json__()) self.emit_notification("drawing.created", drawing.asdict())
if dump: if dump:
self.dump() self.dump()
return drawing return drawing
@ -706,7 +706,7 @@ class Project:
raise ControllerError(f"Drawing ID {drawing_id} cannot be deleted because it is locked") raise ControllerError(f"Drawing ID {drawing_id} cannot be deleted because it is locked")
del self._drawings[drawing.id] del self._drawings[drawing.id]
self.dump() self.dump()
self.emit_notification("drawing.deleted", drawing.__json__()) self.emit_notification("drawing.deleted", drawing.asdict())
@open_required @open_required
async def add_link(self, link_id=None, dump=True): async def add_link(self, link_id=None, dump=True):
@ -733,7 +733,7 @@ class Project:
if force_delete is False: if force_delete is False:
raise raise
self.dump() self.dump()
self.emit_notification("link.deleted", link.__json__()) self.emit_notification("link.deleted", link.asdict())
@open_required @open_required
def get_link(self, link_id): def get_link(self, link_id):
@ -810,7 +810,7 @@ class Project:
self._clean_pictures() self._clean_pictures()
self._status = "closed" self._status = "closed"
if not ignore_notification: if not ignore_notification:
self.emit_notification("project.closed", self.__json__()) self.emit_notification("project.closed", self.asdict())
self.reset() self.reset()
self._closing = False self._closing = False
@ -1117,7 +1117,7 @@ class Project:
try: try:
topo = project_to_topology(self) topo = project_to_topology(self)
path = self._topology_file() 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: with open(path + ".tmp", "w+", encoding="utf-8") as f:
json.dump(topo, f, indent=4, sort_keys=True) json.dump(topo, f, indent=4, sort_keys=True)
shutil.move(path + ".tmp", path) shutil.move(path + ".tmp", path)
@ -1176,10 +1176,11 @@ class Project:
:param z: Z position :param z: Z position
:returns: New node :returns: New node
""" """
if node.status != "stopped" and not node.is_always_running(): if node.status != "stopped" and not node.is_always_running():
raise ControllerError("Cannot duplicate node data while the node is 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 # Some properties like internal ID should not be duplicated
for unique_property in ( for unique_property in (
"node_id", "node_id",
@ -1219,7 +1220,7 @@ class Project:
"snapshots": len(self._snapshots), "snapshots": len(self._snapshots),
} }
def __json__(self): def asdict(self):
return { return {
"name": self._name, "name": self._name,
"project_id": self._id, "project_id": self._id,

View File

@ -36,10 +36,6 @@ import logging
log = logging.getLogger(__name__) 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: class Snapshot:
""" """
A snapshot object A snapshot object
@ -59,7 +55,7 @@ class Snapshot:
filename = ( filename = (
self._name 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" + ".gns3project"
) )
else: else:
@ -67,7 +63,7 @@ class Snapshot:
datestring = filename.replace(self._name + "_", "").split(".")[0] datestring = filename.replace(self._name + "_", "").split(".")[0]
try: try:
self._created_at = ( 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: except ValueError:
self._created_at = datetime.utcnow().timestamp() self._created_at = datetime.utcnow().timestamp()
@ -137,10 +133,10 @@ class Snapshot:
except (OSError, PermissionError) as e: except (OSError, PermissionError) as e:
raise ControllerError(str(e)) raise ControllerError(str(e))
await project.open() await project.open()
self._project.emit_notification("snapshot.restored", self.__json__()) self._project.emit_notification("snapshot.restored", self.asdict())
return self._project return self._project
def __json__(self): def asdict(self):
return { return {
"snapshot_id": self._id, "snapshot_id": self._id,
"name": self._name, "name": self._name,

View File

@ -30,9 +30,13 @@ from ..version import __version__
from ..utils.qt import qt_font_to_style from ..utils.qt import qt_font_to_style
from ..compute.dynamips import PLATFORMS_DEFAULT_RAM from ..compute.dynamips import PLATFORMS_DEFAULT_RAM
from .controller_error import ControllerError 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.controller.topology import Topology
from gns3server.schemas.dynamips_nodes import DynamipsCreate from gns3server.schemas.compute.dynamips_nodes import DynamipsCreate
import logging import logging
@ -89,23 +93,23 @@ def project_to_topology(project):
} }
for node in project.nodes.values(): for node in project.nodes.values():
if hasattr(node, "__json__"): if isinstance(node, Node):
data["topology"]["nodes"].append(node.__json__(topology_dump=True)) data["topology"]["nodes"].append(node.asdict(topology_dump=True))
else: else:
data["topology"]["nodes"].append(node) data["topology"]["nodes"].append(node)
for link in project.links.values(): for link in project.links.values():
if hasattr(link, "__json__"): if isinstance(link, Link):
data["topology"]["links"].append(link.__json__(topology_dump=True)) data["topology"]["links"].append(link.asdict(topology_dump=True))
else: else:
data["topology"]["links"].append(link) data["topology"]["links"].append(link)
for drawing in project.drawings.values(): for drawing in project.drawings.values():
if hasattr(drawing, "__json__"): if isinstance(drawing, Drawing):
data["topology"]["drawings"].append(drawing.__json__(topology_dump=True)) data["topology"]["drawings"].append(drawing.asdict(topology_dump=True))
else: else:
data["topology"]["drawings"].append(drawing) data["topology"]["drawings"].append(drawing)
for compute in project.computes: for compute in project.computes:
if hasattr(compute, "__json__"): if isinstance(compute, Compute):
compute = compute.__json__(topology_dump=True) compute = compute.asdict(topology_dump=True)
if compute["compute_id"] not in ( if compute["compute_id"] not in (
"vm", "vm",
"local", "local",
@ -121,7 +125,8 @@ def load_topology(path):
""" """
Open a topology file, patch it for last GNS3 release and return it 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: try:
with open(path, encoding="utf-8") as f: with open(path, encoding="utf-8") as f:
topo = json.load(f) topo = json.load(f)

View File

@ -33,6 +33,10 @@ log = logging.getLogger(__name__)
def create_startup_handler(app: FastAPI) -> Callable: def create_startup_handler(app: FastAPI) -> Callable:
"""
Tasks to be performed when the server is starting.
"""
async def start_app() -> None: async def start_app() -> None:
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
logger = logging.getLogger("asyncio") logger = logging.getLogger("asyncio")
@ -77,6 +81,10 @@ def create_startup_handler(app: FastAPI) -> Callable:
def create_shutdown_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: async def shutdown_handler() -> None:
await HTTPClient.close_session() await HTTPClient.close_session()
await Controller.instance().stop() await Controller.instance().stop()

View File

@ -15,9 +15,14 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from sqlalchemy import Boolean, Column, String from sqlalchemy import Boolean, Column, String, event
from .base import BaseTable, generate_uuid, GUID from .base import BaseTable, generate_uuid, GUID
from gns3server.services import auth_service
import logging
log = logging.getLogger(__name__)
class User(BaseTable): class User(BaseTable):
@ -30,4 +35,19 @@ class User(BaseTable):
full_name = Column(String) full_name = Column(String)
hashed_password = Column(String) hashed_password = Column(String)
is_active = Column(Boolean, default=True) 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")

View File

@ -26,6 +26,10 @@ import gns3server.db.models as models
from gns3server import schemas from gns3server import schemas
from gns3server.services import auth_service from gns3server.services import auth_service
import logging
log = logging.getLogger(__name__)
class UsersRepository(BaseRepository): class UsersRepository(BaseRepository):
def __init__(self, db_session: AsyncSession) -> None: def __init__(self, db_session: AsyncSession) -> None:
@ -59,7 +63,7 @@ class UsersRepository(BaseRepository):
async def create_user(self, user: schemas.UserCreate) -> models.User: 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( db_user = models.User(
username=user.username, email=user.email, full_name=user.full_name, hashed_password=hashed_password 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) update_values = user_update.dict(exclude_unset=True)
password = update_values.pop("password", None) password = update_values.pop("password", None)
if password: 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) 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) user = await self.get_user_by_username(username)
if not user: if not user:
return None 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): if not self._auth_service.verify_password(password, user.hashed_password):
return None return None
return user return user

View File

@ -14,46 +14,36 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# General schemas
from .config import ServerConfig from .config import ServerConfig
from .iou_license import IOULicense
from .links import Link
from .common import ErrorMessage from .common import ErrorMessage
from .version import Version 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 # Controller schemas
from .cloud_templates import CloudTemplate from .controller.links import LinkCreate, LinkUpdate, Link
from .iou_templates import IOUTemplate from .controller.computes import ComputeCreate, ComputeUpdate, AutoIdlePC, Compute
from .docker_templates import DockerTemplate from .controller.templates import TemplateCreate, TemplateUpdate, TemplateUsage, Template
from .ethernet_hub_templates import EthernetHubTemplate from .controller.drawings import Drawing
from .ethernet_switch_templates import EthernetSwitchTemplate from .controller.gns3vm import GNS3VM
from .virtualbox_templates import VirtualBoxTemplate from .controller.nodes import NodeCreate, NodeUpdate, NodeDuplicate, NodeCapture, Node
from .vmware_templates import VMwareTemplate from .controller.projects import ProjectCreate, ProjectUpdate, ProjectDuplicate, Project, ProjectFile
from .qemu_templates import QemuTemplate from .controller.users import UserCreate, UserUpdate, User, Credentials
from .dynamips_templates import ( 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, DynamipsTemplate,
C1700DynamipsTemplate, C1700DynamipsTemplate,
C2600DynamipsTemplate, C2600DynamipsTemplate,
@ -63,3 +53,19 @@ from .dynamips_templates import (
C3745DynamipsTemplate, C3745DynamipsTemplate,
C7200DynamipsTemplate, 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

View File

@ -16,6 +16,7 @@
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from typing import Optional, Union from typing import Optional, Union
from enum import Enum
class ErrorMessage(BaseModel): class ErrorMessage(BaseModel):
@ -26,14 +27,45 @@ class ErrorMessage(BaseModel):
message: str message: str
class Label(BaseModel): class NodeStatus(str, Enum):
""" """
Label data. Supported node statuses.
""" """
text: str stopped = "stopped"
style: Optional[Union[str, None]] = Field(None, description="SVG style attribute. Apply default style if null") started = "started"
x: Optional[Union[int, None]] = Field(None, description="Relative X position of the label. Center it if null") suspended = "suspended"
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")
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"

View File

@ -18,7 +18,7 @@ from pydantic import BaseModel
from typing import Optional from typing import Optional
from uuid import UUID from uuid import UUID
from .nodes import NodeStatus from ..common import NodeStatus
class ATMSwitchBase(BaseModel): class ATMSwitchBase(BaseModel):

View File

@ -19,7 +19,7 @@ from typing import Optional, Union, List
from enum import Enum from enum import Enum
from uuid import UUID from uuid import UUID
from .nodes import NodeStatus from ..common import NodeStatus
class HostInterfaceType(Enum): class HostInterfaceType(Enum):

View File

@ -18,7 +18,7 @@ from pydantic import BaseModel, Field
from typing import Optional, List from typing import Optional, List
from uuid import UUID from uuid import UUID
from .nodes import CustomAdapter, ConsoleType, AuxType, NodeStatus from ..common import NodeStatus, CustomAdapter, ConsoleType, AuxType
class DockerBase(BaseModel): class DockerBase(BaseModel):

View File

@ -20,7 +20,7 @@ from typing import Optional, List
from enum import Enum from enum import Enum
from uuid import UUID from uuid import UUID
from .nodes import NodeStatus from ..common import NodeStatus
class DynamipsPlatform(str, Enum): class DynamipsPlatform(str, Enum):

View File

@ -18,7 +18,7 @@ from pydantic import BaseModel
from typing import Optional, List from typing import Optional, List
from uuid import UUID from uuid import UUID
from .nodes import NodeStatus from ..common import NodeStatus
class EthernetHubPort(BaseModel): class EthernetHubPort(BaseModel):

View File

@ -19,7 +19,7 @@ from typing import Optional, List
from uuid import UUID from uuid import UUID
from enum import Enum from enum import Enum
from .nodes import NodeStatus from ..common import NodeStatus
class EthernetSwitchPortType(Enum): class EthernetSwitchPortType(Enum):

View File

@ -18,7 +18,7 @@ from pydantic import BaseModel
from typing import Optional from typing import Optional
from uuid import UUID from uuid import UUID
from .nodes import NodeStatus from ..common import NodeStatus
class FrameRelaySwitchBase(BaseModel): class FrameRelaySwitchBase(BaseModel):

View File

@ -18,7 +18,7 @@ from pydantic import BaseModel, Field
from typing import Optional from typing import Optional
from uuid import UUID from uuid import UUID
from .nodes import ConsoleType, NodeStatus from ..common import NodeStatus, ConsoleType
class IOUBase(BaseModel): class IOUBase(BaseModel):

View File

@ -19,7 +19,7 @@ from typing import Optional, Union, List
from enum import Enum from enum import Enum
from uuid import UUID from uuid import UUID
from .nodes import NodeStatus from ..common import NodeStatus
class HostInterfaceType(Enum): class HostInterfaceType(Enum):

Some files were not shown because too many files have changed in this diff Show More