1
0
mirror of https://github.com/GNS3/gns3-server synced 2025-01-22 05:51:17 +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(
atm_switch_nodes.router, prefix="/projects/{project_id}/atm_switch/nodes", tags=["ATM switch"]
capabilities.router,
tags=["Capabilities"]
)
compute_api.include_router(cloud_nodes.router, prefix="/projects/{project_id}/cloud/nodes", tags=["Cloud nodes"])
compute_api.include_router(docker_nodes.router, prefix="/projects/{project_id}/docker/nodes", tags=["Docker nodes"])
compute_api.include_router(
dynamips_nodes.router, prefix="/projects/{project_id}/dynamips/nodes", tags=["Dynamips nodes"]
compute.router,
tags=["Compute"]
)
compute_api.include_router(
notifications.router,
tags=["Notifications"]
)
compute_api.include_router(
projects.router,
tags=["Projects"]
)
compute_api.include_router(
images.router,
tags=["Images"]
)
compute_api.include_router(
atm_switch_nodes.router,
prefix="/projects/{project_id}/atm_switch/nodes",
tags=["ATM switch"]
)
compute_api.include_router(
ethernet_hub_nodes.router, prefix="/projects/{project_id}/ethernet_hub/nodes", tags=["Ethernet hub nodes"]
cloud_nodes.router,
prefix="/projects/{project_id}/cloud/nodes",
tags=["Cloud nodes"]
)
compute_api.include_router(
ethernet_switch_nodes.router, prefix="/projects/{project_id}/ethernet_switch/nodes", tags=["Ethernet switch nodes"]
docker_nodes.router,
prefix="/projects/{project_id}/docker/nodes",
tags=["Docker nodes"]
)
compute_api.include_router(
dynamips_nodes.router,
prefix="/projects/{project_id}/dynamips/nodes",
tags=["Dynamips nodes"]
)
compute_api.include_router(
ethernet_hub_nodes.router,
prefix="/projects/{project_id}/ethernet_hub/nodes",
tags=["Ethernet hub nodes"]
)
compute_api.include_router(
ethernet_switch_nodes.router,
prefix="/projects/{project_id}/ethernet_switch/nodes",
tags=["Ethernet switch nodes"]
)
compute_api.include_router(
frame_relay_switch_nodes.router,
prefix="/projects/{project_id}/frame_relay_switch/nodes",
tags=["Frame Relay switch nodes"],
tags=["Frame Relay switch nodes"]
)
compute_api.include_router(iou_nodes.router, prefix="/projects/{project_id}/iou/nodes", tags=["IOU nodes"])
compute_api.include_router(nat_nodes.router, prefix="/projects/{project_id}/nat/nodes", tags=["NAT nodes"])
compute_api.include_router(qemu_nodes.router, prefix="/projects/{project_id}/qemu/nodes", tags=["Qemu nodes"])
compute_api.include_router(
virtualbox_nodes.router, prefix="/projects/{project_id}/virtualbox/nodes", tags=["VirtualBox nodes"]
iou_nodes.router,
prefix="/projects/{project_id}/iou/nodes",
tags=["IOU nodes"])
compute_api.include_router(
nat_nodes.router,
prefix="/projects/{project_id}/nat/nodes",
tags=["NAT nodes"]
)
compute_api.include_router(
qemu_nodes.router,
prefix="/projects/{project_id}/qemu/nodes",
tags=["Qemu nodes"]
)
compute_api.include_router(
virtualbox_nodes.router,
prefix="/projects/{project_id}/virtualbox/nodes",
tags=["VirtualBox nodes"]
)
compute_api.include_router(
vmware_nodes.router,
prefix="/projects/{project_id}/vmware/nodes",
tags=["VMware nodes"]
)
compute_api.include_router(
vpcs_nodes.router,
prefix="/projects/{project_id}/vpcs/nodes",
tags=["VPCS nodes"]
)
compute_api.include_router(vmware_nodes.router, prefix="/projects/{project_id}/vmware/nodes", tags=["VMware nodes"])
compute_api.include_router(vpcs_nodes.router, prefix="/projects/{project_id}/vpcs/nodes", tags=["VPCS nodes"])

View File

@ -20,7 +20,7 @@ API routes for ATM switch nodes.
import os
from fastapi import APIRouter, Depends, Body, status
from fastapi import APIRouter, Depends, Body, Path, status
from fastapi.encoders import jsonable_encoder
from fastapi.responses import StreamingResponse
from uuid import UUID
@ -34,7 +34,7 @@ responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find
router = APIRouter(responses=responses)
async def dep_node(project_id: UUID, node_id: UUID):
async def dep_node(project_id: UUID, node_id: UUID) -> ATMSwitch:
"""
Dependency to retrieve a node.
"""
@ -50,7 +50,7 @@ async def dep_node(project_id: UUID, node_id: UUID):
status_code=status.HTTP_201_CREATED,
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create ATM switch node"}},
)
async def create_atm_switch(project_id: UUID, node_data: schemas.ATMSwitchCreate):
async def create_atm_switch(project_id: UUID, node_data: schemas.ATMSwitchCreate) -> schemas.ATMSwitch:
"""
Create a new ATM switch node.
"""
@ -59,36 +59,42 @@ async def create_atm_switch(project_id: UUID, node_data: schemas.ATMSwitchCreate
dynamips_manager = Dynamips.instance()
node_data = jsonable_encoder(node_data, exclude_unset=True)
node = await dynamips_manager.create_node(
node_data.pop("name"),
node_data.get("name"),
str(project_id),
node_data.get("node_id"),
node_type="atm_switch",
mappings=node_data.get("mappings"),
)
return node.__json__()
return node.asdict()
@router.get("/{node_id}", response_model=schemas.ATMSwitch)
def get_atm_switch(node: ATMSwitch = Depends(dep_node)):
def get_atm_switch(node: ATMSwitch = Depends(dep_node)) -> schemas.ATMSwitch:
"""
Return an ATM switch node.
"""
return node.__json__()
return node.asdict()
@router.post("/{node_id}/duplicate", response_model=schemas.ATMSwitch, status_code=status.HTTP_201_CREATED)
async def duplicate_atm_switch(destination_node_id: UUID = Body(..., embed=True), node: ATMSwitch = Depends(dep_node)):
async def duplicate_atm_switch(
destination_node_id: UUID = Body(..., embed=True),
node: ATMSwitch = Depends(dep_node)
) -> schemas.ATMSwitch:
"""
Duplicate an ATM switch node.
"""
new_node = await Dynamips.instance().duplicate_node(node.id, str(destination_node_id))
return new_node.__json__()
return new_node.asdict()
@router.put("/{node_id}", response_model=schemas.ATMSwitch)
async def update_atm_switch(node_data: schemas.ATMSwitchUpdate, node: ATMSwitch = Depends(dep_node)):
async def update_atm_switch(
node_data: schemas.ATMSwitchUpdate,
node: ATMSwitch = Depends(dep_node)
) -> schemas.ATMSwitch:
"""
Update an ATM switch node.
"""
@ -99,11 +105,11 @@ async def update_atm_switch(node_data: schemas.ATMSwitchUpdate, node: ATMSwitch
if "mappings" in node_data:
node.mappings = node_data["mappings"]
node.updated()
return node.__json__()
return node.asdict()
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_atm_switch_node(node: ATMSwitch = Depends(dep_node)):
async def delete_atm_switch_node(node: ATMSwitch = Depends(dep_node)) -> None:
"""
Delete an ATM switch node.
"""
@ -122,7 +128,7 @@ def start_atm_switch(node: ATMSwitch = Depends(dep_node)):
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
def stop_atm_switch(node: ATMSwitch = Depends(dep_node)):
def stop_atm_switch(node: ATMSwitch = Depends(dep_node)) -> None:
"""
Stop an ATM switch node.
This endpoint results in no action since ATM switch nodes are always on.
@ -132,7 +138,7 @@ def stop_atm_switch(node: ATMSwitch = Depends(dep_node)):
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
def suspend_atm_switch(node: ATMSwitch = Depends(dep_node)):
def suspend_atm_switch(node: ATMSwitch = Depends(dep_node)) -> None:
"""
Suspend an ATM switch node.
This endpoint results in no action since ATM switch nodes are always on.
@ -147,8 +153,12 @@ def suspend_atm_switch(node: ATMSwitch = Depends(dep_node)):
response_model=schemas.UDPNIO,
)
async def create_nio(
adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: ATMSwitch = Depends(dep_node)
):
*,
adapter_number: int = Path(..., ge=0, le=0),
port_number: int,
nio_data: schemas.UDPNIO,
node: ATMSwitch = Depends(dep_node)
) -> schemas.UDPNIO:
"""
Add a NIO (Network Input/Output) to the node.
The adapter number on the switch is always 0.
@ -156,11 +166,11 @@ async def create_nio(
nio = await Dynamips.instance().create_nio(node, jsonable_encoder(nio_data, exclude_unset=True))
await node.add_nio(nio, port_number)
return nio.__json__()
return nio.asdict()
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
async def delete_nio(adapter_number: int, port_number: int, node: ATMSwitch = Depends(dep_node)):
async def delete_nio(adapter_number: int, port_number: int, node: ATMSwitch = Depends(dep_node)) -> None:
"""
Remove a NIO (Network Input/Output) from the node.
The adapter number on the switch is always 0.
@ -172,8 +182,12 @@ async def delete_nio(adapter_number: int, port_number: int, node: ATMSwitch = De
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
async def start_capture(
adapter_number: int, port_number: int, node_capture_data: schemas.NodeCapture, node: ATMSwitch = Depends(dep_node)
):
*,
adapter_number: int = Path(..., ge=0, le=0),
port_number: int,
node_capture_data: schemas.NodeCapture,
node: ATMSwitch = Depends(dep_node)
) -> dict:
"""
Start a packet capture on the node.
The adapter number on the switch is always 0.
@ -188,7 +202,12 @@ async def start_capture(
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop_capture",
status_code=status.HTTP_204_NO_CONTENT,
)
async def stop_capture(adapter_number: int, port_number: int, node: ATMSwitch = Depends(dep_node)):
async def stop_capture(
*,
adapter_number: int = Path(..., ge=0, le=0),
port_number: int,
node: ATMSwitch = Depends(dep_node)
) -> None:
"""
Stop a packet capture on the node.
The adapter number on the switch is always 0.
@ -198,7 +217,12 @@ async def stop_capture(adapter_number: int, port_number: int, node: ATMSwitch =
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
async def stream_pcap_file(adapter_number: int, port_number: int, node: ATMSwitch = Depends(dep_node)):
async def stream_pcap_file(
*,
adapter_number: int = Path(..., ge=0, le=0),
port_number: int,
node: ATMSwitch = Depends(dep_node)
) -> StreamingResponse:
"""
Stream the pcap capture file.
The adapter number on the switch is always 0.

View File

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

View File

@ -20,7 +20,7 @@ API routes for cloud nodes.
import os
from fastapi import APIRouter, Depends, status
from fastapi import APIRouter, Depends, Path, status
from fastapi.encoders import jsonable_encoder
from fastapi.responses import StreamingResponse
from typing import Union
@ -35,7 +35,7 @@ responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find
router = APIRouter(responses=responses)
def dep_node(project_id: UUID, node_id: UUID):
def dep_node(project_id: UUID, node_id: UUID) -> Cloud:
"""
Dependency to retrieve a node.
"""
@ -51,7 +51,7 @@ def dep_node(project_id: UUID, node_id: UUID):
status_code=status.HTTP_201_CREATED,
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create cloud node"}},
)
async def create_cloud(project_id: UUID, node_data: schemas.CloudCreate):
async def create_cloud(project_id: UUID, node_data: schemas.CloudCreate) -> schemas.Cloud:
"""
Create a new cloud node.
"""
@ -72,20 +72,20 @@ async def create_cloud(project_id: UUID, node_data: schemas.CloudCreate):
node.remote_console_type = node_data.get("remote_console_type", node.remote_console_type)
node.remote_console_http_path = node_data.get("remote_console_http_path", node.remote_console_http_path)
node.usage = node_data.get("usage", "")
return node.__json__()
return node.asdict()
@router.get("/{node_id}", response_model=schemas.Cloud)
def get_cloud(node: Cloud = Depends(dep_node)):
def get_cloud(node: Cloud = Depends(dep_node)) -> schemas.Cloud:
"""
Return a cloud node.
"""
return node.__json__()
return node.asdict()
@router.put("/{node_id}", response_model=schemas.Cloud)
def update_cloud(node_data: schemas.CloudUpdate, node: Cloud = Depends(dep_node)):
def update_cloud(node_data: schemas.CloudUpdate, node: Cloud = Depends(dep_node)) -> schemas.Cloud:
"""
Update a cloud node.
"""
@ -95,11 +95,11 @@ def update_cloud(node_data: schemas.CloudUpdate, node: Cloud = Depends(dep_node)
if hasattr(node, name) and getattr(node, name) != value:
setattr(node, name, value)
node.updated()
return node.__json__()
return node.asdict()
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_cloud(node: Cloud = Depends(dep_node)):
async def delete_cloud(node: Cloud = Depends(dep_node)) -> None:
"""
Delete a cloud node.
"""
@ -108,7 +108,7 @@ async def delete_cloud(node: Cloud = Depends(dep_node)):
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
async def start_cloud(node: Cloud = Depends(dep_node)):
async def start_cloud(node: Cloud = Depends(dep_node)) -> None:
"""
Start a cloud node.
"""
@ -117,7 +117,7 @@ async def start_cloud(node: Cloud = Depends(dep_node)):
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
async def stop_cloud(node: Cloud = Depends(dep_node)):
async def stop_cloud(node: Cloud = Depends(dep_node)) -> None:
"""
Stop a cloud node.
This endpoint results in no action since cloud nodes cannot be stopped.
@ -127,7 +127,7 @@ async def stop_cloud(node: Cloud = Depends(dep_node)):
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
async def suspend_cloud(node: Cloud = Depends(dep_node)):
async def suspend_cloud(node: Cloud = Depends(dep_node)) -> None:
"""
Suspend a cloud node.
This endpoint results in no action since cloud nodes cannot be suspended.
@ -142,11 +142,12 @@ async def suspend_cloud(node: Cloud = Depends(dep_node)):
response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
)
async def create_cloud_nio(
adapter_number: int,
port_number: int,
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
node: Cloud = Depends(dep_node),
):
*,
adapter_number: int = Path(..., ge=0, le=0),
port_number: int,
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
node: Cloud = Depends(dep_node),
) -> Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO]:
"""
Add a NIO (Network Input/Output) to the node.
The adapter number on the cloud is always 0.
@ -154,7 +155,7 @@ async def create_cloud_nio(
nio = Builtin.instance().create_nio(jsonable_encoder(nio_data, exclude_unset=True))
await node.add_nio(nio, port_number)
return nio.__json__()
return nio.asdict()
@router.put(
@ -163,11 +164,12 @@ async def create_cloud_nio(
response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
)
async def update_cloud_nio(
adapter_number: int,
port_number: int,
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
node: Cloud = Depends(dep_node),
):
*,
adapter_number: int = Path(..., ge=0, le=0),
port_number: int,
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
node: Cloud = Depends(dep_node),
) -> Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO]:
"""
Update a NIO (Network Input/Output) to the node.
The adapter number on the cloud is always 0.
@ -177,11 +179,16 @@ async def update_cloud_nio(
if nio_data.filters:
nio.filters = nio_data.filters
await node.update_nio(port_number, nio)
return nio.__json__()
return nio.asdict()
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
async def delete_cloud_nio(adapter_number: int, port_number: int, node: Cloud = Depends(dep_node)):
async def delete_cloud_nio(
*,
adapter_number: int = Path(..., ge=0, le=0),
port_number: int,
node: Cloud = Depends(dep_node)
) -> None:
"""
Remove a NIO (Network Input/Output) from the node.
The adapter number on the cloud is always 0.
@ -192,8 +199,12 @@ async def delete_cloud_nio(adapter_number: int, port_number: int, node: Cloud =
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
async def start_cloud_capture(
adapter_number: int, port_number: int, node_capture_data: schemas.NodeCapture, node: Cloud = Depends(dep_node)
):
*,
adapter_number: int = Path(..., ge=0, le=0),
port_number: int,
node_capture_data: schemas.NodeCapture,
node: Cloud = Depends(dep_node)
) -> dict:
"""
Start a packet capture on the node.
The adapter number on the cloud is always 0.
@ -207,7 +218,12 @@ async def start_cloud_capture(
@router.post(
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT
)
async def stop_cloud_capture(adapter_number: int, port_number: int, node: Cloud = Depends(dep_node)):
async def stop_cloud_capture(
*,
adapter_number: int = Path(..., ge=0, le=0),
port_number: int,
node: Cloud = Depends(dep_node)
) -> None:
"""
Stop a packet capture on the node.
The adapter number on the cloud is always 0.
@ -217,7 +233,12 @@ async def stop_cloud_capture(adapter_number: int, port_number: int, node: Cloud
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/pcap")
async def stream_pcap_file(adapter_number: int, port_number: int, node: Cloud = Depends(dep_node)):
async def stream_pcap_file(
*,
adapter_number: int = Path(..., ge=0, le=0),
port_number: int,
node: Cloud = Depends(dep_node)
) -> StreamingResponse:
"""
Stream the pcap capture file.
The adapter number on the cloud is always 0.

View File

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

View File

@ -34,7 +34,7 @@ responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find
router = APIRouter(responses=responses)
def dep_node(project_id: UUID, node_id: UUID):
def dep_node(project_id: UUID, node_id: UUID) -> DockerVM:
"""
Dependency to retrieve a node.
"""
@ -50,7 +50,7 @@ def dep_node(project_id: UUID, node_id: UUID):
status_code=status.HTTP_201_CREATED,
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Docker node"}},
)
async def create_docker_node(project_id: UUID, node_data: schemas.DockerCreate):
async def create_docker_node(project_id: UUID, node_data: schemas.DockerCreate) -> schemas.Docker:
"""
Create a new Docker node.
"""
@ -82,20 +82,20 @@ async def create_docker_node(project_id: UUID, node_data: schemas.DockerCreate):
if hasattr(container, name) and getattr(container, name) != value:
setattr(container, name, value)
return container.__json__()
return container.asdict()
@router.get("/{node_id}", response_model=schemas.Docker)
def get_docker_node(node: DockerVM = Depends(dep_node)):
def get_docker_node(node: DockerVM = Depends(dep_node)) -> schemas.Docker:
"""
Return a Docker node.
"""
return node.__json__()
return node.asdict()
@router.put("/{node_id}", response_model=schemas.Docker)
async def update_docker_node(node_data: schemas.DockerUpdate, node: DockerVM = Depends(dep_node)):
async def update_docker_node(node_data: schemas.DockerUpdate, node: DockerVM = Depends(dep_node)) -> schemas.Docker:
"""
Update a Docker node.
"""
@ -128,11 +128,11 @@ async def update_docker_node(node_data: schemas.DockerUpdate, node: DockerVM = D
if changed:
await node.update()
node.updated()
return node.__json__()
return node.asdict()
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
async def start_docker_node(node: DockerVM = Depends(dep_node)):
async def start_docker_node(node: DockerVM = Depends(dep_node)) -> None:
"""
Start a Docker node.
"""
@ -141,7 +141,7 @@ async def start_docker_node(node: DockerVM = Depends(dep_node)):
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
async def stop_docker_node(node: DockerVM = Depends(dep_node)):
async def stop_docker_node(node: DockerVM = Depends(dep_node)) -> None:
"""
Stop a Docker node.
"""
@ -150,7 +150,7 @@ async def stop_docker_node(node: DockerVM = Depends(dep_node)):
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
async def suspend_docker_node(node: DockerVM = Depends(dep_node)):
async def suspend_docker_node(node: DockerVM = Depends(dep_node)) -> None:
"""
Suspend a Docker node.
"""
@ -159,7 +159,7 @@ async def suspend_docker_node(node: DockerVM = Depends(dep_node)):
@router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT)
async def reload_docker_node(node: DockerVM = Depends(dep_node)):
async def reload_docker_node(node: DockerVM = Depends(dep_node)) -> None:
"""
Reload a Docker node.
"""
@ -168,7 +168,7 @@ async def reload_docker_node(node: DockerVM = Depends(dep_node)):
@router.post("/{node_id}/pause", status_code=status.HTTP_204_NO_CONTENT)
async def pause_docker_node(node: DockerVM = Depends(dep_node)):
async def pause_docker_node(node: DockerVM = Depends(dep_node)) -> None:
"""
Pause a Docker node.
"""
@ -177,7 +177,7 @@ async def pause_docker_node(node: DockerVM = Depends(dep_node)):
@router.post("/{node_id}/unpause", status_code=status.HTTP_204_NO_CONTENT)
async def unpause_docker_node(node: DockerVM = Depends(dep_node)):
async def unpause_docker_node(node: DockerVM = Depends(dep_node)) -> None:
"""
Unpause a Docker node.
"""
@ -186,7 +186,7 @@ async def unpause_docker_node(node: DockerVM = Depends(dep_node)):
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_docker_node(node: DockerVM = Depends(dep_node)):
async def delete_docker_node(node: DockerVM = Depends(dep_node)) -> None:
"""
Delete a Docker node.
"""
@ -195,13 +195,16 @@ async def delete_docker_node(node: DockerVM = Depends(dep_node)):
@router.post("/{node_id}/duplicate", response_model=schemas.Docker, status_code=status.HTTP_201_CREATED)
async def duplicate_docker_node(destination_node_id: UUID = Body(..., embed=True), node: DockerVM = Depends(dep_node)):
async def duplicate_docker_node(
destination_node_id: UUID = Body(..., embed=True),
node: DockerVM = Depends(dep_node)
) -> schemas.Docker:
"""
Duplicate a Docker node.
"""
new_node = await Docker.instance().duplicate_node(node.id, str(destination_node_id))
return new_node.__json__()
return new_node.asdict()
@router.post(
@ -211,7 +214,7 @@ async def duplicate_docker_node(destination_node_id: UUID = Body(..., embed=True
)
async def create_docker_node_nio(
adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: DockerVM = Depends(dep_node)
):
) -> schemas.UDPNIO:
"""
Add a NIO (Network Input/Output) to the node.
The port number on the Docker node is always 0.
@ -219,7 +222,7 @@ async def create_docker_node_nio(
nio = Docker.instance().create_nio(jsonable_encoder(nio_data, exclude_unset=True))
await node.adapter_add_nio_binding(adapter_number, nio)
return nio.__json__()
return nio.asdict()
@router.put(
@ -229,7 +232,7 @@ async def create_docker_node_nio(
)
async def update_docker_node_nio(
adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: DockerVM = Depends(dep_node)
):
) -> schemas.UDPNIO:
"""
Update a NIO (Network Input/Output) on the node.
The port number on the Docker node is always 0.
@ -239,11 +242,15 @@ async def update_docker_node_nio(
if nio_data.filters:
nio.filters = nio_data.filters
await node.adapter_update_nio_binding(adapter_number, nio)
return nio.__json__()
return nio.asdict()
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
async def delete_docker_node_nio(adapter_number: int, port_number: int, node: DockerVM = Depends(dep_node)):
async def delete_docker_node_nio(
adapter_number: int,
port_number: int,
node: DockerVM = Depends(dep_node)
) -> None:
"""
Delete a NIO (Network Input/Output) from the node.
The port number on the Docker node is always 0.
@ -254,8 +261,11 @@ async def delete_docker_node_nio(adapter_number: int, port_number: int, node: Do
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
async def start_docker_node_capture(
adapter_number: int, port_number: int, node_capture_data: schemas.NodeCapture, node: DockerVM = Depends(dep_node)
):
adapter_number: int,
port_number: int,
node_capture_data: schemas.NodeCapture,
node: DockerVM = Depends(dep_node)
) -> dict:
"""
Start a packet capture on the node.
The port number on the Docker node is always 0.
@ -267,9 +277,14 @@ async def start_docker_node_capture(
@router.post(
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop",
status_code=status.HTTP_204_NO_CONTENT
)
async def stop_docker_node_capture(adapter_number: int, port_number: int, node: DockerVM = Depends(dep_node)):
async def stop_docker_node_capture(
adapter_number: int,
port_number: int,
node: DockerVM = Depends(dep_node)
) -> None:
"""
Stop a packet capture on the node.
The port number on the Docker node is always 0.
@ -279,7 +294,11 @@ async def stop_docker_node_capture(adapter_number: int, port_number: int, node:
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
async def stream_pcap_file(adapter_number: int, port_number: int, node: DockerVM = Depends(dep_node)):
async def stream_pcap_file(
adapter_number: int,
port_number: int,
node: DockerVM = Depends(dep_node)
) -> StreamingResponse:
"""
Stream the pcap capture file.
The port number on the Docker node is always 0.
@ -291,7 +310,7 @@ async def stream_pcap_file(adapter_number: int, port_number: int, node: DockerVM
@router.websocket("/{node_id}/console/ws")
async def console_ws(websocket: WebSocket, node: DockerVM = Depends(dep_node)):
async def console_ws(websocket: WebSocket, node: DockerVM = Depends(dep_node)) -> None:
"""
Console WebSocket.
"""
@ -300,6 +319,6 @@ async def console_ws(websocket: WebSocket, node: DockerVM = Depends(dep_node)):
@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
async def reset_console(node: DockerVM = Depends(dep_node)):
async def reset_console(node: DockerVM = Depends(dep_node)) -> None:
await node.reset_console()

View File

@ -40,7 +40,7 @@ router = APIRouter(responses=responses)
DEFAULT_CHASSIS = {"c1700": "1720", "c2600": "2610", "c3600": "3640"}
def dep_node(project_id: UUID, node_id: UUID):
def dep_node(project_id: UUID, node_id: UUID) -> Router:
"""
Dependency to retrieve a node.
"""
@ -56,7 +56,7 @@ def dep_node(project_id: UUID, node_id: UUID):
status_code=status.HTTP_201_CREATED,
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Dynamips node"}},
)
async def create_router(project_id: UUID, node_data: schemas.DynamipsCreate):
async def create_router(project_id: UUID, node_data: schemas.DynamipsCreate) -> schemas.Dynamips:
"""
Create a new Dynamips router.
"""
@ -81,31 +81,31 @@ async def create_router(project_id: UUID, node_data: schemas.DynamipsCreate):
node_type="dynamips",
)
await dynamips_manager.update_vm_settings(vm, node_data)
return vm.__json__()
return vm.asdict()
@router.get("/{node_id}", response_model=schemas.Dynamips)
def get_router(node: Router = Depends(dep_node)):
def get_router(node: Router = Depends(dep_node)) -> schemas.Dynamips:
"""
Return Dynamips router.
"""
return node.__json__()
return node.asdict()
@router.put("/{node_id}", response_model=schemas.Dynamips)
async def update_router(node_data: schemas.DynamipsUpdate, node: Router = Depends(dep_node)):
async def update_router(node_data: schemas.DynamipsUpdate, node: Router = Depends(dep_node)) -> schemas.Dynamips:
"""
Update a Dynamips router.
"""
await Dynamips.instance().update_vm_settings(node, jsonable_encoder(node_data, exclude_unset=True))
node.updated()
return node.__json__()
return node.asdict()
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_router(node: Router = Depends(dep_node)):
async def delete_router(node: Router = Depends(dep_node)) -> None:
"""
Delete a Dynamips router.
"""
@ -114,7 +114,7 @@ async def delete_router(node: Router = Depends(dep_node)):
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
async def start_router(node: Router = Depends(dep_node)):
async def start_router(node: Router = Depends(dep_node)) -> None:
"""
Start a Dynamips router.
"""
@ -127,7 +127,7 @@ async def start_router(node: Router = Depends(dep_node)):
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
async def stop_router(node: Router = Depends(dep_node)):
async def stop_router(node: Router = Depends(dep_node)) -> None:
"""
Stop a Dynamips router.
"""
@ -136,13 +136,13 @@ async def stop_router(node: Router = Depends(dep_node)):
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
async def suspend_router(node: Router = Depends(dep_node)):
async def suspend_router(node: Router = Depends(dep_node)) -> None:
await node.suspend()
@router.post("/{node_id}/resume", status_code=status.HTTP_204_NO_CONTENT)
async def resume_router(node: Router = Depends(dep_node)):
async def resume_router(node: Router = Depends(dep_node)) -> None:
"""
Resume a suspended Dynamips router.
"""
@ -151,7 +151,7 @@ async def resume_router(node: Router = Depends(dep_node)):
@router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT)
async def reload_router(node: Router = Depends(dep_node)):
async def reload_router(node: Router = Depends(dep_node)) -> None:
"""
Reload a suspended Dynamips router.
"""
@ -164,14 +164,19 @@ async def reload_router(node: Router = Depends(dep_node)):
status_code=status.HTTP_201_CREATED,
response_model=schemas.UDPNIO,
)
async def create_nio(adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: Router = Depends(dep_node)):
async def create_nio(
adapter_number: int,
port_number: int,
nio_data: schemas.UDPNIO,
node: Router = Depends(dep_node)
) -> schemas.UDPNIO:
"""
Add a NIO (Network Input/Output) to the node.
"""
nio = await Dynamips.instance().create_nio(node, jsonable_encoder(nio_data, exclude_unset=True))
await node.slot_add_nio_binding(adapter_number, port_number, nio)
return nio.__json__()
return nio.asdict()
@router.put(
@ -179,7 +184,12 @@ async def create_nio(adapter_number: int, port_number: int, nio_data: schemas.UD
status_code=status.HTTP_201_CREATED,
response_model=schemas.UDPNIO,
)
async def update_nio(adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: Router = Depends(dep_node)):
async def update_nio(
adapter_number: int,
port_number: int,
nio_data: schemas.UDPNIO,
node: Router = Depends(dep_node)
) -> schemas.UDPNIO:
"""
Update a NIO (Network Input/Output) on the node.
"""
@ -188,11 +198,11 @@ async def update_nio(adapter_number: int, port_number: int, nio_data: schemas.UD
if nio_data.filters:
nio.filters = nio_data.filters
await node.slot_update_nio_binding(adapter_number, port_number, nio)
return nio.__json__()
return nio.asdict()
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
async def delete_nio(adapter_number: int, port_number: int, node: Router = Depends(dep_node)):
async def delete_nio(adapter_number: int, port_number: int, node: Router = Depends(dep_node)) -> None:
"""
Delete a NIO (Network Input/Output) from the node.
"""
@ -203,8 +213,11 @@ async def delete_nio(adapter_number: int, port_number: int, node: Router = Depen
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
async def start_capture(
adapter_number: int, port_number: int, node_capture_data: schemas.NodeCapture, node: Router = Depends(dep_node)
):
adapter_number: int,
port_number: int,
node_capture_data: schemas.NodeCapture,
node: Router = Depends(dep_node)
) -> dict:
"""
Start a packet capture on the node.
"""
@ -227,7 +240,7 @@ async def start_capture(
@router.post(
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT
)
async def stop_capture(adapter_number: int, port_number: int, node: Router = Depends(dep_node)):
async def stop_capture(adapter_number: int, port_number: int, node: Router = Depends(dep_node)) -> None:
"""
Stop a packet capture on the node.
"""
@ -236,7 +249,11 @@ async def stop_capture(adapter_number: int, port_number: int, node: Router = Dep
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
async def stream_pcap_file(adapter_number: int, port_number: int, node: Router = Depends(dep_node)):
async def stream_pcap_file(
adapter_number: int,
port_number: int,
node: Router = Depends(dep_node)
) -> StreamingResponse:
"""
Stream the pcap capture file.
"""
@ -266,18 +283,18 @@ async def get_auto_idlepc(node: Router = Depends(dep_node)) -> dict:
return {"idlepc": idlepc}
@router.post("/{node_id}/duplicate", status_code=status.HTTP_201_CREATED)
async def duplicate_router(destination_node_id: UUID, node: Router = Depends(dep_node)):
@router.post("/{node_id}/duplicate", response_model=schemas.Dynamips, status_code=status.HTTP_201_CREATED)
async def duplicate_router(destination_node_id: UUID, node: Router = Depends(dep_node)) -> schemas.Dynamips:
"""
Duplicate a router.
"""
new_node = await Dynamips.instance().duplicate_node(node.id, str(destination_node_id))
return new_node.__json__()
return new_node.asdict()
@router.websocket("/{node_id}/console/ws")
async def console_ws(websocket: WebSocket, node: Router = Depends(dep_node)):
async def console_ws(websocket: WebSocket, node: Router = Depends(dep_node)) -> None:
"""
Console WebSocket.
"""
@ -286,6 +303,6 @@ async def console_ws(websocket: WebSocket, node: Router = Depends(dep_node)):
@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
async def reset_console(node: Router = Depends(dep_node)):
async def reset_console(node: Router = Depends(dep_node)) -> None:
await node.reset_console()

View File

@ -20,7 +20,7 @@ API routes for Ethernet hub nodes.
import os
from fastapi import APIRouter, Depends, Body, status
from fastapi import APIRouter, Depends, Body, Path, status
from fastapi.encoders import jsonable_encoder
from fastapi.responses import StreamingResponse
from uuid import UUID
@ -34,7 +34,7 @@ responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find
router = APIRouter(responses=responses)
def dep_node(project_id: UUID, node_id: UUID):
def dep_node(project_id: UUID, node_id: UUID) -> EthernetHub:
"""
Dependency to retrieve a node.
"""
@ -50,7 +50,7 @@ def dep_node(project_id: UUID, node_id: UUID):
status_code=status.HTTP_201_CREATED,
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Ethernet hub node"}},
)
async def create_ethernet_hub(project_id: UUID, node_data: schemas.EthernetHubCreate):
async def create_ethernet_hub(project_id: UUID, node_data: schemas.EthernetHubCreate) -> schemas.EthernetHub:
"""
Create a new Ethernet hub.
"""
@ -65,32 +65,35 @@ async def create_ethernet_hub(project_id: UUID, node_data: schemas.EthernetHubCr
node_type="ethernet_hub",
ports=node_data.get("ports_mapping"),
)
return node.__json__()
return node.asdict()
@router.get("/{node_id}", response_model=schemas.EthernetHub)
def get_ethernet_hub(node: EthernetHub = Depends(dep_node)):
def get_ethernet_hub(node: EthernetHub = Depends(dep_node)) -> schemas.EthernetHub:
"""
Return an Ethernet hub.
"""
return node.__json__()
return node.asdict()
@router.post("/{node_id}/duplicate", response_model=schemas.EthernetHub, status_code=status.HTTP_201_CREATED)
async def duplicate_ethernet_hub(
destination_node_id: UUID = Body(..., embed=True), node: EthernetHub = Depends(dep_node)
):
) -> schemas.EthernetHub:
"""
Duplicate an Ethernet hub.
"""
new_node = await Dynamips.instance().duplicate_node(node.id, str(destination_node_id))
return new_node.__json__()
return new_node.asdict()
@router.put("/{node_id}", response_model=schemas.EthernetHub)
async def update_ethernet_hub(node_data: schemas.EthernetHubUpdate, node: EthernetHub = Depends(dep_node)):
async def update_ethernet_hub(
node_data: schemas.EthernetHubUpdate,
node: EthernetHub = Depends(dep_node)
) -> schemas.EthernetHub:
"""
Update an Ethernet hub.
"""
@ -101,11 +104,11 @@ async def update_ethernet_hub(node_data: schemas.EthernetHubUpdate, node: Ethern
if "ports_mapping" in node_data:
node.ports_mapping = node_data["ports_mapping"]
node.updated()
return node.__json__()
return node.asdict()
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_ethernet_hub(node: EthernetHub = Depends(dep_node)):
async def delete_ethernet_hub(node: EthernetHub = Depends(dep_node)) -> None:
"""
Delete an Ethernet hub.
"""
@ -114,7 +117,7 @@ async def delete_ethernet_hub(node: EthernetHub = Depends(dep_node)):
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
def start_ethernet_hub(node: EthernetHub = Depends(dep_node)):
def start_ethernet_hub(node: EthernetHub = Depends(dep_node)) -> None:
"""
Start an Ethernet hub.
This endpoint results in no action since Ethernet hub nodes are always on.
@ -124,7 +127,7 @@ def start_ethernet_hub(node: EthernetHub = Depends(dep_node)):
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
def stop_ethernet_hub(node: EthernetHub = Depends(dep_node)):
def stop_ethernet_hub(node: EthernetHub = Depends(dep_node)) -> None:
"""
Stop an Ethernet hub.
This endpoint results in no action since Ethernet hub nodes are always on.
@ -134,7 +137,7 @@ def stop_ethernet_hub(node: EthernetHub = Depends(dep_node)):
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
def suspend_ethernet_hub(node: EthernetHub = Depends(dep_node)):
def suspend_ethernet_hub(node: EthernetHub = Depends(dep_node)) -> None:
"""
Suspend an Ethernet hub.
This endpoint results in no action since Ethernet hub nodes are always on.
@ -149,8 +152,12 @@ def suspend_ethernet_hub(node: EthernetHub = Depends(dep_node)):
response_model=schemas.UDPNIO,
)
async def create_nio(
adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: EthernetHub = Depends(dep_node)
):
*,
adapter_number: int = Path(..., ge=0, le=0),
port_number: int,
nio_data: schemas.UDPNIO,
node: EthernetHub = Depends(dep_node)
) -> schemas.UDPNIO:
"""
Add a NIO (Network Input/Output) to the node.
The adapter number on the hub is always 0.
@ -158,11 +165,16 @@ async def create_nio(
nio = await Dynamips.instance().create_nio(node, jsonable_encoder(nio_data, exclude_unset=True))
await node.add_nio(nio, port_number)
return nio.__json__()
return nio.asdict()
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
async def delete_nio(adapter_number: int, port_number: int, node: EthernetHub = Depends(dep_node)):
async def delete_nio(
*,
adapter_number: int = Path(..., ge=0, le=0),
port_number: int,
node: EthernetHub = Depends(dep_node)
) -> None:
"""
Delete a NIO (Network Input/Output) from the node.
The adapter number on the hub is always 0.
@ -174,8 +186,12 @@ async def delete_nio(adapter_number: int, port_number: int, node: EthernetHub =
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
async def start_capture(
adapter_number: int, port_number: int, node_capture_data: schemas.NodeCapture, node: EthernetHub = Depends(dep_node)
):
*,
adapter_number: int = Path(..., ge=0, le=0),
port_number: int,
node_capture_data: schemas.NodeCapture,
node: EthernetHub = Depends(dep_node)
) -> dict:
"""
Start a packet capture on the node.
The adapter number on the hub is always 0.
@ -189,7 +205,12 @@ async def start_capture(
@router.post(
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT
)
async def stop_capture(adapter_number: int, port_number: int, node: EthernetHub = Depends(dep_node)):
async def stop_capture(
*,
adapter_number: int = Path(..., ge=0, le=0),
port_number: int,
node: EthernetHub = Depends(dep_node)
) -> None:
"""
Stop a packet capture on the node.
The adapter number on the hub is always 0.
@ -199,7 +220,12 @@ async def stop_capture(adapter_number: int, port_number: int, node: EthernetHub
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
async def stream_pcap_file(adapter_number: int, port_number: int, node: EthernetHub = Depends(dep_node)):
async def stream_pcap_file(
*,
adapter_number: int = Path(..., ge=0, le=0),
port_number: int,
node: EthernetHub = Depends(dep_node)
) -> StreamingResponse:
"""
Stream the pcap capture file.
The adapter number on the hub is always 0.

View File

@ -20,7 +20,7 @@ API routes for Ethernet switch nodes.
import os
from fastapi import APIRouter, Depends, Body, status
from fastapi import APIRouter, Depends, Body, Path, status
from fastapi.encoders import jsonable_encoder
from fastapi.responses import StreamingResponse
from uuid import UUID
@ -34,7 +34,7 @@ responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find
router = APIRouter(responses=responses)
def dep_node(project_id: UUID, node_id: UUID):
def dep_node(project_id: UUID, node_id: UUID) -> EthernetSwitch:
"""
Dependency to retrieve a node.
"""
@ -50,7 +50,7 @@ def dep_node(project_id: UUID, node_id: UUID):
status_code=status.HTTP_201_CREATED,
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Ethernet switch node"}},
)
async def create_ethernet_switch(project_id: UUID, node_data: schemas.EthernetSwitchCreate):
async def create_ethernet_switch(project_id: UUID, node_data: schemas.EthernetSwitchCreate) -> schemas.EthernetSwitch:
"""
Create a new Ethernet switch.
"""
@ -68,29 +68,33 @@ async def create_ethernet_switch(project_id: UUID, node_data: schemas.EthernetSw
ports=node_data.get("ports_mapping"),
)
return node.__json__()
return node.asdict()
@router.get("/{node_id}", response_model=schemas.EthernetSwitch)
def get_ethernet_switch(node: EthernetSwitch = Depends(dep_node)):
def get_ethernet_switch(node: EthernetSwitch = Depends(dep_node)) -> schemas.EthernetSwitch:
return node.__json__()
return node.asdict()
@router.post("/{node_id}/duplicate", response_model=schemas.EthernetSwitch, status_code=status.HTTP_201_CREATED)
async def duplicate_ethernet_switch(
destination_node_id: UUID = Body(..., embed=True), node: EthernetSwitch = Depends(dep_node)
):
destination_node_id: UUID = Body(..., embed=True),
node: EthernetSwitch = Depends(dep_node)
) -> schemas.EthernetSwitch:
"""
Duplicate an Ethernet switch.
"""
new_node = await Dynamips.instance().duplicate_node(node.id, str(destination_node_id))
return new_node.__json__()
return new_node.asdict()
@router.put("/{node_id}", response_model=schemas.EthernetSwitch)
async def update_ethernet_switch(node_data: schemas.EthernetSwitchUpdate, node: EthernetSwitch = Depends(dep_node)):
async def update_ethernet_switch(
node_data: schemas.EthernetSwitchUpdate,
node: EthernetSwitch = Depends(dep_node)
) -> schemas.EthernetSwitch:
"""
Update an Ethernet switch.
"""
@ -104,11 +108,11 @@ async def update_ethernet_switch(node_data: schemas.EthernetSwitchUpdate, node:
if "console_type" in node_data:
node.console_type = node_data["console_type"]
node.updated()
return node.__json__()
return node.asdict()
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_ethernet_switch(node: EthernetSwitch = Depends(dep_node)):
async def delete_ethernet_switch(node: EthernetSwitch = Depends(dep_node)) -> None:
"""
Delete an Ethernet switch.
"""
@ -117,7 +121,7 @@ async def delete_ethernet_switch(node: EthernetSwitch = Depends(dep_node)):
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
def start_ethernet_switch(node: EthernetSwitch = Depends(dep_node)):
def start_ethernet_switch(node: EthernetSwitch = Depends(dep_node)) -> None:
"""
Start an Ethernet switch.
This endpoint results in no action since Ethernet switch nodes are always on.
@ -127,7 +131,7 @@ def start_ethernet_switch(node: EthernetSwitch = Depends(dep_node)):
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
def stop_ethernet_switch(node: EthernetSwitch = Depends(dep_node)):
def stop_ethernet_switch(node: EthernetSwitch = Depends(dep_node)) -> None:
"""
Stop an Ethernet switch.
This endpoint results in no action since Ethernet switch nodes are always on.
@ -137,7 +141,7 @@ def stop_ethernet_switch(node: EthernetSwitch = Depends(dep_node)):
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
def suspend_ethernet_switch(node: EthernetSwitch = Depends(dep_node)):
def suspend_ethernet_switch(node: EthernetSwitch = Depends(dep_node)) -> None:
"""
Suspend an Ethernet switch.
This endpoint results in no action since Ethernet switch nodes are always on.
@ -152,16 +156,25 @@ def suspend_ethernet_switch(node: EthernetSwitch = Depends(dep_node)):
response_model=schemas.UDPNIO,
)
async def create_nio(
adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: EthernetSwitch = Depends(dep_node)
):
*,
adapter_number: int = Path(..., ge=0, le=0),
port_number: int,
nio_data: schemas.UDPNIO,
node: EthernetSwitch = Depends(dep_node)
) -> schemas.UDPNIO:
nio = await Dynamips.instance().create_nio(node, jsonable_encoder(nio_data, exclude_unset=True))
await node.add_nio(nio, port_number)
return nio.__json__()
return nio.asdict()
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
async def delete_nio(adapter_number: int, port_number: int, node: EthernetSwitch = Depends(dep_node)):
async def delete_nio(
*,
adapter_number: int = Path(..., ge=0, le=0),
port_number: int,
node: EthernetSwitch = Depends(dep_node)
) -> None:
"""
Delete a NIO (Network Input/Output) from the node.
The adapter number on the switch is always 0.
@ -173,11 +186,12 @@ async def delete_nio(adapter_number: int, port_number: int, node: EthernetSwitch
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
async def start_capture(
adapter_number: int,
port_number: int,
node_capture_data: schemas.NodeCapture,
node: EthernetSwitch = Depends(dep_node),
):
*,
adapter_number: int = Path(..., ge=0, le=0),
port_number: int,
node_capture_data: schemas.NodeCapture,
node: EthernetSwitch = Depends(dep_node),
) -> dict:
"""
Start a packet capture on the node.
The adapter number on the switch is always 0.
@ -191,7 +205,12 @@ async def start_capture(
@router.post(
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT
)
async def stop_capture(adapter_number: int, port_number: int, node: EthernetSwitch = Depends(dep_node)):
async def stop_capture(
*,
adapter_number: int = Path(..., ge=0, le=0),
port_number: int,
node: EthernetSwitch = Depends(dep_node)
) -> None:
"""
Stop a packet capture on the node.
The adapter number on the switch is always 0.
@ -201,7 +220,12 @@ async def stop_capture(adapter_number: int, port_number: int, node: EthernetSwit
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
async def stream_pcap_file(adapter_number: int, port_number: int, node: EthernetSwitch = Depends(dep_node)):
async def stream_pcap_file(
*,
adapter_number: int = Path(..., ge=0, le=0),
port_number: int,
node: EthernetSwitch = Depends(dep_node)
) -> StreamingResponse:
"""
Stream the pcap capture file.
The adapter number on the switch is always 0.

View File

@ -20,7 +20,7 @@ API routes for Frame Relay switch nodes.
import os
from fastapi import APIRouter, Depends, Body, status
from fastapi import APIRouter, Depends, Body, Path, status
from fastapi.encoders import jsonable_encoder
from fastapi.responses import StreamingResponse
from uuid import UUID
@ -34,7 +34,7 @@ responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find
router = APIRouter(responses=responses)
def dep_node(project_id: UUID, node_id: UUID):
def dep_node(project_id: UUID, node_id: UUID) -> FrameRelaySwitch:
"""
Dependency to retrieve a node.
"""
@ -50,7 +50,10 @@ def dep_node(project_id: UUID, node_id: UUID):
status_code=status.HTTP_201_CREATED,
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Frame Relay switch node"}},
)
async def create_frame_relay_switch(project_id: UUID, node_data: schemas.FrameRelaySwitchCreate):
async def create_frame_relay_switch(
project_id: UUID,
node_data: schemas.FrameRelaySwitchCreate
) -> schemas.FrameRelaySwitch:
"""
Create a new Frame Relay switch node.
"""
@ -65,34 +68,36 @@ async def create_frame_relay_switch(project_id: UUID, node_data: schemas.FrameRe
node_type="frame_relay_switch",
mappings=node_data.get("mappings"),
)
return node.__json__()
return node.asdict()
@router.get("/{node_id}", response_model=schemas.FrameRelaySwitch)
def get_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)):
def get_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)) -> schemas.FrameRelaySwitch:
"""
Return a Frame Relay switch node.
"""
return node.__json__()
return node.asdict()
@router.post("/{node_id}/duplicate", response_model=schemas.FrameRelaySwitch, status_code=status.HTTP_201_CREATED)
async def duplicate_frame_relay_switch(
destination_node_id: UUID = Body(..., embed=True), node: FrameRelaySwitch = Depends(dep_node)
):
destination_node_id: UUID = Body(..., embed=True),
node: FrameRelaySwitch = Depends(dep_node)
) -> schemas.FrameRelaySwitch:
"""
Duplicate a Frame Relay switch node.
"""
new_node = await Dynamips.instance().duplicate_node(node.id, str(destination_node_id))
return new_node.__json__()
return new_node.asdict()
@router.put("/{node_id}", response_model=schemas.FrameRelaySwitch)
async def update_frame_relay_switch(
node_data: schemas.FrameRelaySwitchUpdate, node: FrameRelaySwitch = Depends(dep_node)
):
node_data: schemas.FrameRelaySwitchUpdate,
node: FrameRelaySwitch = Depends(dep_node)
) -> schemas.FrameRelaySwitch:
"""
Update an Frame Relay switch node.
"""
@ -103,11 +108,11 @@ async def update_frame_relay_switch(
if "mappings" in node_data:
node.mappings = node_data["mappings"]
node.updated()
return node.__json__()
return node.asdict()
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)):
async def delete_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)) -> None:
"""
Delete a Frame Relay switch node.
"""
@ -116,7 +121,7 @@ async def delete_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)):
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
def start_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)):
def start_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)) -> None:
"""
Start a Frame Relay switch node.
This endpoint results in no action since Frame Relay switch nodes are always on.
@ -126,7 +131,7 @@ def start_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)):
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
def stop_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)):
def stop_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)) -> None:
"""
Stop a Frame Relay switch node.
This endpoint results in no action since Frame Relay switch nodes are always on.
@ -136,7 +141,7 @@ def stop_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)):
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
def suspend_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)):
def suspend_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)) -> None:
"""
Suspend a Frame Relay switch node.
This endpoint results in no action since Frame Relay switch nodes are always on.
@ -151,8 +156,12 @@ def suspend_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)):
response_model=schemas.UDPNIO,
)
async def create_nio(
adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: FrameRelaySwitch = Depends(dep_node)
):
*,
adapter_number: int = Path(..., ge=0, le=0),
port_number: int,
nio_data: schemas.UDPNIO,
node: FrameRelaySwitch = Depends(dep_node)
) -> schemas.UDPNIO:
"""
Add a NIO (Network Input/Output) to the node.
The adapter number on the switch is always 0.
@ -160,11 +169,16 @@ async def create_nio(
nio = await Dynamips.instance().create_nio(node, jsonable_encoder(nio_data, exclude_unset=True))
await node.add_nio(nio, port_number)
return nio.__json__()
return nio.asdict()
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
async def delete_nio(adapter_number: int, port_number: int, node: FrameRelaySwitch = Depends(dep_node)):
async def delete_nio(
*,
adapter_number: int = Path(..., ge=0, le=0),
port_number: int,
node: FrameRelaySwitch = Depends(dep_node)
) -> None:
"""
Remove a NIO (Network Input/Output) from the node.
The adapter number on the switch is always 0.
@ -176,11 +190,12 @@ async def delete_nio(adapter_number: int, port_number: int, node: FrameRelaySwit
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
async def start_capture(
adapter_number: int,
port_number: int,
node_capture_data: schemas.NodeCapture,
node: FrameRelaySwitch = Depends(dep_node),
):
*,
adapter_number: int = Path(..., ge=0, le=0),
port_number: int,
node_capture_data: schemas.NodeCapture,
node: FrameRelaySwitch = Depends(dep_node),
) -> dict:
"""
Start a packet capture on the node.
The adapter number on the switch is always 0.
@ -194,7 +209,12 @@ async def start_capture(
@router.post(
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT
)
async def stop_capture(adapter_number: int, port_number: int, node: FrameRelaySwitch = Depends(dep_node)):
async def stop_capture(
*,
adapter_number: int = Path(..., ge=0, le=0),
port_number: int,
node: FrameRelaySwitch = Depends(dep_node)
) -> None:
"""
Stop a packet capture on the node.
The adapter number on the switch is always 0.
@ -204,7 +224,12 @@ async def stop_capture(adapter_number: int, port_number: int, node: FrameRelaySw
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
async def stream_pcap_file(adapter_number: int, port_number: int, node: FrameRelaySwitch = Depends(dep_node)):
async def stream_pcap_file(
*,
adapter_number: int = Path(..., ge=0, le=0),
port_number: int,
node: FrameRelaySwitch = Depends(dep_node)
) -> StreamingResponse:
"""
Stream the pcap capture file.
The adapter number on the hub is always 0.

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

View File

@ -35,7 +35,7 @@ responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find
router = APIRouter(responses=responses)
def dep_node(project_id: UUID, node_id: UUID):
def dep_node(project_id: UUID, node_id: UUID) -> IOUVM:
"""
Dependency to retrieve a node.
"""
@ -51,7 +51,7 @@ def dep_node(project_id: UUID, node_id: UUID):
status_code=status.HTTP_201_CREATED,
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create IOU node"}},
)
async def create_iou_node(project_id: UUID, node_data: schemas.IOUCreate):
async def create_iou_node(project_id: UUID, node_data: schemas.IOUCreate) -> schemas.IOU:
"""
Create a new IOU node.
"""
@ -79,20 +79,20 @@ async def create_iou_node(project_id: UUID, node_data: schemas.IOUCreate):
if node_data.get("use_default_iou_values") and (name == "ram" or name == "nvram"):
continue
setattr(vm, name, value)
return vm.__json__()
return vm.asdict()
@router.get("/{node_id}", response_model=schemas.IOU)
def get_iou_node(node: IOUVM = Depends(dep_node)):
def get_iou_node(node: IOUVM = Depends(dep_node)) -> schemas.IOU:
"""
Return an IOU node.
"""
return node.__json__()
return node.asdict()
@router.put("/{node_id}", response_model=schemas.IOU)
async def update_iou_node(node_data: schemas.IOUUpdate, node: IOUVM = Depends(dep_node)):
async def update_iou_node(node_data: schemas.IOUUpdate, node: IOUVM = Depends(dep_node)) -> schemas.IOU:
"""
Update an IOU node.
"""
@ -109,11 +109,11 @@ async def update_iou_node(node_data: schemas.IOUUpdate, node: IOUVM = Depends(de
# this is important to have the correct NVRAM amount in order to correctly push the configs to the NVRAM
await node.update_default_iou_values()
node.updated()
return node.__json__()
return node.asdict()
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_iou_node(node: IOUVM = Depends(dep_node)):
async def delete_iou_node(node: IOUVM = Depends(dep_node)) -> None:
"""
Delete an IOU node.
"""
@ -122,17 +122,20 @@ async def delete_iou_node(node: IOUVM = Depends(dep_node)):
@router.post("/{node_id}/duplicate", response_model=schemas.IOU, status_code=status.HTTP_201_CREATED)
async def duplicate_iou_node(destination_node_id: UUID = Body(..., embed=True), node: IOUVM = Depends(dep_node)):
async def duplicate_iou_node(
destination_node_id: UUID = Body(..., embed=True),
node: IOUVM = Depends(dep_node)
) -> schemas.IOU:
"""
Duplicate an IOU node.
"""
new_node = await IOU.instance().duplicate_node(node.id, str(destination_node_id))
return new_node.__json__()
return new_node.asdict()
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
async def start_iou_node(start_data: schemas.IOUStart, node: IOUVM = Depends(dep_node)):
async def start_iou_node(start_data: schemas.IOUStart, node: IOUVM = Depends(dep_node)) -> None:
"""
Start an IOU node.
"""
@ -143,11 +146,11 @@ async def start_iou_node(start_data: schemas.IOUStart, node: IOUVM = Depends(dep
setattr(node, name, value)
await node.start()
return node.__json__()
return node.asdict()
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
async def stop_iou_node(node: IOUVM = Depends(dep_node)):
async def stop_iou_node(node: IOUVM = Depends(dep_node)) -> None:
"""
Stop an IOU node.
"""
@ -156,7 +159,7 @@ async def stop_iou_node(node: IOUVM = Depends(dep_node)):
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
def suspend_iou_node(node: IOUVM = Depends(dep_node)):
def suspend_iou_node(node: IOUVM = Depends(dep_node)) -> None:
"""
Suspend an IOU node.
Does nothing since IOU doesn't support being suspended.
@ -166,7 +169,7 @@ def suspend_iou_node(node: IOUVM = Depends(dep_node)):
@router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT)
async def reload_iou_node(node: IOUVM = Depends(dep_node)):
async def reload_iou_node(node: IOUVM = Depends(dep_node)) -> None:
"""
Reload an IOU node.
"""
@ -184,14 +187,14 @@ async def create_iou_node_nio(
port_number: int,
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
node: IOUVM = Depends(dep_node),
):
) -> Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO]:
"""
Add a NIO (Network Input/Output) to the node.
"""
nio = IOU.instance().create_nio(jsonable_encoder(nio_data, exclude_unset=True))
await node.adapter_add_nio_binding(adapter_number, port_number, nio)
return nio.__json__()
return nio.asdict()
@router.put(
@ -204,7 +207,7 @@ async def update_iou_node_nio(
port_number: int,
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
node: IOUVM = Depends(dep_node),
):
) -> Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO]:
"""
Update a NIO (Network Input/Output) on the node.
"""
@ -213,11 +216,11 @@ async def update_iou_node_nio(
if nio_data.filters:
nio.filters = nio_data.filters
await node.adapter_update_nio_binding(adapter_number, port_number, nio)
return nio.__json__()
return nio.asdict()
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
async def delete_iou_node_nio(adapter_number: int, port_number: int, node: IOUVM = Depends(dep_node)):
async def delete_iou_node_nio(adapter_number: int, port_number: int, node: IOUVM = Depends(dep_node)) -> None:
"""
Delete a NIO (Network Input/Output) from the node.
"""
@ -227,8 +230,11 @@ async def delete_iou_node_nio(adapter_number: int, port_number: int, node: IOUVM
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
async def start_iou_node_capture(
adapter_number: int, port_number: int, node_capture_data: schemas.NodeCapture, node: IOUVM = Depends(dep_node)
):
adapter_number: int,
port_number: int,
node_capture_data: schemas.NodeCapture,
node: IOUVM = Depends(dep_node)
) -> dict:
"""
Start a packet capture on the node.
"""
@ -241,7 +247,7 @@ async def start_iou_node_capture(
@router.post(
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT
)
async def stop_iou_node_capture(adapter_number: int, port_number: int, node: IOUVM = Depends(dep_node)):
async def stop_iou_node_capture(adapter_number: int, port_number: int, node: IOUVM = Depends(dep_node)) -> None:
"""
Stop a packet capture on the node.
"""
@ -250,7 +256,11 @@ async def stop_iou_node_capture(adapter_number: int, port_number: int, node: IOU
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
async def stream_pcap_file(adapter_number: int, port_number: int, node: IOUVM = Depends(dep_node)):
async def stream_pcap_file(
adapter_number: int,
port_number: int,
node: IOUVM = Depends(dep_node)
) -> StreamingResponse:
"""
Stream the pcap capture file.
"""
@ -261,7 +271,7 @@ async def stream_pcap_file(adapter_number: int, port_number: int, node: IOUVM =
@router.websocket("/{node_id}/console/ws")
async def console_ws(websocket: WebSocket, node: IOUVM = Depends(dep_node)):
async def console_ws(websocket: WebSocket, node: IOUVM = Depends(dep_node)) -> None:
"""
Console WebSocket.
"""
@ -270,6 +280,6 @@ async def console_ws(websocket: WebSocket, node: IOUVM = Depends(dep_node)):
@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
async def reset_console(node: IOUVM = Depends(dep_node)):
async def reset_console(node: IOUVM = Depends(dep_node)) -> None:
await node.reset_console()

View File

@ -20,7 +20,7 @@ API routes for NAT nodes.
import os
from fastapi import APIRouter, Depends, status
from fastapi import APIRouter, Depends, Path, status
from fastapi.encoders import jsonable_encoder
from fastapi.responses import StreamingResponse
from typing import Union
@ -35,7 +35,7 @@ responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find
router = APIRouter(responses=responses)
def dep_node(project_id: UUID, node_id: UUID):
def dep_node(project_id: UUID, node_id: UUID) -> Nat:
"""
Dependency to retrieve a node.
"""
@ -51,7 +51,7 @@ def dep_node(project_id: UUID, node_id: UUID):
status_code=status.HTTP_201_CREATED,
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create NAT node"}},
)
async def create_nat_node(project_id: UUID, node_data: schemas.NATCreate):
async def create_nat_node(project_id: UUID, node_data: schemas.NATCreate) -> schemas.NAT:
"""
Create a new NAT node.
"""
@ -67,20 +67,20 @@ async def create_nat_node(project_id: UUID, node_data: schemas.NATCreate):
)
node.usage = node_data.get("usage", "")
return node.__json__()
return node.asdict()
@router.get("/{node_id}", response_model=schemas.NAT)
def get_nat_node(node: Nat = Depends(dep_node)):
def get_nat_node(node: Nat = Depends(dep_node)) -> schemas.NAT:
"""
Return a NAT node.
"""
return node.__json__()
return node.asdict()
@router.put("/{node_id}", response_model=schemas.NAT)
def update_nat_node(node_data: schemas.NATUpdate, node: Nat = Depends(dep_node)):
def update_nat_node(node_data: schemas.NATUpdate, node: Nat = Depends(dep_node)) -> schemas.NAT:
"""
Update a NAT node.
"""
@ -90,11 +90,11 @@ def update_nat_node(node_data: schemas.NATUpdate, node: Nat = Depends(dep_node))
if hasattr(node, name) and getattr(node, name) != value:
setattr(node, name, value)
node.updated()
return node.__json__()
return node.asdict()
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_nat_node(node: Nat = Depends(dep_node)):
async def delete_nat_node(node: Nat = Depends(dep_node)) -> None:
"""
Delete a cloud node.
"""
@ -103,7 +103,7 @@ async def delete_nat_node(node: Nat = Depends(dep_node)):
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
async def start_nat_node(node: Nat = Depends(dep_node)):
async def start_nat_node(node: Nat = Depends(dep_node)) -> None:
"""
Start a NAT node.
"""
@ -112,7 +112,7 @@ async def start_nat_node(node: Nat = Depends(dep_node)):
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
async def stop_nat_node(node: Nat = Depends(dep_node)):
async def stop_nat_node(node: Nat = Depends(dep_node)) -> None:
"""
Stop a NAT node.
This endpoint results in no action since cloud nodes cannot be stopped.
@ -122,7 +122,7 @@ async def stop_nat_node(node: Nat = Depends(dep_node)):
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
async def suspend_nat_node(node: Nat = Depends(dep_node)):
async def suspend_nat_node(node: Nat = Depends(dep_node)) -> None:
"""
Suspend a NAT node.
This endpoint results in no action since NAT nodes cannot be suspended.
@ -137,11 +137,12 @@ async def suspend_nat_node(node: Nat = Depends(dep_node)):
response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
)
async def create_nat_node_nio(
adapter_number: int,
port_number: int,
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
node: Nat = Depends(dep_node),
):
*,
adapter_number: int = Path(..., ge=0, le=0),
port_number: int,
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
node: Nat = Depends(dep_node),
) -> Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO]:
"""
Add a NIO (Network Input/Output) to the node.
The adapter number on the cloud is always 0.
@ -149,7 +150,7 @@ async def create_nat_node_nio(
nio = Builtin.instance().create_nio(jsonable_encoder(nio_data, exclude_unset=True))
await node.add_nio(nio, port_number)
return nio.__json__()
return nio.asdict()
@router.put(
@ -158,11 +159,12 @@ async def create_nat_node_nio(
response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
)
async def update_nat_node_nio(
adapter_number: int,
port_number: int,
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
node: Nat = Depends(dep_node),
):
*,
adapter_number: int = Path(..., ge=0, le=0),
port_number: int,
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
node: Nat = Depends(dep_node),
) -> Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO]:
"""
Update a NIO (Network Input/Output) to the node.
The adapter number on the cloud is always 0.
@ -172,11 +174,16 @@ async def update_nat_node_nio(
if nio_data.filters:
nio.filters = nio_data.filters
await node.update_nio(port_number, nio)
return nio.__json__()
return nio.asdict()
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
async def delete_nat_node_nio(adapter_number: int, port_number: int, node: Nat = Depends(dep_node)):
async def delete_nat_node_nio(
*,
adapter_number: int = Path(..., ge=0, le=0),
port_number: int,
node: Nat = Depends(dep_node)
) -> None:
"""
Remove a NIO (Network Input/Output) from the node.
The adapter number on the cloud is always 0.
@ -187,8 +194,12 @@ async def delete_nat_node_nio(adapter_number: int, port_number: int, node: Nat =
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
async def start_nat_node_capture(
adapter_number: int, port_number: int, node_capture_data: schemas.NodeCapture, node: Nat = Depends(dep_node)
):
*,
adapter_number: int = Path(..., ge=0, le=0),
port_number: int,
node_capture_data: schemas.NodeCapture,
node: Nat = Depends(dep_node)
) -> dict:
"""
Start a packet capture on the node.
The adapter number on the cloud is always 0.
@ -202,7 +213,12 @@ async def start_nat_node_capture(
@router.post(
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT
)
async def stop_nat_node_capture(adapter_number: int, port_number: int, node: Nat = Depends(dep_node)):
async def stop_nat_node_capture(
*,
adapter_number: int = Path(..., ge=0, le=0),
port_number: int,
node: Nat = Depends(dep_node)
):
"""
Stop a packet capture on the node.
The adapter number on the cloud is always 0.
@ -212,7 +228,12 @@ async def stop_nat_node_capture(adapter_number: int, port_number: int, node: Nat
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
async def stream_pcap_file(adapter_number: int, port_number: int, node: Nat = Depends(dep_node)):
async def stream_pcap_file(
*,
adapter_number: int = Path(..., ge=0, le=0),
port_number: int,
node: Nat = Depends(dep_node)
) -> StreamingResponse:
"""
Stream the pcap capture file.
The adapter number on the cloud is always 0.

View File

@ -31,7 +31,7 @@ router = APIRouter()
@router.websocket("/notifications/ws")
async def notification_ws(websocket: WebSocket):
async def notification_ws(websocket: WebSocket) -> None:
"""
Receive project notifications about the project from WebSocket.
"""

View File

@ -41,7 +41,7 @@ router = APIRouter()
_notifications_listening = {}
def dep_project(project_id: UUID):
def dep_project(project_id: UUID) -> Project:
"""
Dependency to retrieve a project.
"""
@ -52,17 +52,17 @@ def dep_project(project_id: UUID):
@router.get("/projects", response_model=List[schemas.Project])
def get_compute_projects():
def get_compute_projects() -> List[schemas.Project]:
"""
Get all projects opened on the compute.
"""
pm = ProjectManager.instance()
return [p.__json__() for p in pm.projects]
return [p.asdict() for p in pm.projects]
@router.post("/projects", status_code=status.HTTP_201_CREATED, response_model=schemas.Project)
def create_compute_project(project_data: schemas.ProjectCreate):
def create_compute_project(project_data: schemas.ProjectCreate) -> schemas.Project:
"""
Create a new project on the compute.
"""
@ -75,30 +75,33 @@ def create_compute_project(project_data: schemas.ProjectCreate):
project_id=project_data.get("project_id"),
variables=project_data.get("variables", None),
)
return project.__json__()
return project.asdict()
@router.put("/projects/{project_id}", response_model=schemas.Project)
async def update_compute_project(project_data: schemas.ProjectUpdate, project: Project = Depends(dep_project)):
async def update_compute_project(
project_data: schemas.ProjectUpdate,
project: Project = Depends(dep_project)
) -> schemas.Project:
"""
Update project on the compute.
"""
await project.update(variables=project_data.variables)
return project.__json__()
return project.asdict()
@router.get("/projects/{project_id}", response_model=schemas.Project)
def get_compute_project(project: Project = Depends(dep_project)):
def get_compute_project(project: Project = Depends(dep_project)) -> schemas.Project:
"""
Return a project from the compute.
"""
return project.__json__()
return project.asdict()
@router.post("/projects/{project_id}/close", status_code=status.HTTP_204_NO_CONTENT)
async def close_compute_project(project: Project = Depends(dep_project)):
async def close_compute_project(project: Project = Depends(dep_project)) -> None:
"""
Close a project on the compute.
"""
@ -116,7 +119,7 @@ async def close_compute_project(project: Project = Depends(dep_project)):
@router.delete("/projects/{project_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_compute_project(project: Project = Depends(dep_project)):
async def delete_compute_project(project: Project = Depends(dep_project)) -> None:
"""
Delete project from the compute.
"""
@ -152,8 +155,8 @@ async def delete_compute_project(project: Project = Depends(dep_project)):
# while True:
# try:
# (action, msg) = await asyncio.wait_for(queue.get(), 5)
# if hasattr(msg, "__json__"):
# msg = json.dumps({"action": action, "event": msg.__json__()}, sort_keys=True)
# if hasattr(msg, "asdict"):
# msg = json.dumps({"action": action, "event": msg.asdict()}, sort_keys=True)
# else:
# msg = json.dumps({"action": action, "event": msg}, sort_keys=True)
# log.debug("Send notification: %s", msg)
@ -180,7 +183,7 @@ async def delete_compute_project(project: Project = Depends(dep_project)):
@router.get("/projects/{project_id}/files", response_model=List[schemas.ProjectFile])
async def get_compute_project_files(project: Project = Depends(dep_project)):
async def get_compute_project_files(project: Project = Depends(dep_project)) -> List[schemas.ProjectFile]:
"""
Return files belonging to a project.
"""
@ -189,7 +192,7 @@ async def get_compute_project_files(project: Project = Depends(dep_project)):
@router.get("/projects/{project_id}/files/{file_path:path}")
async def get_compute_project_file(file_path: str, project: Project = Depends(dep_project)):
async def get_compute_project_file(file_path: str, project: Project = Depends(dep_project)) -> FileResponse:
"""
Get a file from a project.
"""
@ -208,7 +211,7 @@ async def get_compute_project_file(file_path: str, project: Project = Depends(de
@router.post("/projects/{project_id}/files/{file_path:path}", status_code=status.HTTP_204_NO_CONTENT)
async def write_compute_project_file(file_path: str, request: Request, project: Project = Depends(dep_project)):
async def write_compute_project_file(file_path: str, request: Request, project: Project = Depends(dep_project)) -> None:
path = os.path.normpath(file_path)

View File

@ -19,9 +19,8 @@ API routes for Qemu nodes.
"""
import os
import sys
from fastapi import APIRouter, WebSocket, Depends, Body, status
from fastapi import APIRouter, WebSocket, Depends, Body, Path, status
from fastapi.encoders import jsonable_encoder
from fastapi.responses import StreamingResponse
from uuid import UUID
@ -36,7 +35,7 @@ responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find
router = APIRouter(responses=responses)
def dep_node(project_id: UUID, node_id: UUID):
def dep_node(project_id: UUID, node_id: UUID) -> QemuVM:
"""
Dependency to retrieve a node.
"""
@ -52,7 +51,7 @@ def dep_node(project_id: UUID, node_id: UUID):
status_code=status.HTTP_201_CREATED,
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Qemu node"}},
)
async def create_qemu_node(project_id: UUID, node_data: schemas.QemuCreate):
async def create_qemu_node(project_id: UUID, node_data: schemas.QemuCreate) -> schemas.Qemu:
"""
Create a new Qemu node.
"""
@ -76,20 +75,20 @@ async def create_qemu_node(project_id: UUID, node_data: schemas.QemuCreate):
if hasattr(vm, name) and getattr(vm, name) != value:
setattr(vm, name, value)
return vm.__json__()
return vm.asdict()
@router.get("/{node_id}", response_model=schemas.Qemu)
def get_qemu_node(node: QemuVM = Depends(dep_node)):
def get_qemu_node(node: QemuVM = Depends(dep_node)) -> schemas.Qemu:
"""
Return a Qemu node.
"""
return node.__json__()
return node.asdict()
@router.put("/{node_id}", response_model=schemas.Qemu)
async def update_qemu_node(node_data: schemas.QemuUpdate, node: QemuVM = Depends(dep_node)):
async def update_qemu_node(node_data: schemas.QemuUpdate, node: QemuVM = Depends(dep_node)) -> schemas.Qemu:
"""
Update a Qemu node.
"""
@ -101,11 +100,11 @@ async def update_qemu_node(node_data: schemas.QemuUpdate, node: QemuVM = Depends
if hasattr(node, name) and getattr(node, name) != value:
await node.update_property(name, value)
node.updated()
return node.__json__()
return node.asdict()
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_qemu_node(node: QemuVM = Depends(dep_node)):
async def delete_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
"""
Delete a Qemu node.
"""
@ -114,23 +113,26 @@ async def delete_qemu_node(node: QemuVM = Depends(dep_node)):
@router.post("/{node_id}/duplicate", response_model=schemas.Qemu, status_code=status.HTTP_201_CREATED)
async def duplicate_qemu_node(destination_node_id: UUID = Body(..., embed=True), node: QemuVM = Depends(dep_node)):
async def duplicate_qemu_node(
destination_node_id: UUID = Body(..., embed=True),
node: QemuVM = Depends(dep_node)
) -> schemas.Qemu:
"""
Duplicate a Qemu node.
"""
new_node = await Qemu.instance().duplicate_node(node.id, str(destination_node_id))
return new_node.__json__()
return new_node.asdict()
@router.post("/{node_id}/resize_disk", status_code=status.HTTP_204_NO_CONTENT)
async def resize_qemu_node_disk(node_data: schemas.QemuDiskResize, node: QemuVM = Depends(dep_node)):
async def resize_qemu_node_disk(node_data: schemas.QemuDiskResize, node: QemuVM = Depends(dep_node)) -> None:
await node.resize_disk(node_data.drive_name, node_data.extend)
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
async def start_qemu_node(node: QemuVM = Depends(dep_node)):
async def start_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
"""
Start a Qemu node.
"""
@ -146,7 +148,7 @@ async def start_qemu_node(node: QemuVM = Depends(dep_node)):
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
async def stop_qemu_node(node: QemuVM = Depends(dep_node)):
async def stop_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
"""
Stop a Qemu node.
"""
@ -155,7 +157,7 @@ async def stop_qemu_node(node: QemuVM = Depends(dep_node)):
@router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT)
async def reload_qemu_node(node: QemuVM = Depends(dep_node)):
async def reload_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
"""
Reload a Qemu node.
"""
@ -164,7 +166,7 @@ async def reload_qemu_node(node: QemuVM = Depends(dep_node)):
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
async def suspend_qemu_node(node: QemuVM = Depends(dep_node)):
async def suspend_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
"""
Suspend a Qemu node.
"""
@ -173,7 +175,7 @@ async def suspend_qemu_node(node: QemuVM = Depends(dep_node)):
@router.post("/{node_id}/resume", status_code=status.HTTP_204_NO_CONTENT)
async def resume_qemu_node(node: QemuVM = Depends(dep_node)):
async def resume_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
"""
Resume a Qemu node.
"""
@ -187,8 +189,12 @@ async def resume_qemu_node(node: QemuVM = Depends(dep_node)):
response_model=schemas.UDPNIO,
)
async def create_qemu_node_nio(
adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: QemuVM = Depends(dep_node)
):
*,
adapter_number: int,
port_number: int = Path(..., ge=0, le=0),
nio_data: schemas.UDPNIO,
node: QemuVM = Depends(dep_node)
) -> schemas.UDPNIO:
"""
Add a NIO (Network Input/Output) to the node.
The port number on the Qemu node is always 0.
@ -196,7 +202,7 @@ async def create_qemu_node_nio(
nio = Qemu.instance().create_nio(jsonable_encoder(nio_data, exclude_unset=True))
await node.adapter_add_nio_binding(adapter_number, nio)
return nio.__json__()
return nio.asdict()
@router.put(
@ -205,8 +211,12 @@ async def create_qemu_node_nio(
response_model=schemas.UDPNIO,
)
async def update_qemu_node_nio(
adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: QemuVM = Depends(dep_node)
):
*,
adapter_number: int,
port_number: int = Path(..., ge=0, le=0),
nio_data: schemas.UDPNIO,
node: QemuVM = Depends(dep_node)
) -> schemas.UDPNIO:
"""
Update a NIO (Network Input/Output) on the node.
The port number on the Qemu node is always 0.
@ -218,11 +228,15 @@ async def update_qemu_node_nio(
if nio_data.suspend:
nio.suspend = nio_data.suspend
await node.adapter_update_nio_binding(adapter_number, nio)
return nio.__json__()
return nio.asdict()
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
async def delete_qemu_node_nio(adapter_number: int, port_number: int, node: QemuVM = Depends(dep_node)):
async def delete_qemu_node_nio(
adapter_number: int,
port_number: int = Path(..., ge=0, le=0),
node: QemuVM = Depends(dep_node)
) -> None:
"""
Delete a NIO (Network Input/Output) from the node.
The port number on the Qemu node is always 0.
@ -233,8 +247,12 @@ async def delete_qemu_node_nio(adapter_number: int, port_number: int, node: Qemu
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
async def start_qemu_node_capture(
adapter_number: int, port_number: int, node_capture_data: schemas.NodeCapture, node: QemuVM = Depends(dep_node)
):
*,
adapter_number: int,
port_number: int = Path(..., ge=0, le=0),
node_capture_data: schemas.NodeCapture,
node: QemuVM = Depends(dep_node)
) -> dict:
"""
Start a packet capture on the node.
The port number on the Qemu node is always 0.
@ -248,7 +266,11 @@ async def start_qemu_node_capture(
@router.post(
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT
)
async def stop_qemu_node_capture(adapter_number: int, port_number: int, node: QemuVM = Depends(dep_node)):
async def stop_qemu_node_capture(
adapter_number: int,
port_number: int = Path(..., ge=0, le=0),
node: QemuVM = Depends(dep_node)
) -> None:
"""
Stop a packet capture on the node.
The port number on the Qemu node is always 0.
@ -258,7 +280,10 @@ async def stop_qemu_node_capture(adapter_number: int, port_number: int, node: Qe
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
async def stream_pcap_file(adapter_number: int, port_number: int, node: QemuVM = Depends(dep_node)):
async def stream_pcap_file(
adapter_number: int,
port_number: int = Path(..., ge=0, le=0),
node: QemuVM = Depends(dep_node)) -> StreamingResponse:
"""
Stream the pcap capture file.
The port number on the Qemu node is always 0.
@ -270,7 +295,7 @@ async def stream_pcap_file(adapter_number: int, port_number: int, node: QemuVM =
@router.websocket("/{node_id}/console/ws")
async def console_ws(websocket: WebSocket, node: QemuVM = Depends(dep_node)):
async def console_ws(websocket: WebSocket, node: QemuVM = Depends(dep_node)) -> None:
"""
Console WebSocket.
"""
@ -279,6 +304,6 @@ async def console_ws(websocket: WebSocket, node: QemuVM = Depends(dep_node)):
@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
async def reset_console(node: QemuVM = Depends(dep_node)):
async def reset_console(node: QemuVM = Depends(dep_node)) -> None:
await node.reset_console()

View File

@ -20,7 +20,7 @@ API routes for VirtualBox nodes.
import os
from fastapi import APIRouter, WebSocket, Depends, status
from fastapi import APIRouter, WebSocket, Depends, Path, status
from fastapi.encoders import jsonable_encoder
from fastapi.responses import StreamingResponse
from uuid import UUID
@ -28,7 +28,6 @@ from uuid import UUID
from gns3server import schemas
from gns3server.compute.virtualbox import VirtualBox
from gns3server.compute.virtualbox.virtualbox_error import VirtualBoxError
from gns3server.compute.project_manager import ProjectManager
from gns3server.compute.virtualbox.virtualbox_vm import VirtualBoxVM
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or VirtualBox node"}}
@ -36,7 +35,7 @@ responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find
router = APIRouter(responses=responses)
def dep_node(project_id: UUID, node_id: UUID):
def dep_node(project_id: UUID, node_id: UUID) -> VirtualBoxVM:
"""
Dependency to retrieve a node.
"""
@ -52,7 +51,7 @@ def dep_node(project_id: UUID, node_id: UUID):
status_code=status.HTTP_201_CREATED,
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create VirtualBox node"}},
)
async def create_virtualbox_node(project_id: UUID, node_data: schemas.VirtualBoxCreate):
async def create_virtualbox_node(project_id: UUID, node_data: schemas.VirtualBoxCreate) -> schemas.VirtualBox:
"""
Create a new VirtualBox node.
"""
@ -80,20 +79,23 @@ async def create_virtualbox_node(project_id: UUID, node_data: schemas.VirtualBox
if hasattr(vm, name) and getattr(vm, name) != value:
setattr(vm, name, value)
return vm.__json__()
return vm.asdict()
@router.get("/{node_id}", response_model=schemas.VirtualBox)
def get_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)):
def get_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> schemas.VirtualBox:
"""
Return a VirtualBox node.
"""
return node.__json__()
return node.asdict()
@router.put("/{node_id}", response_model=schemas.VirtualBox)
async def update_virtualbox_node(node_data: schemas.VirtualBoxUpdate, node: VirtualBoxVM = Depends(dep_node)):
async def update_virtualbox_node(
node_data: schemas.VirtualBoxUpdate,
node: VirtualBoxVM = Depends(dep_node)
) -> schemas.VirtualBox:
"""
Update a VirtualBox node.
"""
@ -131,11 +133,11 @@ async def update_virtualbox_node(node_data: schemas.VirtualBoxUpdate, node: Virt
setattr(node, name, value)
node.updated()
return node.__json__()
return node.asdict()
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)):
async def delete_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
"""
Delete a VirtualBox node.
"""
@ -144,21 +146,16 @@ async def delete_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)):
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
async def start_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)):
async def start_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
"""
Start a VirtualBox node.
"""
if await node.check_hw_virtualization():
pm = ProjectManager.instance()
if pm.check_hardware_virtualization(node) is False:
pass # FIXME: check this
# raise ComputeError("Cannot start VM with hardware acceleration (KVM/HAX) enabled because hardware virtualization (VT-x/AMD-V) is already used by another software like VMware or VirtualBox")
await node.start()
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
async def stop_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)):
async def stop_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
"""
Stop a VirtualBox node.
"""
@ -167,7 +164,7 @@ async def stop_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)):
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
async def suspend_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)):
async def suspend_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
"""
Suspend a VirtualBox node.
"""
@ -176,7 +173,7 @@ async def suspend_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)):
@router.post("/{node_id}/resume", status_code=status.HTTP_204_NO_CONTENT)
async def resume_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)):
async def resume_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
"""
Resume a VirtualBox node.
"""
@ -185,7 +182,7 @@ async def resume_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)):
@router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT)
async def reload_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)):
async def reload_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
"""
Reload a VirtualBox node.
"""
@ -199,8 +196,12 @@ async def reload_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)):
response_model=schemas.UDPNIO,
)
async def create_virtualbox_node_nio(
adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: VirtualBoxVM = Depends(dep_node)
):
*,
adapter_number: int,
port_number: int = Path(..., ge=0, le=0),
nio_data: schemas.UDPNIO,
node: VirtualBoxVM = Depends(dep_node)
) -> schemas.UDPNIO:
"""
Add a NIO (Network Input/Output) to the node.
The port number on the VirtualBox node is always 0.
@ -208,7 +209,7 @@ async def create_virtualbox_node_nio(
nio = VirtualBox.instance().create_nio(jsonable_encoder(nio_data, exclude_unset=True))
await node.adapter_add_nio_binding(adapter_number, nio)
return nio.__json__()
return nio.asdict()
@router.put(
@ -217,8 +218,12 @@ async def create_virtualbox_node_nio(
response_model=schemas.UDPNIO,
)
async def update_virtualbox_node_nio(
adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: VirtualBoxVM = Depends(dep_node)
):
*,
adapter_number: int,
port_number: int = Path(..., ge=0, le=0),
nio_data: schemas.UDPNIO,
node: VirtualBoxVM = Depends(dep_node)
) -> schemas.UDPNIO:
"""
Update a NIO (Network Input/Output) on the node.
The port number on the VirtualBox node is always 0.
@ -230,11 +235,15 @@ async def update_virtualbox_node_nio(
if nio_data.suspend:
nio.suspend = nio_data.suspend
await node.adapter_update_nio_binding(adapter_number, nio)
return nio.__json__()
return nio.asdict()
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
async def delete_virtualbox_node_nio(adapter_number: int, port_number: int, node: VirtualBoxVM = Depends(dep_node)):
async def delete_virtualbox_node_nio(
adapter_number: int,
port_number: int = Path(..., ge=0, le=0),
node: VirtualBoxVM = Depends(dep_node)
) -> None:
"""
Delete a NIO (Network Input/Output) from the node.
The port number on the VirtualBox node is always 0.
@ -245,11 +254,12 @@ async def delete_virtualbox_node_nio(adapter_number: int, port_number: int, node
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
async def start_virtualbox_node_capture(
adapter_number: int,
port_number: int,
node_capture_data: schemas.NodeCapture,
node: VirtualBoxVM = Depends(dep_node),
):
*,
adapter_number: int,
port_number: int = Path(..., ge=0, le=0),
node_capture_data: schemas.NodeCapture,
node: VirtualBoxVM = Depends(dep_node),
) -> dict:
"""
Start a packet capture on the node.
The port number on the VirtualBox node is always 0.
@ -263,7 +273,11 @@ async def start_virtualbox_node_capture(
@router.post(
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT
)
async def stop_virtualbox_node_capture(adapter_number: int, port_number: int, node: VirtualBoxVM = Depends(dep_node)):
async def stop_virtualbox_node_capture(
adapter_number: int,
port_number: int = Path(..., ge=0, le=0),
node: VirtualBoxVM = Depends(dep_node)
) -> None:
"""
Stop a packet capture on the node.
The port number on the VirtualBox node is always 0.
@ -273,7 +287,11 @@ async def stop_virtualbox_node_capture(adapter_number: int, port_number: int, no
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
async def stream_pcap_file(adapter_number: int, port_number: int, node: VirtualBoxVM = Depends(dep_node)):
async def stream_pcap_file(
adapter_number: int,
port_number: int = Path(..., ge=0, le=0),
node: VirtualBoxVM = Depends(dep_node)
) -> StreamingResponse:
"""
Stream the pcap capture file.
The port number on the VirtualBox node is always 0.
@ -285,7 +303,7 @@ async def stream_pcap_file(adapter_number: int, port_number: int, node: VirtualB
@router.websocket("/{node_id}/console/ws")
async def console_ws(websocket: WebSocket, node: VirtualBoxVM = Depends(dep_node)):
async def console_ws(websocket: WebSocket, node: VirtualBoxVM = Depends(dep_node)) -> None:
"""
Console WebSocket.
"""
@ -294,6 +312,6 @@ async def console_ws(websocket: WebSocket, node: VirtualBoxVM = Depends(dep_node
@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
async def reset_console(node: VirtualBoxVM = Depends(dep_node)):
async def reset_console(node: VirtualBoxVM = Depends(dep_node)) -> None:
await node.reset_console()

View File

@ -20,7 +20,7 @@ API routes for VMware nodes.
import os
from fastapi import APIRouter, WebSocket, Depends, status
from fastapi import APIRouter, WebSocket, Depends, Path, status
from fastapi.encoders import jsonable_encoder
from fastapi.responses import StreamingResponse
from uuid import UUID
@ -35,7 +35,7 @@ responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find
router = APIRouter(responses=responses)
def dep_node(project_id: UUID, node_id: UUID):
def dep_node(project_id: UUID, node_id: UUID) -> VMwareVM:
"""
Dependency to retrieve a node.
"""
@ -51,7 +51,7 @@ def dep_node(project_id: UUID, node_id: UUID):
status_code=status.HTTP_201_CREATED,
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create VMware node"}},
)
async def create_vmware_node(project_id: UUID, node_data: schemas.VMwareCreate):
async def create_vmware_node(project_id: UUID, node_data: schemas.VMwareCreate) -> schemas.VMware:
"""
Create a new VMware node.
"""
@ -73,20 +73,20 @@ async def create_vmware_node(project_id: UUID, node_data: schemas.VMwareCreate):
if hasattr(vm, name) and getattr(vm, name) != value:
setattr(vm, name, value)
return vm.__json__()
return vm.asdict()
@router.get("/{node_id}", response_model=schemas.VMware)
def get_vmware_node(node: VMwareVM = Depends(dep_node)):
def get_vmware_node(node: VMwareVM = Depends(dep_node)) -> schemas.VMware:
"""
Return a VMware node.
"""
return node.__json__()
return node.asdict()
@router.put("/{node_id}", response_model=schemas.VMware)
def update_vmware_node(node_data: schemas.VMwareUpdate, node: VMwareVM = Depends(dep_node)):
def update_vmware_node(node_data: schemas.VMwareUpdate, node: VMwareVM = Depends(dep_node)) -> schemas.VMware:
"""
Update a VMware node.
"""
@ -99,11 +99,11 @@ def update_vmware_node(node_data: schemas.VMwareUpdate, node: VMwareVM = Depends
setattr(node, name, value)
node.updated()
return node.__json__()
return node.asdict()
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_vmware_node(node: VMwareVM = Depends(dep_node)):
async def delete_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
"""
Delete a VMware node.
"""
@ -112,21 +112,16 @@ async def delete_vmware_node(node: VMwareVM = Depends(dep_node)):
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
async def start_vmware_node(node: VMwareVM = Depends(dep_node)):
async def start_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
"""
Start a VMware node.
"""
if node.check_hw_virtualization():
pm = ProjectManager.instance()
if pm.check_hardware_virtualization(node) is False:
pass # FIXME: check this
# raise ComputeError("Cannot start VM with hardware acceleration (KVM/HAX) enabled because hardware virtualization (VT-x/AMD-V) is already used by another software like VMware or VirtualBox")
await node.start()
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
async def stop_vmware_node(node: VMwareVM = Depends(dep_node)):
async def stop_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
"""
Stop a VMware node.
"""
@ -135,7 +130,7 @@ async def stop_vmware_node(node: VMwareVM = Depends(dep_node)):
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
async def suspend_vmware_node(node: VMwareVM = Depends(dep_node)):
async def suspend_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
"""
Suspend a VMware node.
"""
@ -144,7 +139,7 @@ async def suspend_vmware_node(node: VMwareVM = Depends(dep_node)):
@router.post("/{node_id}/resume", status_code=status.HTTP_204_NO_CONTENT)
async def resume_vmware_node(node: VMwareVM = Depends(dep_node)):
async def resume_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
"""
Resume a VMware node.
"""
@ -153,7 +148,7 @@ async def resume_vmware_node(node: VMwareVM = Depends(dep_node)):
@router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT)
async def reload_vmware_node(node: VMwareVM = Depends(dep_node)):
async def reload_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
"""
Reload a VMware node.
"""
@ -167,8 +162,12 @@ async def reload_vmware_node(node: VMwareVM = Depends(dep_node)):
response_model=schemas.UDPNIO,
)
async def create_vmware_node_nio(
adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: VMwareVM = Depends(dep_node)
):
*,
adapter_number: int,
port_number: int = Path(..., ge=0, le=0),
nio_data: schemas.UDPNIO,
node: VMwareVM = Depends(dep_node)
) -> schemas.UDPNIO:
"""
Add a NIO (Network Input/Output) to the node.
The port number on the VMware node is always 0.
@ -176,7 +175,7 @@ async def create_vmware_node_nio(
nio = VMware.instance().create_nio(jsonable_encoder(nio_data, exclude_unset=True))
await node.adapter_add_nio_binding(adapter_number, nio)
return nio.__json__()
return nio.asdict()
@router.put(
@ -185,8 +184,12 @@ async def create_vmware_node_nio(
response_model=schemas.UDPNIO,
)
async def update_vmware_node_nio(
adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: VMwareVM = Depends(dep_node)
):
*,
adapter_number: int,
port_number: int = Path(..., ge=0, le=0),
nio_data: schemas.UDPNIO,
node: VMwareVM = Depends(dep_node)
) -> schemas.UDPNIO:
"""
Update a NIO (Network Input/Output) on the node.
The port number on the VMware node is always 0.
@ -196,11 +199,15 @@ async def update_vmware_node_nio(
if nio_data.filters:
nio.filters = nio_data.filters
await node.adapter_update_nio_binding(adapter_number, nio)
return nio.__json__()
return nio.asdict()
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
async def delete_vmware_node_nio(adapter_number: int, port_number: int, node: VMwareVM = Depends(dep_node)):
async def delete_vmware_node_nio(
adapter_number: int,
port_number: int = Path(..., ge=0, le=0),
node: VMwareVM = Depends(dep_node)
) -> None:
"""
Delete a NIO (Network Input/Output) from the node.
The port number on the VMware node is always 0.
@ -211,8 +218,12 @@ async def delete_vmware_node_nio(adapter_number: int, port_number: int, node: VM
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
async def start_vmware_node_capture(
adapter_number: int, port_number: int, node_capture_data: schemas.NodeCapture, node: VMwareVM = Depends(dep_node)
):
*,
adapter_number: int,
port_number: int = Path(..., ge=0, le=0),
node_capture_data: schemas.NodeCapture,
node: VMwareVM = Depends(dep_node)
) -> dict:
"""
Start a packet capture on the node.
The port number on the VMware node is always 0.
@ -226,7 +237,11 @@ async def start_vmware_node_capture(
@router.post(
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT
)
async def stop_vmware_node_capture(adapter_number: int, port_number: int, node: VMwareVM = Depends(dep_node)):
async def stop_vmware_node_capture(
adapter_number: int,
port_number: int = Path(..., ge=0, le=0),
node: VMwareVM = Depends(dep_node)
) -> None:
"""
Stop a packet capture on the node.
The port number on the VMware node is always 0.
@ -236,7 +251,11 @@ async def stop_vmware_node_capture(adapter_number: int, port_number: int, node:
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
async def stream_pcap_file(adapter_number: int, port_number: int, node: VMwareVM = Depends(dep_node)):
async def stream_pcap_file(
adapter_number: int,
port_number: int = Path(..., ge=0, le=0),
node: VMwareVM = Depends(dep_node)
) -> StreamingResponse:
"""
Stream the pcap capture file.
The port number on the VMware node is always 0.
@ -261,7 +280,7 @@ def allocate_vmnet(node: VMwareVM = Depends(dep_node)) -> dict:
@router.websocket("/{node_id}/console/ws")
async def console_ws(websocket: WebSocket, node: VMwareVM = Depends(dep_node)):
async def console_ws(websocket: WebSocket, node: VMwareVM = Depends(dep_node)) -> None:
"""
Console WebSocket.
"""
@ -270,6 +289,6 @@ async def console_ws(websocket: WebSocket, node: VMwareVM = Depends(dep_node)):
@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
async def reset_console(node: VMwareVM = Depends(dep_node)):
async def reset_console(node: VMwareVM = Depends(dep_node)) -> None:
await node.reset_console()

View File

@ -20,7 +20,7 @@ API routes for VPCS nodes.
import os
from fastapi import APIRouter, WebSocket, Depends, Body, status
from fastapi import APIRouter, WebSocket, Depends, Body, Path, status
from fastapi.encoders import jsonable_encoder
from fastapi.responses import StreamingResponse
from uuid import UUID
@ -34,7 +34,7 @@ responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find
router = APIRouter(responses=responses)
def dep_node(project_id: UUID, node_id: UUID):
def dep_node(project_id: UUID, node_id: UUID) -> VPCSVM:
"""
Dependency to retrieve a node.
"""
@ -50,7 +50,7 @@ def dep_node(project_id: UUID, node_id: UUID):
status_code=status.HTTP_201_CREATED,
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create VMware node"}},
)
async def create_vpcs_node(project_id: UUID, node_data: schemas.VPCSCreate):
async def create_vpcs_node(project_id: UUID, node_data: schemas.VPCSCreate) -> schemas.VPCS:
"""
Create a new VPCS node.
"""
@ -66,20 +66,20 @@ async def create_vpcs_node(project_id: UUID, node_data: schemas.VPCSCreate):
startup_script=node_data.get("startup_script"),
)
return vm.__json__()
return vm.asdict()
@router.get("/{node_id}", response_model=schemas.VPCS)
def get_vpcs_node(node: VPCSVM = Depends(dep_node)):
def get_vpcs_node(node: VPCSVM = Depends(dep_node)) -> schemas.VPCS:
"""
Return a VPCS node.
"""
return node.__json__()
return node.asdict()
@router.put("/{node_id}", response_model=schemas.VPCS)
def update_vpcs_node(node_data: schemas.VPCSUpdate, node: VPCSVM = Depends(dep_node)):
def update_vpcs_node(node_data: schemas.VPCSUpdate, node: VPCSVM = Depends(dep_node)) -> schemas.VPCS:
"""
Update a VPCS node.
"""
@ -89,11 +89,11 @@ def update_vpcs_node(node_data: schemas.VPCSUpdate, node: VPCSVM = Depends(dep_n
node.console = node_data.get("console", node.console)
node.console_type = node_data.get("console_type", node.console_type)
node.updated()
return node.__json__()
return node.asdict()
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_vpcs_node(node: VPCSVM = Depends(dep_node)):
async def delete_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
"""
Delete a VPCS node.
"""
@ -102,17 +102,19 @@ async def delete_vpcs_node(node: VPCSVM = Depends(dep_node)):
@router.post("/{node_id}/duplicate", response_model=schemas.VPCS, status_code=status.HTTP_201_CREATED)
async def duplicate_vpcs_node(destination_node_id: UUID = Body(..., embed=True), node: VPCSVM = Depends(dep_node)):
async def duplicate_vpcs_node(
destination_node_id: UUID = Body(..., embed=True),
node: VPCSVM = Depends(dep_node)) -> None:
"""
Duplicate a VPCS node.
"""
new_node = await VPCS.instance().duplicate_node(node.id, str(destination_node_id))
return new_node.__json__()
return new_node.asdict()
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
async def start_vpcs_node(node: VPCSVM = Depends(dep_node)):
async def start_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
"""
Start a VPCS node.
"""
@ -121,7 +123,7 @@ async def start_vpcs_node(node: VPCSVM = Depends(dep_node)):
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
async def stop_vpcs_node(node: VPCSVM = Depends(dep_node)):
async def stop_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
"""
Stop a VPCS node.
"""
@ -130,7 +132,7 @@ async def stop_vpcs_node(node: VPCSVM = Depends(dep_node)):
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
async def suspend_vpcs_node(node: VPCSVM = Depends(dep_node)):
async def suspend_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
"""
Suspend a VPCS node.
Does nothing, suspend is not supported by VPCS.
@ -140,7 +142,7 @@ async def suspend_vpcs_node(node: VPCSVM = Depends(dep_node)):
@router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT)
async def reload_vpcs_node(node: VPCSVM = Depends(dep_node)):
async def reload_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
"""
Reload a VPCS node.
"""
@ -154,8 +156,12 @@ async def reload_vpcs_node(node: VPCSVM = Depends(dep_node)):
response_model=schemas.UDPNIO,
)
async def create_vpcs_node_nio(
adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: VPCSVM = Depends(dep_node)
):
*,
adapter_number: int = Path(..., ge=0, le=0),
port_number: int,
nio_data: schemas.UDPNIO,
node: VPCSVM = Depends(dep_node)
) -> schemas.UDPNIO:
"""
Add a NIO (Network Input/Output) to the node.
The adapter number on the VPCS node is always 0.
@ -163,7 +169,7 @@ async def create_vpcs_node_nio(
nio = VPCS.instance().create_nio(jsonable_encoder(nio_data, exclude_unset=True))
await node.port_add_nio_binding(port_number, nio)
return nio.__json__()
return nio.asdict()
@router.put(
@ -172,8 +178,12 @@ async def create_vpcs_node_nio(
response_model=schemas.UDPNIO,
)
async def update_vpcs_node_nio(
adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: VPCSVM = Depends(dep_node)
):
*,
adapter_number: int = Path(..., ge=0, le=0),
port_number: int,
nio_data: schemas.UDPNIO,
node: VPCSVM = Depends(dep_node)
) -> schemas.UDPNIO:
"""
Update a NIO (Network Input/Output) on the node.
The adapter number on the VPCS node is always 0.
@ -183,11 +193,16 @@ async def update_vpcs_node_nio(
if nio_data.filters:
nio.filters = nio_data.filters
await node.port_update_nio_binding(port_number, nio)
return nio.__json__()
return nio.asdict()
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
async def delete_vpcs_node_nio(adapter_number: int, port_number: int, node: VPCSVM = Depends(dep_node)):
async def delete_vpcs_node_nio(
*,
adapter_number: int = Path(..., ge=0, le=0),
port_number: int,
node: VPCSVM = Depends(dep_node)
) -> None:
"""
Delete a NIO (Network Input/Output) from the node.
The adapter number on the VPCS node is always 0.
@ -198,8 +213,12 @@ async def delete_vpcs_node_nio(adapter_number: int, port_number: int, node: VPCS
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
async def start_vpcs_node_capture(
adapter_number: int, port_number: int, node_capture_data: schemas.NodeCapture, node: VPCSVM = Depends(dep_node)
):
*,
adapter_number: int = Path(..., ge=0, le=0),
port_number: int,
node_capture_data: schemas.NodeCapture,
node: VPCSVM = Depends(dep_node)
) -> dict:
"""
Start a packet capture on the node.
The adapter number on the VPCS node is always 0.
@ -213,7 +232,12 @@ async def start_vpcs_node_capture(
@router.post(
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT
)
async def stop_vpcs_node_capture(adapter_number: int, port_number: int, node: VPCSVM = Depends(dep_node)):
async def stop_vpcs_node_capture(
*,
adapter_number: int = Path(..., ge=0, le=0),
port_number: int,
node: VPCSVM = Depends(dep_node)
) -> None:
"""
Stop a packet capture on the node.
The adapter number on the VPCS node is always 0.
@ -223,13 +247,18 @@ async def stop_vpcs_node_capture(adapter_number: int, port_number: int, node: VP
@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
async def reset_console(node: VPCSVM = Depends(dep_node)):
async def reset_console(node: VPCSVM = Depends(dep_node)) -> None:
await node.reset_console()
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
async def stream_pcap_file(adapter_number: int, port_number: int, node: VPCSVM = Depends(dep_node)):
async def stream_pcap_file(
*,
adapter_number: int = Path(..., ge=0, le=0),
port_number: int,
node: VPCSVM = Depends(dep_node)
) -> StreamingResponse:
"""
Stream the pcap capture file.
The adapter number on the VPCS node is always 0.
@ -241,7 +270,7 @@ async def stream_pcap_file(adapter_number: int, port_number: int, node: VPCSVM =
@router.websocket("/{node_id}/console/ws")
async def console_ws(websocket: WebSocket, node: VPCSVM = Depends(dep_node)):
async def console_ws(websocket: WebSocket, node: VPCSVM = Depends(dep_node)) -> None:
"""
Console WebSocket.
"""

View File

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

View File

@ -19,13 +19,13 @@ API routes for appliances.
"""
from fastapi import APIRouter
from typing import Optional
from typing import Optional, List
router = APIRouter()
@router.get("")
async def get_appliances(update: Optional[bool] = None, symbol_theme: Optional[str] = "Classic"):
async def get_appliances(update: Optional[bool] = None, symbol_theme: Optional[str] = "Classic") -> List[dict]:
"""
Return all appliances known by the controller.
"""
@ -36,4 +36,4 @@ async def get_appliances(update: Optional[bool] = None, symbol_theme: Optional[s
if update:
await controller.appliance_manager.download_appliances()
controller.appliance_manager.load_appliances(symbol_theme=symbol_theme)
return [c.__json__() for c in controller.appliance_manager.appliances.values()]
return [c.asdict() for c in controller.appliance_manager.appliances.values()]

View File

@ -93,7 +93,7 @@ async def update_compute(
@router.delete("/{compute_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_compute(
compute_id: Union[str, UUID], computes_repo: ComputesRepository = Depends(get_repository(ComputesRepository))
):
) -> None:
"""
Delete a compute from the controller.
"""
@ -102,7 +102,7 @@ async def delete_compute(
@router.get("/{compute_id}/{emulator}/images")
async def get_images(compute_id: Union[str, UUID], emulator: str):
async def get_images(compute_id: Union[str, UUID], emulator: str) -> List[str]:
"""
Return the list of images available on a compute for a given emulator type.
"""
@ -113,7 +113,7 @@ async def get_images(compute_id: Union[str, UUID], emulator: str):
@router.get("/{compute_id}/{emulator}/{endpoint_path:path}")
async def forward_get(compute_id: Union[str, UUID], emulator: str, endpoint_path: str):
async def forward_get(compute_id: Union[str, UUID], emulator: str, endpoint_path: str) -> dict:
"""
Forward a GET request to a compute.
Read the full compute API documentation for available routes.
@ -125,7 +125,7 @@ async def forward_get(compute_id: Union[str, UUID], emulator: str, endpoint_path
@router.post("/{compute_id}/{emulator}/{endpoint_path:path}")
async def forward_post(compute_id: Union[str, UUID], emulator: str, endpoint_path: str, compute_data: dict):
async def forward_post(compute_id: Union[str, UUID], emulator: str, endpoint_path: str, compute_data: dict) -> dict:
"""
Forward a POST request to a compute.
Read the full compute API documentation for available routes.
@ -136,7 +136,7 @@ async def forward_post(compute_id: Union[str, UUID], emulator: str, endpoint_pat
@router.put("/{compute_id}/{emulator}/{endpoint_path:path}")
async def forward_put(compute_id: Union[str, UUID], emulator: str, endpoint_path: str, compute_data: dict):
async def forward_put(compute_id: Union[str, UUID], emulator: str, endpoint_path: str, compute_data: dict) -> dict:
"""
Forward a PUT request to a compute.
Read the full compute API documentation for available routes.
@ -147,7 +147,7 @@ async def forward_put(compute_id: Union[str, UUID], emulator: str, endpoint_path
@router.post("/{compute_id}/auto_idlepc")
async def autoidlepc(compute_id: Union[str, UUID], auto_idle_pc: schemas.AutoIdlePC):
async def autoidlepc(compute_id: Union[str, UUID], auto_idle_pc: schemas.AutoIdlePC) -> str:
"""
Find a suitable Idle-PC value for a given IOS image. This may take a few minutes.
"""

View File

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

View File

@ -24,7 +24,7 @@ from gns3server.services import auth_service
from .database import get_repository
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/v3/users/login") # FIXME: URL prefix
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/v3/users/login")
async def get_user_from_token(
@ -44,6 +44,10 @@ async def get_user_from_token(
async def get_current_active_user(current_user: schemas.User = Depends(get_user_from_token)) -> schemas.User:
# Super admin is always authorized
if current_user.is_superadmin:
return current_user
if not current_user.is_active:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,

View File

@ -32,39 +32,39 @@ router = APIRouter(responses=responses)
@router.get("", response_model=List[schemas.Drawing], response_model_exclude_unset=True)
async def get_drawings(project_id: UUID):
async def get_drawings(project_id: UUID) -> List[schemas.Drawing]:
"""
Return the list of all drawings for a given project.
"""
project = await Controller.instance().get_loaded_project(str(project_id))
return [v.__json__() for v in project.drawings.values()]
return [v.asdict() for v in project.drawings.values()]
@router.post("", status_code=status.HTTP_201_CREATED, response_model=schemas.Drawing)
async def create_drawing(project_id: UUID, drawing_data: schemas.Drawing):
async def create_drawing(project_id: UUID, drawing_data: schemas.Drawing) -> schemas.Drawing:
"""
Create a new drawing.
"""
project = await Controller.instance().get_loaded_project(str(project_id))
drawing = await project.add_drawing(**jsonable_encoder(drawing_data, exclude_unset=True))
return drawing.__json__()
return drawing.asdict()
@router.get("/{drawing_id}", response_model=schemas.Drawing, response_model_exclude_unset=True)
async def get_drawing(project_id: UUID, drawing_id: UUID):
async def get_drawing(project_id: UUID, drawing_id: UUID) -> schemas.Drawing:
"""
Return a drawing.
"""
project = await Controller.instance().get_loaded_project(str(project_id))
drawing = project.get_drawing(str(drawing_id))
return drawing.__json__()
return drawing.asdict()
@router.put("/{drawing_id}", response_model=schemas.Drawing, response_model_exclude_unset=True)
async def update_drawing(project_id: UUID, drawing_id: UUID, drawing_data: schemas.Drawing):
async def update_drawing(project_id: UUID, drawing_id: UUID, drawing_data: schemas.Drawing) -> schemas.Drawing:
"""
Update a drawing.
"""
@ -72,11 +72,11 @@ async def update_drawing(project_id: UUID, drawing_id: UUID, drawing_data: schem
project = await Controller.instance().get_loaded_project(str(project_id))
drawing = project.get_drawing(str(drawing_id))
await drawing.update(**jsonable_encoder(drawing_data, exclude_unset=True))
return drawing.__json__()
return drawing.asdict()
@router.delete("/{drawing_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_drawing(project_id: UUID, drawing_id: UUID):
async def delete_drawing(project_id: UUID, drawing_id: UUID) -> None:
"""
Delete a drawing.
"""

View File

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

View File

@ -42,7 +42,7 @@ responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find
router = APIRouter(responses=responses)
async def dep_link(project_id: UUID, link_id: UUID):
async def dep_link(project_id: UUID, link_id: UUID) -> Link:
"""
Dependency to retrieve a link.
"""
@ -53,13 +53,13 @@ async def dep_link(project_id: UUID, link_id: UUID):
@router.get("", response_model=List[schemas.Link], response_model_exclude_unset=True)
async def get_links(project_id: UUID):
async def get_links(project_id: UUID) -> List[schemas.Link]:
"""
Return all links for a given project.
"""
project = await Controller.instance().get_loaded_project(str(project_id))
return [v.__json__() for v in project.links.values()]
return [v.asdict() for v in project.links.values()]
@router.post(
@ -71,7 +71,7 @@ async def get_links(project_id: UUID):
409: {"model": schemas.ErrorMessage, "description": "Could not create link"},
},
)
async def create_link(project_id: UUID, link_data: schemas.Link):
async def create_link(project_id: UUID, link_data: schemas.LinkCreate) -> schemas.Link:
"""
Create a new link.
"""
@ -94,11 +94,11 @@ async def create_link(project_id: UUID, link_data: schemas.Link):
except ControllerError as e:
await project.delete_link(link.id)
raise e
return link.__json__()
return link.asdict()
@router.get("/{link_id}/available_filters")
async def get_filters(link: Link = Depends(dep_link)):
async def get_filters(link: Link = Depends(dep_link)) -> List[dict]:
"""
Return all filters available for a given link.
"""
@ -107,16 +107,16 @@ async def get_filters(link: Link = Depends(dep_link)):
@router.get("/{link_id}", response_model=schemas.Link, response_model_exclude_unset=True)
async def get_link(link: Link = Depends(dep_link)):
async def get_link(link: Link = Depends(dep_link)) -> schemas.Link:
"""
Return a link.
"""
return link.__json__()
return link.asdict()
@router.put("/{link_id}", response_model=schemas.Link, response_model_exclude_unset=True)
async def update_link(link_data: schemas.Link, link: Link = Depends(dep_link)):
async def update_link(link_data: schemas.LinkUpdate, link: Link = Depends(dep_link)) -> schemas.Link:
"""
Update a link.
"""
@ -128,11 +128,11 @@ async def update_link(link_data: schemas.Link, link: Link = Depends(dep_link)):
await link.update_suspend(link_data["suspend"])
if "nodes" in link_data:
await link.update_nodes(link_data["nodes"])
return link.__json__()
return link.asdict()
@router.delete("/{link_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_link(project_id: UUID, link: Link = Depends(dep_link)):
async def delete_link(project_id: UUID, link: Link = Depends(dep_link)) -> None:
"""
Delete a link.
"""
@ -142,17 +142,17 @@ async def delete_link(project_id: UUID, link: Link = Depends(dep_link)):
@router.post("/{link_id}/reset", response_model=schemas.Link)
async def reset_link(link: Link = Depends(dep_link)):
async def reset_link(link: Link = Depends(dep_link)) -> schemas.Link:
"""
Reset a link.
"""
await link.reset()
return link.__json__()
return link.asdict()
@router.post("/{link_id}/capture/start", status_code=status.HTTP_201_CREATED, response_model=schemas.Link)
async def start_capture(capture_data: dict, link: Link = Depends(dep_link)):
async def start_capture(capture_data: dict, link: Link = Depends(dep_link)) -> schemas.Link:
"""
Start packet capture on the link.
"""
@ -161,11 +161,11 @@ async def start_capture(capture_data: dict, link: Link = Depends(dep_link)):
data_link_type=capture_data.get("data_link_type", "DLT_EN10MB"),
capture_file_name=capture_data.get("capture_file_name"),
)
return link.__json__()
return link.asdict()
@router.post("/{link_id}/capture/stop", status_code=status.HTTP_204_NO_CONTENT)
async def stop_capture(link: Link = Depends(dep_link)):
async def stop_capture(link: Link = Depends(dep_link)) -> None:
"""
Stop packet capture on the link.
"""
@ -174,7 +174,7 @@ async def stop_capture(link: Link = Depends(dep_link)):
@router.get("/{link_id}/capture/stream")
async def stream_pcap(request: Request, link: Link = Depends(dep_link)):
async def stream_pcap(request: Request, link: Link = Depends(dep_link)) -> StreamingResponse:
"""
Stream the PCAP capture file from compute.
"""

View File

@ -81,7 +81,7 @@ responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find
router = APIRouter(route_class=NodeConcurrency, responses=responses)
async def dep_project(project_id: UUID):
async def dep_project(project_id: UUID) -> Project:
"""
Dependency to retrieve a project.
"""
@ -90,7 +90,7 @@ async def dep_project(project_id: UUID):
return project
async def dep_node(node_id: UUID, project: Project = Depends(dep_project)):
async def dep_node(node_id: UUID, project: Project = Depends(dep_project)) -> None:
"""
Dependency to retrieve a node.
"""
@ -108,7 +108,7 @@ async def dep_node(node_id: UUID, project: Project = Depends(dep_project)):
409: {"model": schemas.ErrorMessage, "description": "Could not create node"},
},
)
async def create_node(node_data: schemas.Node, project: Project = Depends(dep_project)):
async def create_node(node_data: schemas.NodeCreate, project: Project = Depends(dep_project)) -> schemas.Node:
"""
Create a new node.
"""
@ -117,20 +117,20 @@ async def create_node(node_data: schemas.Node, project: Project = Depends(dep_pr
compute = controller.get_compute(str(node_data.compute_id))
node_data = jsonable_encoder(node_data, exclude_unset=True)
node = await project.add_node(compute, node_data.pop("name"), node_data.pop("node_id", None), **node_data)
return node.__json__()
return node.asdict()
@router.get("", response_model=List[schemas.Node], response_model_exclude_unset=True)
async def get_nodes(project: Project = Depends(dep_project)):
async def get_nodes(project: Project = Depends(dep_project)) -> List[schemas.Node]:
"""
Return all nodes belonging to a given project.
"""
return [v.__json__() for v in project.nodes.values()]
return [v.asdict() for v in project.nodes.values()]
@router.post("/start", status_code=status.HTTP_204_NO_CONTENT)
async def start_all_nodes(project: Project = Depends(dep_project)):
async def start_all_nodes(project: Project = Depends(dep_project)) -> None:
"""
Start all nodes belonging to a given project.
"""
@ -139,7 +139,7 @@ async def start_all_nodes(project: Project = Depends(dep_project)):
@router.post("/stop", status_code=status.HTTP_204_NO_CONTENT)
async def stop_all_nodes(project: Project = Depends(dep_project)):
async def stop_all_nodes(project: Project = Depends(dep_project)) -> None:
"""
Stop all nodes belonging to a given project.
"""
@ -148,7 +148,7 @@ async def stop_all_nodes(project: Project = Depends(dep_project)):
@router.post("/suspend", status_code=status.HTTP_204_NO_CONTENT)
async def suspend_all_nodes(project: Project = Depends(dep_project)):
async def suspend_all_nodes(project: Project = Depends(dep_project)) -> None:
"""
Suspend all nodes belonging to a given project.
"""
@ -157,7 +157,7 @@ async def suspend_all_nodes(project: Project = Depends(dep_project)):
@router.post("/reload", status_code=status.HTTP_204_NO_CONTENT)
async def reload_all_nodes(project: Project = Depends(dep_project)):
async def reload_all_nodes(project: Project = Depends(dep_project)) -> None:
"""
Reload all nodes belonging to a given project.
"""
@ -167,16 +167,16 @@ async def reload_all_nodes(project: Project = Depends(dep_project)):
@router.get("/{node_id}", response_model=schemas.Node)
def get_node(node: Node = Depends(dep_node)):
def get_node(node: Node = Depends(dep_node)) -> schemas.Node:
"""
Return a node from a given project.
"""
return node.__json__()
return node.asdict()
@router.put("/{node_id}", response_model=schemas.Node, response_model_exclude_unset=True)
async def update_node(node_data: schemas.NodeUpdate, node: Node = Depends(dep_node)):
async def update_node(node_data: schemas.NodeUpdate, node: Node = Depends(dep_node)) -> schemas.Node:
"""
Update a node.
"""
@ -189,7 +189,7 @@ async def update_node(node_data: schemas.NodeUpdate, node: Node = Depends(dep_no
node_data.pop("compute_id", None)
await node.update(**node_data)
return node.__json__()
return node.asdict()
@router.delete(
@ -197,7 +197,7 @@ async def update_node(node_data: schemas.NodeUpdate, node: Node = Depends(dep_no
status_code=status.HTTP_204_NO_CONTENT,
responses={**responses, 409: {"model": schemas.ErrorMessage, "description": "Cannot delete node"}},
)
async def delete_node(node_id: UUID, project: Project = Depends(dep_project)):
async def delete_node(node_id: UUID, project: Project = Depends(dep_project)) -> None:
"""
Delete a node from a project.
"""
@ -206,17 +206,17 @@ async def delete_node(node_id: UUID, project: Project = Depends(dep_project)):
@router.post("/{node_id}/duplicate", response_model=schemas.Node, status_code=status.HTTP_201_CREATED)
async def duplicate_node(duplicate_data: schemas.NodeDuplicate, node: Node = Depends(dep_node)):
async def duplicate_node(duplicate_data: schemas.NodeDuplicate, node: Node = Depends(dep_node)) -> schemas.Node:
"""
Duplicate a node.
"""
new_node = await node.project.duplicate_node(node, duplicate_data.x, duplicate_data.y, duplicate_data.z)
return new_node.__json__()
return new_node.asdict()
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
async def start_node(start_data: dict, node: Node = Depends(dep_node)):
async def start_node(start_data: dict, node: Node = Depends(dep_node)) -> None:
"""
Start a node.
"""
@ -225,7 +225,7 @@ async def start_node(start_data: dict, node: Node = Depends(dep_node)):
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
async def stop_node(node: Node = Depends(dep_node)):
async def stop_node(node: Node = Depends(dep_node)) -> None:
"""
Stop a node.
"""
@ -234,7 +234,7 @@ async def stop_node(node: Node = Depends(dep_node)):
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
async def suspend_node(node: Node = Depends(dep_node)):
async def suspend_node(node: Node = Depends(dep_node)) -> None:
"""
Suspend a node.
"""
@ -243,7 +243,7 @@ async def suspend_node(node: Node = Depends(dep_node)):
@router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT)
async def reload_node(node: Node = Depends(dep_node)):
async def reload_node(node: Node = Depends(dep_node)) -> None:
"""
Reload a node.
"""
@ -252,19 +252,19 @@ async def reload_node(node: Node = Depends(dep_node)):
@router.get("/{node_id}/links", response_model=List[schemas.Link], response_model_exclude_unset=True)
async def get_node_links(node: Node = Depends(dep_node)):
async def get_node_links(node: Node = Depends(dep_node)) -> List[schemas.Link]:
"""
Return all the links connected to a node.
"""
links = []
for link in node.links:
links.append(link.__json__())
links.append(link.asdict())
return links
@router.get("/{node_id}/dynamips/auto_idlepc")
async def auto_idlepc(node: Node = Depends(dep_node)):
async def auto_idlepc(node: Node = Depends(dep_node)) -> str:
"""
Compute an Idle-PC value for a Dynamips node
"""
@ -273,7 +273,7 @@ async def auto_idlepc(node: Node = Depends(dep_node)):
@router.get("/{node_id}/dynamips/idlepc_proposals")
async def idlepc_proposals(node: Node = Depends(dep_node)):
async def idlepc_proposals(node: Node = Depends(dep_node)) -> List[str]:
"""
Compute a list of potential idle-pc values for a Dynamips node
"""
@ -281,8 +281,8 @@ async def idlepc_proposals(node: Node = Depends(dep_node)):
return await node.dynamips_idlepc_proposals()
@router.post("/{node_id}/resize_disk", status_code=status.HTTP_201_CREATED)
async def resize_disk(resize_data: dict, node: Node = Depends(dep_node)):
@router.post("/{node_id}/resize_disk", status_code=status.HTTP_204_NO_CONTENT)
async def resize_disk(resize_data: dict, node: Node = Depends(dep_node)) -> None:
"""
Resize a disk image.
"""
@ -290,7 +290,7 @@ async def resize_disk(resize_data: dict, node: Node = Depends(dep_node)):
@router.get("/{node_id}/files/{file_path:path}")
async def get_file(file_path: str, node: Node = Depends(dep_node)):
async def get_file(file_path: str, node: Node = Depends(dep_node)) -> Response:
"""
Return a file in the node directory
"""
@ -309,7 +309,7 @@ async def get_file(file_path: str, node: Node = Depends(dep_node)):
@router.post("/{node_id}/files/{file_path:path}", status_code=status.HTTP_201_CREATED)
async def post_file(file_path: str, request: Request, node: Node = Depends(dep_node)):
async def post_file(file_path: str, request: Request, node: Node = Depends(dep_node)) -> dict:
"""
Write a file in the node directory.
"""
@ -329,7 +329,7 @@ async def post_file(file_path: str, request: Request, node: Node = Depends(dep_n
@router.websocket("/{node_id}/console/ws")
async def ws_console(websocket: WebSocket, node: Node = Depends(dep_node)):
async def ws_console(websocket: WebSocket, node: Node = Depends(dep_node)) -> None:
"""
WebSocket console.
"""
@ -377,7 +377,7 @@ async def ws_console(websocket: WebSocket, node: Node = Depends(dep_node)):
@router.post("/console/reset", status_code=status.HTTP_204_NO_CONTENT)
async def reset_console_all_nodes(project: Project = Depends(dep_project)):
async def reset_console_all_nodes(project: Project = Depends(dep_project)) -> None:
"""
Reset console for all nodes belonging to the project.
"""
@ -386,6 +386,6 @@ async def reset_console_all_nodes(project: Project = Depends(dep_project)):
@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
async def console_reset(node: Node = Depends(dep_node)):
async def console_reset(node: Node = Depends(dep_node)) -> None:
await node.post("/console/reset") # , request.json)

View File

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

View File

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

View File

@ -36,7 +36,7 @@ responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find
router = APIRouter(responses=responses)
def dep_project(project_id: UUID):
def dep_project(project_id: UUID) -> Project:
"""
Dependency to retrieve a project.
"""
@ -46,27 +46,30 @@ def dep_project(project_id: UUID):
@router.post("", status_code=status.HTTP_201_CREATED, response_model=schemas.Snapshot)
async def create_snapshot(snapshot_data: schemas.SnapshotCreate, project: Project = Depends(dep_project)):
async def create_snapshot(
snapshot_data: schemas.SnapshotCreate,
project: Project = Depends(dep_project)
) -> schemas.Snapshot:
"""
Create a new snapshot of a project.
"""
snapshot = await project.snapshot(snapshot_data.name)
return snapshot.__json__()
return snapshot.asdict()
@router.get("", response_model=List[schemas.Snapshot], response_model_exclude_unset=True)
def get_snapshots(project: Project = Depends(dep_project)):
def get_snapshots(project: Project = Depends(dep_project)) -> List[schemas.Snapshot]:
"""
Return all snapshots belonging to a given project.
"""
snapshots = [s for s in project.snapshots.values()]
return [s.__json__() for s in sorted(snapshots, key=lambda s: (s.created_at, s.name))]
return [s.asdict() for s in sorted(snapshots, key=lambda s: (s.created_at, s.name))]
@router.delete("/{snapshot_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_snapshot(snapshot_id: UUID, project: Project = Depends(dep_project)):
async def delete_snapshot(snapshot_id: UUID, project: Project = Depends(dep_project)) -> None:
"""
Delete a snapshot.
"""
@ -75,11 +78,11 @@ async def delete_snapshot(snapshot_id: UUID, project: Project = Depends(dep_proj
@router.post("/{snapshot_id}/restore", status_code=status.HTTP_201_CREATED, response_model=schemas.Project)
async def restore_snapshot(snapshot_id: UUID, project: Project = Depends(dep_project)):
async def restore_snapshot(snapshot_id: UUID, project: Project = Depends(dep_project)) -> schemas.Project:
"""
Restore a snapshot.
"""
snapshot = project.get_snapshot(str(snapshot_id))
project = await snapshot.restore()
return project.__json__()
return project.asdict()

View File

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

View File

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

View File

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

View File

@ -25,7 +25,7 @@ from fastapi import FastAPI, Request
from starlette.exceptions import HTTPException as StarletteHTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from uvicorn.main import Server as UvicornServer
from gns3server.controller.controller_error import (
ControllerError,
@ -82,6 +82,18 @@ def get_application() -> FastAPI:
app = get_application()
# Monkey Patch uvicorn signal handler to detect the application is shutting down
app.state.exiting = False
unicorn_exit_handler = UvicornServer.handle_exit
def handle_exit(*args, **kwargs):
app.state.exiting = True
unicorn_exit_handler(*args, **kwargs)
UvicornServer.handle_exit = handle_exit
@app.exception_handler(ControllerError)
async def controller_error_handler(request: Request, exc: ControllerError):

View File

@ -20,25 +20,25 @@ import struct
import stat
import asyncio
import aiofiles
import socket
import shutil
import re
import logging
from gns3server.utils.asyncio import cancellable_wait_run_in_executor
from gns3server.compute.compute_error import ComputeError, ComputeForbiddenError, ComputeNotFoundError
log = logging.getLogger(__name__)
from uuid import UUID, uuid4
from gns3server.utils.asyncio import cancellable_wait_run_in_executor
from gns3server.compute.compute_error import ComputeError, ComputeForbiddenError, ComputeNotFoundError
from gns3server.utils.interfaces import is_interface_up
from uuid import UUID, uuid4
from typing import Type
from ..config import Config
from ..utils.asyncio import wait_run_in_executor
from ..utils import force_unix_path
from .project_manager import ProjectManager
from .port_manager import PortManager
from .base_node import BaseNode
from .nios.nio_udp import NIOUDP
from .nios.nio_tap import NIOTAP
@ -150,7 +150,7 @@ class BaseManager:
BaseManager._instance = None
log.debug(f"Module {self.module_name} unloaded")
def get_node(self, node_id, project_id=None):
def get_node(self, node_id, project_id=None) -> Type[BaseNode]:
"""
Returns a Node instance.

View File

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

View File

@ -38,9 +38,14 @@ class EthernetHub(BaseNode):
super().__init__(name, node_id, project, manager)
def __json__(self):
def asdict(self):
return {"name": self.name, "usage": self.usage, "node_id": self.id, "project_id": self.project.id}
return {
"name": self.name,
"usage": self.usage,
"node_id": self.id,
"project_id": self.project.id
}
async def create(self):
"""

View File

@ -38,9 +38,14 @@ class EthernetSwitch(BaseNode):
super().__init__(name, node_id, project, manager)
def __json__(self):
def asdict(self):
return {"name": self.name, "usage": self.usage, "node_id": self.id, "project_id": self.project.id}
return {
"name": self.name,
"usage": self.usage,
"node_id": self.id,
"project_id": self.project.id
}
async def create(self):
"""

View File

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

View File

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

View File

@ -65,6 +65,9 @@ class NIOGenericEthernet(NIO):
return self._ethernet_device
def __json__(self):
def asdict(self):
return {"type": "nio_generic_ethernet", "ethernet_device": self._ethernet_device}
return {
"type": "nio_generic_ethernet",
"ethernet_device": self._ethernet_device
}

View File

@ -64,6 +64,9 @@ class NIOLinuxEthernet(NIO):
return self._ethernet_device
def __json__(self):
def asdict(self):
return {"type": "nio_linux_ethernet", "ethernet_device": self._ethernet_device}
return {
"type": "nio_linux_ethernet",
"ethernet_device": self._ethernet_device
}

View File

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

View File

@ -58,6 +58,9 @@ class NIOTAP(NIO):
return self._tap_device
def __json__(self):
def asdict(self):
return {"type": "nio_tap", "tap_device": self._tap_device}
return {
"type": "nio_tap",
"tap_device": self._tap_device
}

View File

@ -126,6 +126,11 @@ class NIOUDP(NIO):
return self._rport
def __json__(self):
def asdict(self):
return {"type": "nio_udp", "lport": self._lport, "rport": self._rport, "rhost": self._rhost}
return {
"type": "nio_udp",
"lport": self._lport,
"rport": self._rport,
"rhost": self._rhost
}

View File

@ -79,6 +79,10 @@ class NIOUNIX(NIO):
return self._remote_file
def __json__(self):
def asdict(self):
return {"type": "nio_unix", "local_file": self._local_file, "remote_file": self._remote_file}
return {
"type": "nio_unix",
"local_file": self._local_file,
"remote_file": self._remote_file
}

View File

@ -79,6 +79,10 @@ class NIOVDE(NIO):
return self._local_file
def __json__(self):
def asdict(self):
return {"type": "nio_vde", "local_file": self._local_file, "control_file": self._control_file}
return {
"type": "nio_vde",
"local_file": self._local_file,
"control_file": self._control_file
}

View File

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

View File

@ -76,11 +76,11 @@ class C1700(Router):
self._clock_divisor = 8
self._sparsemem = False # never activate sparsemem for c1700 (unstable)
def __json__(self):
def asdict(self):
c1700_router_info = {"iomem": self._iomem, "chassis": self._chassis, "sparsemem": self._sparsemem}
router_info = Router.__json__(self)
router_info = Router.asdict(self)
router_info.update(c1700_router_info)
return router_info

View File

@ -93,11 +93,11 @@ class C2600(Router):
self._clock_divisor = 8
self._sparsemem = False # never activate sparsemem for c2600 (unstable)
def __json__(self):
def asdict(self):
c2600_router_info = {"iomem": self._iomem, "chassis": self._chassis, "sparsemem": self._sparsemem}
router_info = Router.__json__(self)
router_info = Router.asdict(self)
router_info.update(c2600_router_info)
return router_info

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -48,6 +48,9 @@ class NIOEthernet(NIO):
return "NIO Ethernet"
def __json__(self):
def asdict(self):
return {"type": "nio_ethernet", "ethernet_device": self._ethernet_device}
return {
"type": "nio_ethernet",
"ethernet_device": self._ethernet_device
}

View File

@ -48,6 +48,9 @@ class NIOTAP(NIO):
return "NIO TAP"
def __json__(self):
def asdict(self):
return {"type": "nio_tap", "tap_device": self._tap_device}
return {
"type": "nio_tap",
"tap_device": self._tap_device
}

View File

@ -72,6 +72,11 @@ class NIOUDP(NIO):
return "NIO UDP"
def __json__(self):
def asdict(self):
return {"type": "nio_udp", "lport": self._lport, "rport": self._rport, "rhost": self._rhost}
return {
"type": "nio_udp",
"lport": self._lport,
"rport": self._rport,
"rhost": self._rhost
}

View File

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

View File

@ -77,9 +77,13 @@ class Project:
log.info(f"Project {self._id} with path '{self._path}' created")
def __json__(self):
def asdict(self):
return {"name": self._name, "project_id": self._id, "variables": self._variables}
return {
"name": self._name,
"project_id": self._id,
"variables": self._variables
}
def is_local(self):

View File

@ -120,18 +120,3 @@ class ProjectManager:
if project_id not in self._projects:
raise ComputeNotFoundError(f"Project ID {project_id} doesn't exist")
del self._projects[project_id]
def check_hardware_virtualization(self, source_node):
"""
Checks if hardware virtualization can be used.
:returns: boolean
"""
for project in self._projects.values():
for node in project.nodes:
if node == source_node:
continue
if node.hw_virtualization and node.__class__.__name__ != source_node.__class__.__name__:
return False
return True

View File

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

View File

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

View File

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

View File

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

View File

@ -353,13 +353,14 @@ class Controller:
self._computes[compute.id] = compute
# self.save()
if connect:
# call compute.connect() later to give time to the controller to be fully started
asyncio.get_event_loop().call_later(1, lambda: asyncio.ensure_future(compute.connect()))
self.notification.controller_emit("compute.created", compute.__json__())
self.notification.controller_emit("compute.created", compute.asdict())
return compute
else:
if connect:
await self._computes[compute_id].connect()
self.notification.controller_emit("compute.updated", self._computes[compute_id].__json__())
self.notification.controller_emit("compute.updated", self._computes[compute_id].asdict())
return self._computes[compute_id]
async def close_compute_projects(self, compute):
@ -398,7 +399,7 @@ class Controller:
await compute.close()
del self._computes[compute_id]
# self.save()
self.notification.controller_emit("compute.deleted", compute.__json__())
self.notification.controller_emit("compute.deleted", compute.asdict())
@property
def notification(self):

View File

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

View File

@ -104,7 +104,7 @@ class ApplianceManager:
try:
with open(path, encoding="utf-8") as f:
appliance = Appliance(appliance_id, json.load(f), builtin=builtin)
json_data = appliance.__json__() # Check if loaded without error
json_data = appliance.asdict() # Check if loaded without error
if appliance.status != "broken":
self._appliances[appliance.id] = appliance
if not appliance.symbol or appliance.symbol.startswith(":/symbols/"):

View File

@ -21,7 +21,6 @@ import asyncio
import async_timeout
import socket
import json
import uuid
import sys
import io
from operator import itemgetter
@ -163,7 +162,7 @@ class Compute:
if self._http_session and not self._http_session.closed:
await self._http_session.close()
self._connected = False
self._controller.notification.controller_emit("compute.updated", self.__json__())
self._controller.notification.controller_emit("compute.updated", self.asdict())
self._controller.save()
async def close(self):
@ -291,10 +290,11 @@ class Compute:
def disk_usage_percent(self):
return self._disk_usage_percent
def __json__(self, topology_dump=False):
def asdict(self, topology_dump=False):
"""
:param topology_dump: Filter to keep only properties require for saving on disk
"""
if topology_dump:
return {
"compute_id": self._id,
@ -444,7 +444,7 @@ class Compute:
self._connected = True
self._connection_failure = 0
self._last_error = None
self._controller.notification.controller_emit("compute.updated", self.__json__())
self._controller.notification.controller_emit("compute.updated", self.asdict())
async def _connect_notification(self):
"""
@ -466,7 +466,7 @@ class Compute:
self._memory_usage_percent = event["memory_usage_percent"]
self._disk_usage_percent = event["disk_usage_percent"]
# FIXME: slow down number of compute events
self._controller.notification.controller_emit("compute.updated", self.__json__())
self._controller.notification.controller_emit("compute.updated", self.asdict())
else:
await self._controller.notification.dispatch(
action, event, project_id=project_id, compute_id=self.id
@ -486,14 +486,15 @@ class Compute:
log.info(f"Connection closed to compute '{self._id}' WebSocket '{ws_url}'")
# Try to reconnect after 1 second if server unavailable only if not during tests (otherwise we create a ressources usage bomb)
if self.id != "local" and not hasattr(sys, "_called_from_test") or not sys._called_from_test:
from gns3server.api.server import app
if not app.state.exiting and not hasattr(sys, "_called_from_test") or not sys._called_from_test:
log.info(f"Reconnecting to to compute '{self._id}' WebSocket '{ws_url}'")
asyncio.get_event_loop().call_later(1, lambda: asyncio.ensure_future(self.connect()))
self._cpu_usage_percent = None
self._memory_usage_percent = None
self._disk_usage_percent = None
self._controller.notification.controller_emit("compute.updated", self.__json__())
self._controller.notification.controller_emit("compute.updated", self.asdict())
def _getUrl(self, path):
host = self._host
@ -522,8 +523,8 @@ class Compute:
if data == {}:
data = None
elif data is not None:
if hasattr(data, "__json__"):
data = json.dumps(data.__json__())
if hasattr(data, "asdict"):
data = json.dumps(data.asdict())
elif isinstance(data, aiohttp.streams.EmptyStreamReader):
data = None
# Stream the request

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
svg_changed = True
setattr(self, prop, kwargs[prop])
data = self.__json__()
data = self.asdict()
if not svg_changed:
del data["svg"]
self._project.emit_notification("drawing.updated", data)
self._project.dump()
def __json__(self, topology_dump=False):
def asdict(self, topology_dump=False):
"""
:param topology_dump: Filter to keep only properties require for saving on disk
"""
if topology_dump:
return {
"drawing_id": self._id,

View File

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

View File

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

View File

@ -390,7 +390,6 @@ class Node:
data["node_id"] = self._id
if self._node_type == "docker":
timeout = None
else:
timeout = 1200
trial = 0
@ -420,7 +419,7 @@ class Node:
# When updating properties used only on controller we don't need to call the compute
update_compute = False
old_json = self.__json__()
old_json = self.asdict()
compute_properties = None
# Update node properties with additional elements
@ -450,9 +449,9 @@ class Node:
data = self._node_data(properties=compute_properties)
response = await self.put(None, data=data)
await self.parse_node_response(response.json)
elif old_json != self.__json__():
elif old_json != self.asdict():
# We send notif only if object has changed
self.project.emit_notification("node.updated", self.__json__())
self.project.emit_notification("node.updated", self.asdict())
self.project.dump()
async def parse_node_response(self, response):
@ -543,7 +542,7 @@ class Node:
if self.custom_adapters:
data["custom_adapters"] = self.custom_adapters
# None properties are not be send. Because it can mean the emulator doesn't support it
# None properties are not be sent because it can mean the emulator doesn't support it
for key in list(data.keys()):
if data[key] is None or data[key] is {} or key in self.CONTROLLER_ONLY_PROPERTIES:
del data[key]
@ -628,7 +627,7 @@ class Node:
async def put(self, path, data=None, **kwargs):
"""
HTTP post on the node
HTTP put on the node
"""
if path is None:
path = f"/projects/{self._project.id}/{self._node_type}/nodes/{self._id}"
@ -778,13 +777,12 @@ class Node:
return False
return self.id == other.id and other.project.id == self.project.id
def __json__(self, topology_dump=False):
def asdict(self, topology_dump=False):
"""
:param topology_dump: Filter to keep only properties require for saving on disk
:param topology_dump: Filter to keep only properties required for saving on disk
"""
if topology_dump:
return {
topology = {
"compute_id": str(self._compute.id),
"node_id": self._id,
"node_type": self._node_type,
@ -808,35 +806,18 @@ class Node:
"port_segment_size": self._port_segment_size,
"first_port_name": self._first_port_name,
"custom_adapters": self._custom_adapters,
}
return {
"compute_id": str(self._compute.id),
"project_id": self._project.id,
"node_id": self._id,
"template_id": self._template_id,
"node_type": self._node_type,
"node_directory": self._node_directory,
"name": self._name,
"console": self._console,
"console_host": str(self._compute.console_host),
"console_type": self._console_type,
"aux": self._aux,
"aux_type": self._aux_type,
"console_auto_start": self._console_auto_start,
"command_line": self._command_line,
"properties": self._properties,
"status": self._status,
"label": self._label,
"x": self._x,
"y": self._y,
"z": self._z,
"locked": self._locked,
"width": self._width,
"height": self._height,
"symbol": self._symbol,
"port_name_format": self._port_name_format,
"port_segment_size": self._port_segment_size,
"first_port_name": self._first_port_name,
"custom_adapters": self._custom_adapters,
"ports": [port.__json__() for port in self.ports],
}
if topology_dump:
return topology
additional_data = {
"project_id": self._project.id,
"command_line": self._command_line,
"status": self._status,
"console_host": str(self._compute.console_host),
"node_directory": self._node_directory,
"ports": [port.asdict() for port in self.ports]
}
topology.update(additional_data)
return topology

View File

@ -28,6 +28,7 @@ class Notification:
"""
def __init__(self, controller):
self._controller = controller
self._project_listeners = {}
self._controller_listeners = set()
@ -71,19 +72,6 @@ class Notification:
:param event: Event to send
"""
# If use in tests for documentation we save a sample
if os.environ.get("PYTEST_BUILD_DOCUMENTATION") == "1":
os.makedirs("docs/api/notifications", exist_ok=True)
try:
import json
data = json.dumps(event, indent=4, sort_keys=True)
if "MagicMock" not in data:
with open(os.path.join("docs/api/notifications", action + ".json"), "w+") as f:
f.write(data)
except TypeError: # If we receive a mock as an event it will raise TypeError when using json dump
pass
for controller_listener in self._controller_listeners:
controller_listener.put_nowait((action, event, {}))
@ -110,7 +98,7 @@ class Notification:
project = self._controller.get_project(event["project_id"])
node = project.get_node(event["node_id"])
await node.parse_node_response(event)
self.project_emit("node.updated", node.__json__())
self.project_emit("node.updated", node.asdict())
except ControllerError: # Project closing
return
elif action == "ping":
@ -127,19 +115,6 @@ class Notification:
:param event: Event to send
"""
# If use in tests for documentation we save a sample
if os.environ.get("PYTEST_BUILD_DOCUMENTATION") == "1":
os.makedirs("docs/api/notifications", exist_ok=True)
try:
import json
data = json.dumps(event, indent=4, sort_keys=True)
if "MagicMock" not in data:
with open(os.path.join("docs/api/notifications", action + ".json"), "w+") as f:
f.write(data)
except TypeError: # If we receive a mock as an event it will raise TypeError when using json dump
pass
if "project_id" in event or project_id:
self._send_event_to_project(event.get("project_id", project_id), action, event)
else:

View File

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

View File

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

View File

@ -36,10 +36,6 @@ import logging
log = logging.getLogger(__name__)
# The string use to extract the date from the filename
FILENAME_TIME_FORMAT = "%d%m%y_%H%M%S"
class Snapshot:
"""
A snapshot object
@ -59,7 +55,7 @@ class Snapshot:
filename = (
self._name
+ "_"
+ datetime.utcfromtimestamp(self._created_at).replace(tzinfo=None).strftime(FILENAME_TIME_FORMAT)
+ datetime.utcfromtimestamp(self._created_at).replace(tzinfo=None).strftime("%d%m%y_%H%M%S")
+ ".gns3project"
)
else:
@ -67,7 +63,7 @@ class Snapshot:
datestring = filename.replace(self._name + "_", "").split(".")[0]
try:
self._created_at = (
datetime.strptime(datestring, FILENAME_TIME_FORMAT).replace(tzinfo=timezone.utc).timestamp()
datetime.strptime(datestring, "%d%m%y_%H%M%S").replace(tzinfo=timezone.utc).timestamp()
)
except ValueError:
self._created_at = datetime.utcnow().timestamp()
@ -137,10 +133,10 @@ class Snapshot:
except (OSError, PermissionError) as e:
raise ControllerError(str(e))
await project.open()
self._project.emit_notification("snapshot.restored", self.__json__())
self._project.emit_notification("snapshot.restored", self.asdict())
return self._project
def __json__(self):
def asdict(self):
return {
"snapshot_id": self._id,
"name": self._name,

View File

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

View File

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

View File

@ -15,9 +15,14 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from sqlalchemy import Boolean, Column, String
from sqlalchemy import Boolean, Column, String, event
from .base import BaseTable, generate_uuid, GUID
from gns3server.services import auth_service
import logging
log = logging.getLogger(__name__)
class User(BaseTable):
@ -30,4 +35,19 @@ class User(BaseTable):
full_name = Column(String)
hashed_password = Column(String)
is_active = Column(Boolean, default=True)
is_superuser = Column(Boolean, default=False)
is_superadmin = Column(Boolean, default=False)
@event.listens_for(User.__table__, 'after_create')
def create_default_super_admin(target, connection, **kw):
hashed_password = auth_service.hash_password("admin")
stmt = target.insert().values(
username="admin",
full_name="Super Administrator",
hashed_password=hashed_password,
is_superadmin=True
)
connection.execute(stmt)
connection.commit()
log.info("The default super admin account has been created in the database")

View File

@ -26,6 +26,10 @@ import gns3server.db.models as models
from gns3server import schemas
from gns3server.services import auth_service
import logging
log = logging.getLogger(__name__)
class UsersRepository(BaseRepository):
def __init__(self, db_session: AsyncSession) -> None:
@ -59,7 +63,7 @@ class UsersRepository(BaseRepository):
async def create_user(self, user: schemas.UserCreate) -> models.User:
hashed_password = self._auth_service.hash_password(user.password)
hashed_password = self._auth_service.hash_password(user.password.get_secret_value())
db_user = models.User(
username=user.username, email=user.email, full_name=user.full_name, hashed_password=hashed_password
)
@ -73,7 +77,7 @@ class UsersRepository(BaseRepository):
update_values = user_update.dict(exclude_unset=True)
password = update_values.pop("password", None)
if password:
update_values["hashed_password"] = self._auth_service.hash_password(password=password)
update_values["hashed_password"] = self._auth_service.hash_password(password=password.get_secret_value())
query = update(models.User).where(models.User.user_id == user_id).values(update_values)
@ -93,6 +97,13 @@ class UsersRepository(BaseRepository):
user = await self.get_user_by_username(username)
if not user:
return None
# Allow user to be authenticated if hashed password in the db is null
# this is useful for manual password recovery like:
# sqlite3 gns3_controller.db "UPDATE users SET hashed_password = null WHERE username = 'admin';"
if user.hashed_password is None:
log.warning(f"User '{username}' has been authenticated without a password "
f"configured. Please set a new password.")
return user
if not self._auth_service.verify_password(password, user.hashed_password):
return None
return user

View File

@ -14,46 +14,36 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# General schemas
from .config import ServerConfig
from .iou_license import IOULicense
from .links import Link
from .common import ErrorMessage
from .version import Version
from .computes import ComputeCreate, ComputeUpdate, AutoIdlePC, Compute
from .templates import TemplateCreate, TemplateUpdate, TemplateUsage, Template
from .drawings import Drawing
from .gns3vm import GNS3VM
from .nodes import NodeUpdate, NodeDuplicate, NodeCapture, Node
from .projects import ProjectCreate, ProjectUpdate, ProjectDuplicate, Project, ProjectFile
from .users import UserCreate, UserUpdate, User
from .tokens import Token
from .snapshots import SnapshotCreate, Snapshot
from .capabilities import Capabilities
from .nios import UDPNIO, TAPNIO, EthernetNIO
from .atm_switch_nodes import ATMSwitchCreate, ATMSwitchUpdate, ATMSwitch
from .cloud_nodes import CloudCreate, CloudUpdate, Cloud
from .docker_nodes import DockerCreate, DockerUpdate, Docker
from .dynamips_nodes import DynamipsCreate, DynamipsUpdate, Dynamips
from .ethernet_hub_nodes import EthernetHubCreate, EthernetHubUpdate, EthernetHub
from .ethernet_switch_nodes import EthernetSwitchCreate, EthernetSwitchUpdate, EthernetSwitch
from .frame_relay_switch_nodes import FrameRelaySwitchCreate, FrameRelaySwitchUpdate, FrameRelaySwitch
from .qemu_nodes import QemuCreate, QemuUpdate, QemuImageCreate, QemuImageUpdate, QemuDiskResize, Qemu
from .iou_nodes import IOUCreate, IOUUpdate, IOUStart, IOU
from .nat_nodes import NATCreate, NATUpdate, NAT
from .vpcs_nodes import VPCSCreate, VPCSUpdate, VPCS
from .vmware_nodes import VMwareCreate, VMwareUpdate, VMware
from .virtualbox_nodes import VirtualBoxCreate, VirtualBoxUpdate, VirtualBox
from .vpcs_templates import VPCSTemplate
from .cloud_templates import CloudTemplate
from .iou_templates import IOUTemplate
from .docker_templates import DockerTemplate
from .ethernet_hub_templates import EthernetHubTemplate
from .ethernet_switch_templates import EthernetSwitchTemplate
from .virtualbox_templates import VirtualBoxTemplate
from .vmware_templates import VMwareTemplate
from .qemu_templates import QemuTemplate
from .dynamips_templates import (
# Controller schemas
from .controller.links import LinkCreate, LinkUpdate, Link
from .controller.computes import ComputeCreate, ComputeUpdate, AutoIdlePC, Compute
from .controller.templates import TemplateCreate, TemplateUpdate, TemplateUsage, Template
from .controller.drawings import Drawing
from .controller.gns3vm import GNS3VM
from .controller.nodes import NodeCreate, NodeUpdate, NodeDuplicate, NodeCapture, Node
from .controller.projects import ProjectCreate, ProjectUpdate, ProjectDuplicate, Project, ProjectFile
from .controller.users import UserCreate, UserUpdate, User, Credentials
from .controller.tokens import Token
from .controller.snapshots import SnapshotCreate, Snapshot
from .controller.iou_license import IOULicense
from .controller.capabilities import Capabilities
# Controller template schemas
from .controller.templates.vpcs_templates import VPCSTemplate
from .controller.templates.cloud_templates import CloudTemplate
from .controller.templates.iou_templates import IOUTemplate
from .controller.templates.docker_templates import DockerTemplate
from .controller.templates.ethernet_hub_templates import EthernetHubTemplate
from .controller.templates.ethernet_switch_templates import EthernetSwitchTemplate
from .controller.templates.virtualbox_templates import VirtualBoxTemplate
from .controller.templates.vmware_templates import VMwareTemplate
from .controller.templates.qemu_templates import QemuTemplate
from .controller.templates.dynamips_templates import (
DynamipsTemplate,
C1700DynamipsTemplate,
C2600DynamipsTemplate,
@ -63,3 +53,19 @@ from .dynamips_templates import (
C3745DynamipsTemplate,
C7200DynamipsTemplate,
)
# Compute schemas
from .compute.nios import UDPNIO, TAPNIO, EthernetNIO
from .compute.atm_switch_nodes import ATMSwitchCreate, ATMSwitchUpdate, ATMSwitch
from .compute.cloud_nodes import CloudCreate, CloudUpdate, Cloud
from .compute.docker_nodes import DockerCreate, DockerUpdate, Docker
from .compute.dynamips_nodes import DynamipsCreate, DynamipsUpdate, Dynamips
from .compute.ethernet_hub_nodes import EthernetHubCreate, EthernetHubUpdate, EthernetHub
from .compute.ethernet_switch_nodes import EthernetSwitchCreate, EthernetSwitchUpdate, EthernetSwitch
from .compute.frame_relay_switch_nodes import FrameRelaySwitchCreate, FrameRelaySwitchUpdate, FrameRelaySwitch
from .compute.qemu_nodes import QemuCreate, QemuUpdate, QemuImageCreate, QemuImageUpdate, QemuDiskResize, Qemu
from .compute.iou_nodes import IOUCreate, IOUUpdate, IOUStart, IOU
from .compute.nat_nodes import NATCreate, NATUpdate, NAT
from .compute.vpcs_nodes import VPCSCreate, VPCSUpdate, VPCS
from .compute.vmware_nodes import VMwareCreate, VMwareUpdate, VMware
from .compute.virtualbox_nodes import VirtualBoxCreate, VirtualBoxUpdate, VirtualBox

View File

@ -16,6 +16,7 @@
from pydantic import BaseModel, Field
from typing import Optional, Union
from enum import Enum
class ErrorMessage(BaseModel):
@ -26,14 +27,45 @@ class ErrorMessage(BaseModel):
message: str
class Label(BaseModel):
class NodeStatus(str, Enum):
"""
Label data.
Supported node statuses.
"""
text: str
style: Optional[Union[str, None]] = Field(None, description="SVG style attribute. Apply default style if null")
x: Optional[Union[int, None]] = Field(None, description="Relative X position of the label. Center it if null")
y: Optional[int] = Field(None, description="Relative Y position of the label")
rotation: Optional[int] = Field(None, ge=-359, le=360, description="Rotation of the label")
stopped = "stopped"
started = "started"
suspended = "suspended"
class CustomAdapter(BaseModel):
"""
Custom adapter data.
"""
adapter_number: int
port_name: Optional[str] = None
adapter_type: Optional[str] = None
mac_address: Optional[str] = Field(None, regex="^([0-9a-fA-F]{2}[:]){5}([0-9a-fA-F]{2})$")
class ConsoleType(str, Enum):
"""
Supported console types.
"""
vnc = "vnc"
telnet = "telnet"
http = "http"
https = "https"
spice = "spice"
spice_agent = "spice+agent"
none = "none"
class AuxType(str, Enum):
"""
Supported auxiliary console types.
"""
telnet = "telnet"
none = "none"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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