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:
commit
148dc8e692
@ -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"])
|
||||
|
@ -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.
|
||||
|
@ -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:
|
||||
|
@ -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.
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
"""
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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.
|
||||
"""
|
||||
|
@ -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"]
|
||||
)
|
||||
|
@ -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()]
|
||||
|
@ -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.
|
||||
"""
|
||||
|
@ -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.
|
||||
"""
|
||||
|
@ -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,
|
||||
|
@ -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.
|
||||
"""
|
||||
|
@ -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()
|
||||
|
@ -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.
|
||||
"""
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
"""
|
||||
|
@ -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.
|
||||
"""
|
||||
|
@ -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()
|
||||
|
@ -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.
|
||||
"""
|
||||
|
@ -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()
|
||||
|
@ -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.
|
||||
|
@ -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):
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -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,
|
||||
|
@ -133,7 +133,7 @@ class DockerVM(BaseNode):
|
||||
)
|
||||
)
|
||||
|
||||
def __json__(self):
|
||||
def asdict(self):
|
||||
return {
|
||||
"name": self._name,
|
||||
"usage": self.usage,
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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"}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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():
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -53,7 +53,7 @@ class EthernetHub(Bridge):
|
||||
else:
|
||||
self._ports = ports
|
||||
|
||||
def __json__(self):
|
||||
def asdict(self):
|
||||
|
||||
return {
|
||||
"name": self.name,
|
||||
|
@ -109,7 +109,7 @@ class EthernetSwitch(Device):
|
||||
else:
|
||||
self._ports = ports
|
||||
|
||||
def __json__(self):
|
||||
def asdict(self):
|
||||
|
||||
ethernet_switch_info = {
|
||||
"name": self.name,
|
||||
|
@ -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():
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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):
|
||||
|
||||
|
@ -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
|
||||
|
@ -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"]:
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -120,7 +120,7 @@ class VPCSVM(BaseNode):
|
||||
|
||||
await self._check_vpcs_version()
|
||||
|
||||
def __json__(self):
|
||||
def asdict(self):
|
||||
|
||||
return {
|
||||
"name": self.name,
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
"""
|
||||
|
@ -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/"):
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
"""
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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):
|
@ -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):
|
@ -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):
|
@ -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):
|
@ -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):
|
@ -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):
|
@ -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):
|
@ -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):
|
@ -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
Loading…
Reference in New Issue
Block a user