mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-04 22:41:12 +02:00
refactor: ruff autofixes
This commit is contained in:
455 files changed
+12322
-14762
No files matched your search
@@ -155,113 +155,71 @@ async def http_exception_handler(request: Request, exc: StarletteHTTPException):
|
||||
)
|
||||
|
||||
|
||||
compute_api.include_router(
|
||||
capabilities.router,
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
tags=["Capabilities"]
|
||||
)
|
||||
compute_api.include_router(capabilities.router, dependencies=[Depends(compute_authentication)], tags=["Capabilities"])
|
||||
|
||||
compute_api.include_router(
|
||||
compute.router,
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
tags=["Compute"]
|
||||
)
|
||||
compute_api.include_router(compute.router, dependencies=[Depends(compute_authentication)], tags=["Compute"])
|
||||
|
||||
compute_api.include_router(
|
||||
notifications.router,
|
||||
tags=["Notifications"]
|
||||
)
|
||||
compute_api.include_router(notifications.router, tags=["Notifications"])
|
||||
|
||||
compute_api.include_router(
|
||||
projects.router,
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
tags=["Projects"]
|
||||
)
|
||||
compute_api.include_router(projects.router, dependencies=[Depends(compute_authentication)], tags=["Projects"])
|
||||
|
||||
compute_api.include_router(
|
||||
images.router,
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
tags=["Images"]
|
||||
)
|
||||
compute_api.include_router(images.router, dependencies=[Depends(compute_authentication)], tags=["Images"])
|
||||
|
||||
compute_api.include_router(
|
||||
atm_switch_nodes.router,
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
prefix="/projects/{project_id}/atm_switch/nodes",
|
||||
tags=["ATM switch"]
|
||||
tags=["ATM switch"],
|
||||
)
|
||||
compute_api.include_router(
|
||||
cloud_nodes.router,
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
prefix="/projects/{project_id}/cloud/nodes",
|
||||
tags=["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(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"]
|
||||
dynamips_nodes.router, prefix="/projects/{project_id}/dynamips/nodes", tags=["Dynamips nodes"]
|
||||
)
|
||||
|
||||
compute_api.include_router(
|
||||
ethernet_hub_nodes.router,
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
prefix="/projects/{project_id}/ethernet_hub/nodes",
|
||||
tags=["Ethernet hub nodes"]
|
||||
tags=["Ethernet hub nodes"],
|
||||
)
|
||||
|
||||
compute_api.include_router(
|
||||
ethernet_switch_nodes.router,
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
prefix="/projects/{project_id}/ethernet_switch/nodes",
|
||||
tags=["Ethernet switch nodes"]
|
||||
tags=["Ethernet switch nodes"],
|
||||
)
|
||||
|
||||
compute_api.include_router(
|
||||
frame_relay_switch_nodes.router,
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
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(iou_nodes.router, prefix="/projects/{project_id}/iou/nodes", tags=["IOU nodes"])
|
||||
|
||||
compute_api.include_router(
|
||||
nat_nodes.router,
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
prefix="/projects/{project_id}/nat/nodes",
|
||||
tags=["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(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"]
|
||||
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(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(vpcs_nodes.router, prefix="/projects/{project_id}/vpcs/nodes", tags=["VPCS nodes"])
|
||||
@@ -79,8 +79,7 @@ def get_atm_switch(node: ATMSwitch = Depends(dep_node)) -> schemas.ATMSwitch:
|
||||
|
||||
@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)
|
||||
destination_node_id: UUID = Body(..., embed=True), node: ATMSwitch = Depends(dep_node)
|
||||
) -> schemas.ATMSwitch:
|
||||
"""
|
||||
Duplicate an ATM switch node.
|
||||
@@ -92,8 +91,7 @@ async def duplicate_atm_switch(
|
||||
|
||||
@router.put("/{node_id}", response_model=schemas.ATMSwitch)
|
||||
async def update_atm_switch(
|
||||
node_data: schemas.ATMSwitchUpdate,
|
||||
node: ATMSwitch = Depends(dep_node)
|
||||
node_data: schemas.ATMSwitchUpdate, node: ATMSwitch = Depends(dep_node)
|
||||
) -> schemas.ATMSwitch:
|
||||
"""
|
||||
Update an ATM switch node.
|
||||
@@ -124,8 +122,7 @@ def start_atm_switch(node: ATMSwitch = Depends(dep_node)) -> None:
|
||||
"""
|
||||
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_405_METHOD_NOT_ALLOWED,
|
||||
detail="Start is not supported for ATM switches"
|
||||
status_code=status.HTTP_405_METHOD_NOT_ALLOWED, detail="Start is not supported for ATM switches"
|
||||
)
|
||||
|
||||
|
||||
@@ -135,10 +132,7 @@ def stop_atm_switch(node: ATMSwitch = Depends(dep_node)) -> None:
|
||||
Stop an ATM switch node.
|
||||
"""
|
||||
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_405_METHOD_NOT_ALLOWED,
|
||||
detail="Stop is not supported for ATM switches"
|
||||
)
|
||||
raise HTTPException(status_code=status.HTTP_405_METHOD_NOT_ALLOWED, detail="Stop is not supported for ATM switches")
|
||||
|
||||
|
||||
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
||||
@@ -149,8 +143,7 @@ def suspend_atm_switch(node: ATMSwitch = Depends(dep_node)) -> None:
|
||||
"""
|
||||
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_405_METHOD_NOT_ALLOWED,
|
||||
detail="Suspend is not supported for ATM switches"
|
||||
status_code=status.HTTP_405_METHOD_NOT_ALLOWED, detail="Suspend is not supported for ATM switches"
|
||||
)
|
||||
|
||||
|
||||
@@ -160,11 +153,11 @@ def suspend_atm_switch(node: ATMSwitch = Depends(dep_node)) -> None:
|
||||
response_model=schemas.UDPNIO,
|
||||
)
|
||||
async def create_nio(
|
||||
*,
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
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.
|
||||
@@ -189,11 +182,11 @@ 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 = Path(..., ge=0, le=0),
|
||||
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.
|
||||
@@ -210,10 +203,7 @@ async def start_capture(
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
)
|
||||
async def stop_capture(
|
||||
*,
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
port_number: int,
|
||||
node: ATMSwitch = Depends(dep_node)
|
||||
*, adapter_number: int = Path(..., ge=0, le=0), port_number: int, node: ATMSwitch = Depends(dep_node)
|
||||
) -> None:
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
@@ -225,10 +215,7 @@ async def stop_capture(
|
||||
|
||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
|
||||
async def stream_pcap_file(
|
||||
*,
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
port_number: int,
|
||||
node: ATMSwitch = Depends(dep_node)
|
||||
*, adapter_number: int = Path(..., ge=0, le=0), port_number: int, node: ATMSwitch = Depends(dep_node)
|
||||
) -> StreamingResponse:
|
||||
"""
|
||||
Stream the pcap capture file.
|
||||
|
||||
@@ -122,10 +122,7 @@ async def stop_cloud(node: Cloud = Depends(dep_node)) -> None:
|
||||
Stop a cloud node.
|
||||
"""
|
||||
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_405_METHOD_NOT_ALLOWED,
|
||||
detail="Stop is not supported for cloud nodes"
|
||||
)
|
||||
raise HTTPException(status_code=status.HTTP_405_METHOD_NOT_ALLOWED, detail="Stop is not supported for cloud nodes")
|
||||
|
||||
|
||||
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
||||
@@ -136,8 +133,7 @@ async def suspend_cloud(node: Cloud = Depends(dep_node)) -> None:
|
||||
"""
|
||||
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_405_METHOD_NOT_ALLOWED,
|
||||
detail="Suspend is not supported for cloud nodes"
|
||||
status_code=status.HTTP_405_METHOD_NOT_ALLOWED, detail="Suspend is not supported for cloud nodes"
|
||||
)
|
||||
|
||||
|
||||
@@ -147,11 +143,11 @@ async def suspend_cloud(node: Cloud = Depends(dep_node)) -> None:
|
||||
response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
|
||||
)
|
||||
async def create_cloud_nio(
|
||||
*,
|
||||
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),
|
||||
*,
|
||||
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.
|
||||
@@ -169,11 +165,11 @@ async def create_cloud_nio(
|
||||
response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
|
||||
)
|
||||
async def update_cloud_nio(
|
||||
*,
|
||||
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),
|
||||
*,
|
||||
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.
|
||||
@@ -192,10 +188,7 @@ async def update_cloud_nio(
|
||||
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_cloud_nio(
|
||||
*,
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
port_number: int,
|
||||
node: Cloud = Depends(dep_node)
|
||||
*, 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.
|
||||
@@ -207,11 +200,11 @@ async def delete_cloud_nio(
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
||||
async def start_cloud_capture(
|
||||
*,
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
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.
|
||||
@@ -227,10 +220,7 @@ async def start_cloud_capture(
|
||||
"/{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 = Path(..., ge=0, le=0),
|
||||
port_number: int,
|
||||
node: Cloud = Depends(dep_node)
|
||||
*, adapter_number: int = Path(..., ge=0, le=0), port_number: int, node: Cloud = Depends(dep_node)
|
||||
) -> None:
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
@@ -242,10 +232,7 @@ async def stop_cloud_capture(
|
||||
|
||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
|
||||
async def stream_pcap_file(
|
||||
*,
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
port_number: int,
|
||||
node: Cloud = Depends(dep_node)
|
||||
*, adapter_number: int = Path(..., ge=0, le=0), port_number: int, node: Cloud = Depends(dep_node)
|
||||
) -> StreamingResponse:
|
||||
"""
|
||||
Stream the pcap capture file.
|
||||
@@ -257,13 +244,9 @@ async def stream_pcap_file(
|
||||
return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap")
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{node_id}/markers/{marker_name}"
|
||||
)
|
||||
@router.put("/{node_id}/markers/{marker_name}")
|
||||
async def toggle_cloud_marker(
|
||||
marker_name: str,
|
||||
toggle_data: schemas.MarkerToggle,
|
||||
node: Cloud = Depends(dep_node)
|
||||
marker_name: str, toggle_data: schemas.MarkerToggle, node: Cloud = Depends(dep_node)
|
||||
) -> dict:
|
||||
"""
|
||||
Toggle a marker filter on/off without an NIO rebuild (ubridge contract §3.2).
|
||||
@@ -278,19 +261,13 @@ async def toggle_cloud_marker(
|
||||
return {"marker_name": marker_name, "enabled": toggle_data.enabled}
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/markers/pause",
|
||||
status_code=status.HTTP_204_NO_CONTENT
|
||||
)
|
||||
@router.post("/{node_id}/markers/pause", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def pause_cloud_markers(node: Cloud = Depends(dep_node)) -> None:
|
||||
|
||||
await node._ubridge_marker_pause()
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/markers/resume",
|
||||
status_code=status.HTTP_204_NO_CONTENT
|
||||
)
|
||||
@router.post("/{node_id}/markers/resume", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def resume_cloud_markers(node: Cloud = Depends(dep_node)) -> None:
|
||||
|
||||
await node._ubridge_marker_resume()
|
||||
@@ -298,7 +275,7 @@ async def resume_cloud_markers(node: Cloud = Depends(dep_node)) -> None:
|
||||
|
||||
@router.delete(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/markers/{marker_name}",
|
||||
status_code=status.HTTP_204_NO_CONTENT
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
)
|
||||
async def delete_cloud_marker_capture(
|
||||
*,
|
||||
@@ -306,7 +283,7 @@ async def delete_cloud_marker_capture(
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
port_number: int,
|
||||
link_id: str = "",
|
||||
node: Cloud = Depends(dep_node)
|
||||
node: Cloud = Depends(dep_node),
|
||||
) -> None:
|
||||
"""
|
||||
Delete a marker's capture pcap (called by the controller when the marker is
|
||||
@@ -321,9 +298,7 @@ async def delete_cloud_marker_capture(
|
||||
|
||||
@router.put("/{node_id}/markers/{marker_name}/rebuild")
|
||||
async def rebuild_cloud_marker(
|
||||
marker_name: str,
|
||||
rebuild_data: schemas.MarkerRebuild,
|
||||
node: Cloud = Depends(dep_node)
|
||||
marker_name: str, rebuild_data: schemas.MarkerRebuild, node: Cloud = Depends(dep_node)
|
||||
) -> dict:
|
||||
"""
|
||||
Re-install a single marker filter with new BPF/tag/direction (delete + add,
|
||||
@@ -331,7 +306,11 @@ async def rebuild_cloud_marker(
|
||||
"""
|
||||
|
||||
await node.rebuild_marker_filter(
|
||||
marker_name, rebuild_data.link_id, rebuild_data.bpf,
|
||||
rebuild_data.tag, rebuild_data.direction, rebuild_data.enabled,
|
||||
marker_name,
|
||||
rebuild_data.link_id,
|
||||
rebuild_data.bpf,
|
||||
rebuild_data.tag,
|
||||
rebuild_data.direction,
|
||||
rebuild_data.enabled,
|
||||
)
|
||||
return {"marker_name": marker_name}
|
||||
@@ -32,7 +32,7 @@ security = HTTPBasic(auto_error=False)
|
||||
def compute_authentication(credentials: Optional[HTTPBasicCredentials] = Depends(security)) -> None:
|
||||
"""
|
||||
Authenticate compute requests.
|
||||
|
||||
|
||||
Returns None if authentication is disabled or if authentication succeeds
|
||||
Raises HTTPException if authentication is required but credentials are invalid
|
||||
"""
|
||||
@@ -58,9 +58,9 @@ def compute_authentication(credentials: Optional[HTTPBasicCredentials] = Depends
|
||||
headers={"WWW-Authenticate": "Basic"},
|
||||
)
|
||||
|
||||
|
||||
async def ws_compute_authentication(websocket: WebSocket) -> Union[None, WebSocket]:
|
||||
"""
|
||||
"""
|
||||
""" """
|
||||
|
||||
server_settings = Config.instance().settings.Server
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ def dep_node(project_id: UUID, node_id: UUID) -> DockerVM:
|
||||
response_model=schemas.Docker,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Docker node"}},
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def create_docker_node(project_id: UUID, node_data: schemas.DockerCreate) -> schemas.Docker:
|
||||
"""
|
||||
@@ -87,10 +87,23 @@ async def create_docker_node(project_id: UUID, node_data: schemas.DockerCreate)
|
||||
# fallback loop below only applies truly extra keys and does not
|
||||
# re-trigger console/aux port setter logging.
|
||||
for key in (
|
||||
"console", "console_type", "console_resolution", "console_http_port",
|
||||
"console_http_path", "aux", "aux_type", "start_command", "environment",
|
||||
"adapters", "mac_address", "extra_hosts", "extra_volumes", "extra_configs",
|
||||
"memory", "cpus", "image_digest",
|
||||
"console",
|
||||
"console_type",
|
||||
"console_resolution",
|
||||
"console_http_port",
|
||||
"console_http_path",
|
||||
"aux",
|
||||
"aux_type",
|
||||
"start_command",
|
||||
"environment",
|
||||
"adapters",
|
||||
"mac_address",
|
||||
"extra_hosts",
|
||||
"extra_volumes",
|
||||
"extra_configs",
|
||||
"memory",
|
||||
"cpus",
|
||||
"image_digest",
|
||||
):
|
||||
node_data.pop(key, None)
|
||||
for name, value in node_data.items():
|
||||
@@ -101,11 +114,7 @@ async def create_docker_node(project_id: UUID, node_data: schemas.DockerCreate)
|
||||
return container.asdict()
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{node_id}",
|
||||
response_model=schemas.Docker,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
@router.get("/{node_id}", response_model=schemas.Docker, dependencies=[Depends(compute_authentication)])
|
||||
def get_docker_node(node: DockerVM = Depends(dep_node)) -> schemas.Docker:
|
||||
"""
|
||||
Return a Docker node.
|
||||
@@ -114,11 +123,7 @@ def get_docker_node(node: DockerVM = Depends(dep_node)) -> schemas.Docker:
|
||||
return node.asdict()
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{node_id}",
|
||||
response_model=schemas.Docker,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
@router.put("/{node_id}", response_model=schemas.Docker, dependencies=[Depends(compute_authentication)])
|
||||
async def update_docker_node(node_data: schemas.DockerUpdate, node: DockerVM = Depends(dep_node)) -> schemas.Docker:
|
||||
"""
|
||||
Update a Docker node.
|
||||
@@ -160,11 +165,7 @@ async def update_docker_node(node_data: schemas.DockerUpdate, node: DockerVM = D
|
||||
return node.asdict()
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/start",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)])
|
||||
async def start_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Start a Docker node.
|
||||
@@ -173,11 +174,7 @@ async def start_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
||||
await node.start()
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)])
|
||||
async def stop_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Stop a Docker node. This is the explicit user stop — the only path that
|
||||
@@ -189,9 +186,7 @@ async def stop_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/suspend",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
"/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def suspend_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
@@ -202,9 +197,7 @@ async def suspend_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/reload",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
"/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def reload_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
@@ -214,11 +207,7 @@ async def reload_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
||||
await node.restart()
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/pause",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
@router.post("/{node_id}/pause", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)])
|
||||
async def pause_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Pause a Docker node.
|
||||
@@ -228,9 +217,7 @@ async def pause_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/unpause",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
"/{node_id}/unpause", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def unpause_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
@@ -240,11 +227,7 @@ async def unpause_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
||||
await node.unpause()
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{node_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)])
|
||||
async def delete_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Delete a Docker node.
|
||||
@@ -258,11 +241,10 @@ async def delete_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
||||
"/{node_id}/duplicate",
|
||||
response_model=schemas.Docker,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def duplicate_docker_node(
|
||||
destination_node_id: UUID = Body(..., embed=True),
|
||||
node: DockerVM = Depends(dep_node)
|
||||
destination_node_id: UUID = Body(..., embed=True), node: DockerVM = Depends(dep_node)
|
||||
) -> schemas.Docker:
|
||||
"""
|
||||
Duplicate a Docker node.
|
||||
@@ -276,7 +258,7 @@ async def duplicate_docker_node(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def create_docker_node_nio(
|
||||
adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: DockerVM = Depends(dep_node)
|
||||
@@ -295,7 +277,7 @@ async def create_docker_node_nio(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def update_docker_node_nio(
|
||||
adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: DockerVM = Depends(dep_node)
|
||||
@@ -318,13 +300,9 @@ async def update_docker_node_nio(
|
||||
@router.delete(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def delete_docker_node_nio(
|
||||
adapter_number: int,
|
||||
port_number: int,
|
||||
node: DockerVM = Depends(dep_node)
|
||||
) -> None:
|
||||
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.
|
||||
@@ -335,13 +313,10 @@ async def delete_docker_node_nio(
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start",
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
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.
|
||||
@@ -356,13 +331,9 @@ 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,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def stop_docker_node_capture(
|
||||
adapter_number: int,
|
||||
port_number: int,
|
||||
node: DockerVM = Depends(dep_node)
|
||||
) -> None:
|
||||
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.
|
||||
@@ -373,12 +344,10 @@ async def stop_docker_node_capture(
|
||||
|
||||
@router.get(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream",
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def stream_pcap_file(
|
||||
adapter_number: int,
|
||||
port_number: int,
|
||||
node: DockerVM = Depends(dep_node)
|
||||
adapter_number: int, port_number: int, node: DockerVM = Depends(dep_node)
|
||||
) -> StreamingResponse:
|
||||
"""
|
||||
Stream the pcap capture file.
|
||||
@@ -392,8 +361,7 @@ async def stream_pcap_file(
|
||||
|
||||
@router.websocket("/{node_id}/console/ws")
|
||||
async def console_ws(
|
||||
websocket: Union[None, WebSocket] = Depends(ws_compute_authentication),
|
||||
node: DockerVM = Depends(dep_node)
|
||||
websocket: Union[None, WebSocket] = Depends(ws_compute_authentication), node: DockerVM = Depends(dep_node)
|
||||
) -> None:
|
||||
"""
|
||||
Console WebSocket.
|
||||
@@ -403,12 +371,9 @@ async def console_ws(
|
||||
await node.start_websocket_console(websocket)
|
||||
|
||||
|
||||
@router.websocket(
|
||||
"/{node_id}/console/vnc"
|
||||
)
|
||||
@router.websocket("/{node_id}/console/vnc")
|
||||
async def vnc_console_ws(
|
||||
websocket: Union[None, WebSocket] = Depends(ws_compute_authentication),
|
||||
node: DockerVM = Depends(dep_node)
|
||||
websocket: Union[None, WebSocket] = Depends(ws_compute_authentication), node: DockerVM = Depends(dep_node)
|
||||
) -> None:
|
||||
"""
|
||||
VNC Console WebSocket.
|
||||
@@ -419,23 +384,16 @@ async def vnc_console_ws(
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/console/reset",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
"/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def reset_console(node: DockerVM = Depends(dep_node)) -> None:
|
||||
|
||||
await node.reset_console()
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{node_id}/markers/{marker_name}",
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
@router.put("/{node_id}/markers/{marker_name}", dependencies=[Depends(compute_authentication)])
|
||||
async def toggle_docker_marker(
|
||||
marker_name: str,
|
||||
toggle_data: schemas.MarkerToggle,
|
||||
node: DockerVM = Depends(dep_node)
|
||||
marker_name: str, toggle_data: schemas.MarkerToggle, node: DockerVM = Depends(dep_node)
|
||||
) -> dict:
|
||||
"""
|
||||
Toggle a marker filter on/off without an NIO rebuild (ubridge contract §3.2).
|
||||
@@ -451,9 +409,7 @@ async def toggle_docker_marker(
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/markers/pause",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
"/{node_id}/markers/pause", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def pause_docker_markers(node: DockerVM = Depends(dep_node)) -> None:
|
||||
|
||||
@@ -461,9 +417,7 @@ async def pause_docker_markers(node: DockerVM = Depends(dep_node)) -> None:
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/markers/resume",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
"/{node_id}/markers/resume", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def resume_docker_markers(node: DockerVM = Depends(dep_node)) -> None:
|
||||
|
||||
@@ -473,14 +427,10 @@ async def resume_docker_markers(node: DockerVM = Depends(dep_node)) -> None:
|
||||
@router.delete(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/markers/{marker_name}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def delete_docker_marker_capture(
|
||||
marker_name: str,
|
||||
adapter_number: int,
|
||||
port_number: int,
|
||||
link_id: str = "",
|
||||
node: DockerVM = Depends(dep_node)
|
||||
marker_name: str, adapter_number: int, port_number: int, link_id: str = "", node: DockerVM = Depends(dep_node)
|
||||
) -> None:
|
||||
"""
|
||||
Delete a marker's capture pcap (called by the controller when the marker is
|
||||
@@ -493,14 +443,9 @@ async def delete_docker_marker_capture(
|
||||
await node.delete_marker_capture(marker_name, link_id, nio)
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{node_id}/markers/{marker_name}/rebuild",
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
@router.put("/{node_id}/markers/{marker_name}/rebuild", dependencies=[Depends(compute_authentication)])
|
||||
async def rebuild_docker_marker(
|
||||
marker_name: str,
|
||||
rebuild_data: schemas.MarkerRebuild,
|
||||
node: DockerVM = Depends(dep_node)
|
||||
marker_name: str, rebuild_data: schemas.MarkerRebuild, node: DockerVM = Depends(dep_node)
|
||||
) -> dict:
|
||||
"""
|
||||
Re-install a single marker filter with new BPF/tag/direction (delete + add,
|
||||
@@ -508,7 +453,11 @@ async def rebuild_docker_marker(
|
||||
"""
|
||||
|
||||
await node.rebuild_marker_filter(
|
||||
marker_name, rebuild_data.link_id, rebuild_data.bpf,
|
||||
rebuild_data.tag, rebuild_data.direction, rebuild_data.enabled,
|
||||
marker_name,
|
||||
rebuild_data.link_id,
|
||||
rebuild_data.bpf,
|
||||
rebuild_data.tag,
|
||||
rebuild_data.direction,
|
||||
rebuild_data.enabled,
|
||||
)
|
||||
return {"marker_name": marker_name}
|
||||
@@ -55,7 +55,7 @@ def dep_node(project_id: UUID, node_id: UUID) -> Router:
|
||||
response_model=schemas.Dynamips,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Dynamips node"}},
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def create_router(project_id: UUID, node_data: schemas.DynamipsCreate) -> schemas.Dynamips:
|
||||
"""
|
||||
@@ -86,11 +86,7 @@ async def create_router(project_id: UUID, node_data: schemas.DynamipsCreate) ->
|
||||
return vm.asdict()
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{node_id}",
|
||||
response_model=schemas.Dynamips,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
@router.get("/{node_id}", response_model=schemas.Dynamips, dependencies=[Depends(compute_authentication)])
|
||||
def get_router(node: Router = Depends(dep_node)) -> schemas.Dynamips:
|
||||
"""
|
||||
Return Dynamips router.
|
||||
@@ -99,11 +95,7 @@ def get_router(node: Router = Depends(dep_node)) -> schemas.Dynamips:
|
||||
return node.asdict()
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{node_id}",
|
||||
response_model=schemas.Dynamips,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
@router.put("/{node_id}", response_model=schemas.Dynamips, dependencies=[Depends(compute_authentication)])
|
||||
async def update_router(node_data: schemas.DynamipsUpdate, node: Router = Depends(dep_node)) -> schemas.Dynamips:
|
||||
"""
|
||||
Update a Dynamips router.
|
||||
@@ -114,11 +106,7 @@ async def update_router(node_data: schemas.DynamipsUpdate, node: Router = Depend
|
||||
return node.asdict()
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{node_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)])
|
||||
async def delete_router(node: Router = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Delete a Dynamips router.
|
||||
@@ -127,11 +115,7 @@ async def delete_router(node: Router = Depends(dep_node)) -> None:
|
||||
await Dynamips.instance().delete_node(node.id)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/start",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)])
|
||||
async def start_router(node: Router = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Start a Dynamips router.
|
||||
@@ -144,11 +128,7 @@ async def start_router(node: Router = Depends(dep_node)) -> None:
|
||||
await node.start()
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)])
|
||||
async def stop_router(node: Router = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Stop a Dynamips router.
|
||||
@@ -158,9 +138,7 @@ async def stop_router(node: Router = Depends(dep_node)) -> None:
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/suspend",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
"/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def suspend_router(node: Router = Depends(dep_node)) -> None:
|
||||
|
||||
@@ -168,9 +146,7 @@ async def suspend_router(node: Router = Depends(dep_node)) -> None:
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/resume",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
"/{node_id}/resume", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def resume_router(node: Router = Depends(dep_node)) -> None:
|
||||
"""
|
||||
@@ -181,9 +157,7 @@ async def resume_router(node: Router = Depends(dep_node)) -> None:
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/reload",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
"/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def reload_router(node: Router = Depends(dep_node)) -> None:
|
||||
"""
|
||||
@@ -197,13 +171,10 @@ async def reload_router(node: Router = Depends(dep_node)) -> None:
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def create_nio(
|
||||
adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: schemas.UDPNIO,
|
||||
node: Router = Depends(dep_node)
|
||||
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.
|
||||
@@ -218,13 +189,10 @@ async def create_nio(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def update_nio(
|
||||
adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: schemas.UDPNIO,
|
||||
node: Router = Depends(dep_node)
|
||||
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.
|
||||
@@ -242,7 +210,7 @@ async def update_nio(
|
||||
@router.delete(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def delete_nio(adapter_number: int, port_number: int, node: Router = Depends(dep_node)) -> None:
|
||||
"""
|
||||
@@ -255,13 +223,10 @@ 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",
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
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.
|
||||
@@ -275,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,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def stop_capture(adapter_number: int, port_number: int, node: Router = Depends(dep_node)) -> None:
|
||||
"""
|
||||
@@ -287,12 +252,10 @@ 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",
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def stream_pcap_file(
|
||||
adapter_number: int,
|
||||
port_number: int,
|
||||
node: Router = Depends(dep_node)
|
||||
adapter_number: int, port_number: int, node: Router = Depends(dep_node)
|
||||
) -> StreamingResponse:
|
||||
"""
|
||||
Stream the pcap capture file.
|
||||
@@ -303,10 +266,7 @@ async def stream_pcap_file(
|
||||
return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap")
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{node_id}/idlepc_proposals",
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
@router.get("/{node_id}/idlepc_proposals", dependencies=[Depends(compute_authentication)])
|
||||
async def get_idlepcs(node: Router = Depends(dep_node)) -> List[str]:
|
||||
"""
|
||||
Retrieve Dynamips idle-pc proposals
|
||||
@@ -316,10 +276,7 @@ async def get_idlepcs(node: Router = Depends(dep_node)) -> List[str]:
|
||||
return await node.get_idle_pc_prop()
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{node_id}/auto_idlepc",
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
@router.get("/{node_id}/auto_idlepc", dependencies=[Depends(compute_authentication)])
|
||||
async def get_auto_idlepc(node: Router = Depends(dep_node)) -> dict:
|
||||
"""
|
||||
Get an automatically guessed best idle-pc value.
|
||||
@@ -333,9 +290,11 @@ async def get_auto_idlepc(node: Router = Depends(dep_node)) -> dict:
|
||||
"/{node_id}/duplicate",
|
||||
response_model=schemas.Dynamips,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def duplicate_router(destination_node_id: UUID = Body(..., embed=True), node: Router = Depends(dep_node)) -> schemas.Dynamips:
|
||||
async def duplicate_router(
|
||||
destination_node_id: UUID = Body(..., embed=True), node: Router = Depends(dep_node)
|
||||
) -> schemas.Dynamips:
|
||||
"""
|
||||
Duplicate a router.
|
||||
"""
|
||||
@@ -346,9 +305,7 @@ async def duplicate_router(destination_node_id: UUID = Body(..., embed=True), no
|
||||
|
||||
@router.websocket("/{node_id}/console/ws")
|
||||
async def console_ws(
|
||||
websocket: Union[None, WebSocket] = Depends(ws_compute_authentication),
|
||||
node: Router = Depends(dep_node)
|
||||
|
||||
websocket: Union[None, WebSocket] = Depends(ws_compute_authentication), node: Router = Depends(dep_node)
|
||||
) -> None:
|
||||
"""
|
||||
Console WebSocket.
|
||||
@@ -359,23 +316,16 @@ async def console_ws(
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/console/reset",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
"/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def reset_console(node: Router = Depends(dep_node)) -> None:
|
||||
|
||||
await node.reset_console()
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{node_id}/markers/{marker_name}",
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
@router.put("/{node_id}/markers/{marker_name}", dependencies=[Depends(compute_authentication)])
|
||||
async def toggle_dynamips_marker(
|
||||
marker_name: str,
|
||||
toggle_data: schemas.MarkerToggle,
|
||||
node: Router = Depends(dep_node)
|
||||
marker_name: str, toggle_data: schemas.MarkerToggle, node: Router = Depends(dep_node)
|
||||
) -> dict:
|
||||
"""
|
||||
Toggle a marker filter on/off without an NIO rebuild (ubridge contract §3.2).
|
||||
@@ -391,9 +341,7 @@ async def toggle_dynamips_marker(
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/markers/pause",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
"/{node_id}/markers/pause", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def pause_dynamips_markers(node: Router = Depends(dep_node)) -> None:
|
||||
|
||||
@@ -401,9 +349,7 @@ async def pause_dynamips_markers(node: Router = Depends(dep_node)) -> None:
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/markers/resume",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
"/{node_id}/markers/resume", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def resume_dynamips_markers(node: Router = Depends(dep_node)) -> None:
|
||||
|
||||
@@ -413,14 +359,10 @@ async def resume_dynamips_markers(node: Router = Depends(dep_node)) -> None:
|
||||
@router.delete(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/markers/{marker_name}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def delete_dynamips_marker_capture(
|
||||
marker_name: str,
|
||||
adapter_number: int,
|
||||
port_number: int,
|
||||
link_id: str = "",
|
||||
node: Router = Depends(dep_node)
|
||||
marker_name: str, adapter_number: int, port_number: int, link_id: str = "", node: Router = Depends(dep_node)
|
||||
) -> None:
|
||||
"""
|
||||
Delete a marker's capture pcap (called by the controller when the marker is
|
||||
@@ -433,14 +375,9 @@ async def delete_dynamips_marker_capture(
|
||||
await node.delete_marker_capture(marker_name, link_id, nio)
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{node_id}/markers/{marker_name}/rebuild",
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
@router.put("/{node_id}/markers/{marker_name}/rebuild", dependencies=[Depends(compute_authentication)])
|
||||
async def rebuild_dynamips_marker(
|
||||
marker_name: str,
|
||||
rebuild_data: schemas.MarkerRebuild,
|
||||
node: Router = Depends(dep_node)
|
||||
marker_name: str, rebuild_data: schemas.MarkerRebuild, node: Router = Depends(dep_node)
|
||||
) -> dict:
|
||||
"""
|
||||
Re-install a single marker filter with new BPF/tag/direction (delete + add,
|
||||
@@ -448,7 +385,11 @@ async def rebuild_dynamips_marker(
|
||||
"""
|
||||
|
||||
await node.rebuild_marker_filter(
|
||||
marker_name, rebuild_data.link_id, rebuild_data.bpf,
|
||||
rebuild_data.tag, rebuild_data.direction, rebuild_data.enabled,
|
||||
marker_name,
|
||||
rebuild_data.link_id,
|
||||
rebuild_data.bpf,
|
||||
rebuild_data.tag,
|
||||
rebuild_data.direction,
|
||||
rebuild_data.enabled,
|
||||
)
|
||||
return {"marker_name": marker_name}
|
||||
@@ -91,8 +91,7 @@ async def duplicate_ethernet_hub(
|
||||
|
||||
@router.put("/{node_id}", response_model=schemas.EthernetHub)
|
||||
async def update_ethernet_hub(
|
||||
node_data: schemas.EthernetHubUpdate,
|
||||
node: EthernetHub = Depends(dep_node)
|
||||
node_data: schemas.EthernetHubUpdate, node: EthernetHub = Depends(dep_node)
|
||||
) -> schemas.EthernetHub:
|
||||
"""
|
||||
Update an Ethernet hub.
|
||||
@@ -124,8 +123,7 @@ def start_ethernet_hub(node: EthernetHub = Depends(dep_node)) -> None:
|
||||
"""
|
||||
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_405_METHOD_NOT_ALLOWED,
|
||||
detail="Start is not supported for Ethernet hubs"
|
||||
status_code=status.HTTP_405_METHOD_NOT_ALLOWED, detail="Start is not supported for Ethernet hubs"
|
||||
)
|
||||
|
||||
|
||||
@@ -136,8 +134,7 @@ def stop_ethernet_hub(node: EthernetHub = Depends(dep_node)) -> None:
|
||||
"""
|
||||
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_405_METHOD_NOT_ALLOWED,
|
||||
detail="Stop is not supported for Ethernet hubs"
|
||||
status_code=status.HTTP_405_METHOD_NOT_ALLOWED, detail="Stop is not supported for Ethernet hubs"
|
||||
)
|
||||
|
||||
|
||||
@@ -148,8 +145,7 @@ def suspend_ethernet_hub(node: EthernetHub = Depends(dep_node)) -> None:
|
||||
"""
|
||||
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_405_METHOD_NOT_ALLOWED,
|
||||
detail="Suspend is not supported for Ethernet hubs"
|
||||
status_code=status.HTTP_405_METHOD_NOT_ALLOWED, detail="Suspend is not supported for Ethernet hubs"
|
||||
)
|
||||
|
||||
|
||||
@@ -159,11 +155,11 @@ def suspend_ethernet_hub(node: EthernetHub = Depends(dep_node)) -> None:
|
||||
response_model=schemas.UDPNIO,
|
||||
)
|
||||
async def create_nio(
|
||||
*,
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
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.
|
||||
@@ -177,10 +173,7 @@ async def create_nio(
|
||||
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_nio(
|
||||
*,
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
port_number: int,
|
||||
node: EthernetHub = Depends(dep_node)
|
||||
*, 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.
|
||||
@@ -193,11 +186,11 @@ async def delete_nio(
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
||||
async def start_capture(
|
||||
*,
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
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.
|
||||
@@ -213,10 +206,7 @@ async def start_capture(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT
|
||||
)
|
||||
async def stop_capture(
|
||||
*,
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
port_number: int,
|
||||
node: EthernetHub = Depends(dep_node)
|
||||
*, adapter_number: int = Path(..., ge=0, le=0), port_number: int, node: EthernetHub = Depends(dep_node)
|
||||
) -> None:
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
@@ -228,10 +218,7 @@ async def stop_capture(
|
||||
|
||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
|
||||
async def stream_pcap_file(
|
||||
*,
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
port_number: int,
|
||||
node: EthernetHub = Depends(dep_node)
|
||||
*, adapter_number: int = Path(..., ge=0, le=0), port_number: int, node: EthernetHub = Depends(dep_node)
|
||||
) -> StreamingResponse:
|
||||
"""
|
||||
Stream the pcap capture file.
|
||||
|
||||
@@ -82,8 +82,7 @@ def get_ethernet_switch(node: EthernetSwitch = Depends(dep_node)) -> schemas.Eth
|
||||
|
||||
@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.
|
||||
@@ -95,8 +94,7 @@ async def duplicate_ethernet_switch(
|
||||
|
||||
@router.put("/{node_id}", response_model=schemas.EthernetSwitch)
|
||||
async def update_ethernet_switch(
|
||||
node_data: schemas.EthernetSwitchUpdate,
|
||||
node: EthernetSwitch = Depends(dep_node)
|
||||
node_data: schemas.EthernetSwitchUpdate, node: EthernetSwitch = Depends(dep_node)
|
||||
) -> schemas.EthernetSwitch:
|
||||
"""
|
||||
Update an Ethernet switch.
|
||||
@@ -132,8 +130,7 @@ def start_ethernet_switch(node: EthernetSwitch = Depends(dep_node)) -> None:
|
||||
"""
|
||||
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_405_METHOD_NOT_ALLOWED,
|
||||
detail="Start is not supported for Ethernet switches"
|
||||
status_code=status.HTTP_405_METHOD_NOT_ALLOWED, detail="Start is not supported for Ethernet switches"
|
||||
)
|
||||
|
||||
|
||||
@@ -144,8 +141,7 @@ def stop_ethernet_switch(node: EthernetSwitch = Depends(dep_node)) -> None:
|
||||
"""
|
||||
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_405_METHOD_NOT_ALLOWED,
|
||||
detail="Stop is not supported for Ethernet switches"
|
||||
status_code=status.HTTP_405_METHOD_NOT_ALLOWED, detail="Stop is not supported for Ethernet switches"
|
||||
)
|
||||
|
||||
|
||||
@@ -156,8 +152,7 @@ def suspend_ethernet_switch(node: EthernetSwitch = Depends(dep_node)) -> None:
|
||||
"""
|
||||
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_405_METHOD_NOT_ALLOWED,
|
||||
detail="Suspend is not supported for Ethernet switches"
|
||||
status_code=status.HTTP_405_METHOD_NOT_ALLOWED, detail="Suspend is not supported for Ethernet switches"
|
||||
)
|
||||
|
||||
|
||||
@@ -169,8 +164,7 @@ def reload_ethernet_switch(node: EthernetSwitch = Depends(dep_node)) -> None:
|
||||
"""
|
||||
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_405_METHOD_NOT_ALLOWED,
|
||||
detail="Reload is not supported for Ethernet switches"
|
||||
status_code=status.HTTP_405_METHOD_NOT_ALLOWED, detail="Reload is not supported for Ethernet switches"
|
||||
)
|
||||
|
||||
|
||||
@@ -180,11 +174,11 @@ def reload_ethernet_switch(node: EthernetSwitch = Depends(dep_node)) -> None:
|
||||
response_model=schemas.UDPNIO,
|
||||
)
|
||||
async def create_ethernet_switch_nio(
|
||||
*,
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
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 = Builtin.instance().create_nio(jsonable_encoder(nio_data, exclude_unset=True))
|
||||
@@ -198,11 +192,11 @@ async def create_ethernet_switch_nio(
|
||||
response_model=schemas.UDPNIO,
|
||||
)
|
||||
async def update_ethernet_switch_nio(
|
||||
*,
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
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:
|
||||
"""
|
||||
Update a NIO (Network Input/Output) on the node: re-apply the packet
|
||||
@@ -221,10 +215,7 @@ async def update_ethernet_switch_nio(
|
||||
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_ethernet_switch_nio(
|
||||
*,
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
port_number: int,
|
||||
node: EthernetSwitch = Depends(dep_node)
|
||||
*, 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.
|
||||
@@ -236,11 +227,11 @@ async def delete_ethernet_switch_nio(
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
||||
async def start_ethernet_switch_capture(
|
||||
*,
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
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.
|
||||
@@ -256,10 +247,7 @@ async def start_ethernet_switch_capture(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT
|
||||
)
|
||||
async def stop_ethernet_switch_capture(
|
||||
*,
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
port_number: int,
|
||||
node: EthernetSwitch = Depends(dep_node)
|
||||
*, adapter_number: int = Path(..., ge=0, le=0), port_number: int, node: EthernetSwitch = Depends(dep_node)
|
||||
) -> None:
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
@@ -271,10 +259,7 @@ async def stop_ethernet_switch_capture(
|
||||
|
||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
|
||||
async def stream_pcap_file(
|
||||
*,
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
port_number: int,
|
||||
node: EthernetSwitch = Depends(dep_node)
|
||||
*, adapter_number: int = Path(..., ge=0, le=0), port_number: int, node: EthernetSwitch = Depends(dep_node)
|
||||
) -> StreamingResponse:
|
||||
"""
|
||||
Stream the pcap capture file.
|
||||
@@ -288,9 +273,7 @@ async def stream_pcap_file(
|
||||
|
||||
@router.put("/{node_id}/markers/{marker_name}")
|
||||
async def toggle_ethernet_switch_marker(
|
||||
marker_name: str,
|
||||
toggle_data: schemas.MarkerToggle,
|
||||
node: EthernetSwitch = Depends(dep_node)
|
||||
marker_name: str, toggle_data: schemas.MarkerToggle, node: EthernetSwitch = Depends(dep_node)
|
||||
) -> dict:
|
||||
"""
|
||||
Toggle a marker filter on/off without an NIO rebuild (ubridge contract §3.2).
|
||||
@@ -322,12 +305,12 @@ async def resume_ethernet_switch_markers(node: EthernetSwitch = Depends(dep_node
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
)
|
||||
async def delete_ethernet_switch_marker_capture(
|
||||
*,
|
||||
marker_name: str,
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
port_number: int,
|
||||
link_id: str = "",
|
||||
node: EthernetSwitch = Depends(dep_node)
|
||||
*,
|
||||
marker_name: str,
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
port_number: int,
|
||||
link_id: str = "",
|
||||
node: EthernetSwitch = Depends(dep_node),
|
||||
) -> None:
|
||||
"""
|
||||
Delete a marker's capture pcap (called by the controller when the marker is
|
||||
@@ -342,9 +325,7 @@ async def delete_ethernet_switch_marker_capture(
|
||||
|
||||
@router.put("/{node_id}/markers/{marker_name}/rebuild")
|
||||
async def rebuild_ethernet_switch_marker(
|
||||
marker_name: str,
|
||||
rebuild_data: schemas.MarkerRebuild,
|
||||
node: EthernetSwitch = Depends(dep_node)
|
||||
marker_name: str, rebuild_data: schemas.MarkerRebuild, node: EthernetSwitch = Depends(dep_node)
|
||||
) -> dict:
|
||||
"""
|
||||
Re-install a single marker filter with new BPF/tag/direction (delete + add,
|
||||
@@ -352,7 +333,11 @@ async def rebuild_ethernet_switch_marker(
|
||||
"""
|
||||
|
||||
await node.rebuild_marker_filter(
|
||||
marker_name, rebuild_data.link_id, rebuild_data.bpf,
|
||||
rebuild_data.tag, rebuild_data.direction, rebuild_data.enabled,
|
||||
marker_name,
|
||||
rebuild_data.link_id,
|
||||
rebuild_data.bpf,
|
||||
rebuild_data.tag,
|
||||
rebuild_data.direction,
|
||||
rebuild_data.enabled,
|
||||
)
|
||||
return {"marker_name": marker_name}
|
||||
@@ -51,8 +51,7 @@ def dep_node(project_id: UUID, node_id: UUID) -> FrameRelaySwitch:
|
||||
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
|
||||
project_id: UUID, node_data: schemas.FrameRelaySwitchCreate
|
||||
) -> schemas.FrameRelaySwitch:
|
||||
"""
|
||||
Create a new Frame Relay switch node.
|
||||
@@ -82,8 +81,7 @@ def get_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)) -> schema
|
||||
|
||||
@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.
|
||||
@@ -95,8 +93,7 @@ async def duplicate_frame_relay_switch(
|
||||
|
||||
@router.put("/{node_id}", response_model=schemas.FrameRelaySwitch)
|
||||
async def update_frame_relay_switch(
|
||||
node_data: schemas.FrameRelaySwitchUpdate,
|
||||
node: FrameRelaySwitch = Depends(dep_node)
|
||||
node_data: schemas.FrameRelaySwitchUpdate, node: FrameRelaySwitch = Depends(dep_node)
|
||||
) -> schemas.FrameRelaySwitch:
|
||||
"""
|
||||
Update an Frame Relay switch node.
|
||||
@@ -127,8 +124,7 @@ def start_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)) -> None
|
||||
"""
|
||||
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_405_METHOD_NOT_ALLOWED,
|
||||
detail="Start is not supported for Frame Relay switches"
|
||||
status_code=status.HTTP_405_METHOD_NOT_ALLOWED, detail="Start is not supported for Frame Relay switches"
|
||||
)
|
||||
|
||||
|
||||
@@ -139,8 +135,7 @@ def stop_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)) -> None:
|
||||
"""
|
||||
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_405_METHOD_NOT_ALLOWED,
|
||||
detail="Stop is not supported for Frame Relay switches"
|
||||
status_code=status.HTTP_405_METHOD_NOT_ALLOWED, detail="Stop is not supported for Frame Relay switches"
|
||||
)
|
||||
|
||||
|
||||
@@ -151,8 +146,7 @@ def suspend_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)) -> No
|
||||
"""
|
||||
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_405_METHOD_NOT_ALLOWED,
|
||||
detail="Suspend is not supported for Frame Relay switches"
|
||||
status_code=status.HTTP_405_METHOD_NOT_ALLOWED, detail="Suspend is not supported for Frame Relay switches"
|
||||
)
|
||||
|
||||
|
||||
@@ -162,11 +156,11 @@ def suspend_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)) -> No
|
||||
response_model=schemas.UDPNIO,
|
||||
)
|
||||
async def create_nio(
|
||||
*,
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
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.
|
||||
@@ -180,10 +174,7 @@ async def create_nio(
|
||||
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_nio(
|
||||
*,
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
port_number: int,
|
||||
node: FrameRelaySwitch = Depends(dep_node)
|
||||
*, 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.
|
||||
@@ -196,11 +187,11 @@ async def delete_nio(
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
||||
async def start_capture(
|
||||
*,
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
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.
|
||||
@@ -216,10 +207,7 @@ async def start_capture(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT
|
||||
)
|
||||
async def stop_capture(
|
||||
*,
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
port_number: int,
|
||||
node: FrameRelaySwitch = Depends(dep_node)
|
||||
*, adapter_number: int = Path(..., ge=0, le=0), port_number: int, node: FrameRelaySwitch = Depends(dep_node)
|
||||
) -> None:
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
@@ -231,10 +219,7 @@ async def stop_capture(
|
||||
|
||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
|
||||
async def stream_pcap_file(
|
||||
*,
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
port_number: int,
|
||||
node: FrameRelaySwitch = Depends(dep_node)
|
||||
*, adapter_number: int = Path(..., ge=0, le=0), port_number: int, node: FrameRelaySwitch = Depends(dep_node)
|
||||
) -> StreamingResponse:
|
||||
"""
|
||||
Stream the pcap capture file.
|
||||
|
||||
@@ -52,7 +52,7 @@ def dep_node(project_id: UUID, node_id: UUID) -> IOUVM:
|
||||
response_model=schemas.IOU,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create IOU node"}},
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def create_iou_node(project_id: UUID, node_data: schemas.IOUCreate) -> schemas.IOU:
|
||||
"""
|
||||
@@ -85,11 +85,7 @@ async def create_iou_node(project_id: UUID, node_data: schemas.IOUCreate) -> sch
|
||||
return vm.asdict()
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{node_id}",
|
||||
response_model=schemas.IOU,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
@router.get("/{node_id}", response_model=schemas.IOU, dependencies=[Depends(compute_authentication)])
|
||||
def get_iou_node(node: IOUVM = Depends(dep_node)) -> schemas.IOU:
|
||||
"""
|
||||
Return an IOU node.
|
||||
@@ -98,11 +94,7 @@ def get_iou_node(node: IOUVM = Depends(dep_node)) -> schemas.IOU:
|
||||
return node.asdict()
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{node_id}",
|
||||
response_model=schemas.IOU,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
@router.put("/{node_id}", response_model=schemas.IOU, dependencies=[Depends(compute_authentication)])
|
||||
async def update_iou_node(node_data: schemas.IOUUpdate, node: IOUVM = Depends(dep_node)) -> schemas.IOU:
|
||||
"""
|
||||
Update an IOU node.
|
||||
@@ -123,11 +115,7 @@ async def update_iou_node(node_data: schemas.IOUUpdate, node: IOUVM = Depends(de
|
||||
return node.asdict()
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{node_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)])
|
||||
async def delete_iou_node(node: IOUVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Delete an IOU node.
|
||||
@@ -140,11 +128,10 @@ async def delete_iou_node(node: IOUVM = Depends(dep_node)) -> None:
|
||||
"/{node_id}/duplicate",
|
||||
response_model=schemas.IOU,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def duplicate_iou_node(
|
||||
destination_node_id: UUID = Body(..., embed=True),
|
||||
node: IOUVM = Depends(dep_node)
|
||||
destination_node_id: UUID = Body(..., embed=True), node: IOUVM = Depends(dep_node)
|
||||
) -> schemas.IOU:
|
||||
"""
|
||||
Duplicate an IOU node.
|
||||
@@ -154,11 +141,7 @@ async def duplicate_iou_node(
|
||||
return new_node.asdict()
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/start",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)])
|
||||
async def start_iou_node(start_data: schemas.IOUStart, node: IOUVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Start an IOU node.
|
||||
@@ -172,11 +155,7 @@ async def start_iou_node(start_data: schemas.IOUStart, node: IOUVM = Depends(dep
|
||||
await node.start()
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)])
|
||||
async def stop_iou_node(node: IOUVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Stop an IOU node.
|
||||
@@ -185,25 +164,17 @@ async def stop_iou_node(node: IOUVM = Depends(dep_node)) -> None:
|
||||
await node.stop()
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/suspend",
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
@router.post("/{node_id}/suspend", dependencies=[Depends(compute_authentication)])
|
||||
def suspend_iou_node(node: IOUVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Suspend an IOU node.
|
||||
"""
|
||||
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_405_METHOD_NOT_ALLOWED,
|
||||
detail="Suspend is not supported for IOU nodes"
|
||||
)
|
||||
raise HTTPException(status_code=status.HTTP_405_METHOD_NOT_ALLOWED, detail="Suspend is not supported for IOU nodes")
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/reload",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
"/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def reload_iou_node(node: IOUVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
@@ -217,7 +188,7 @@ async def reload_iou_node(node: IOUVM = Depends(dep_node)) -> None:
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def create_iou_node_nio(
|
||||
adapter_number: int,
|
||||
@@ -238,7 +209,7 @@ async def create_iou_node_nio(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def update_iou_node_nio(
|
||||
adapter_number: int,
|
||||
@@ -263,7 +234,7 @@ async def update_iou_node_nio(
|
||||
@router.delete(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def delete_iou_node_nio(adapter_number: int, port_number: int, node: IOUVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
@@ -275,13 +246,10 @@ 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",
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
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.
|
||||
@@ -295,7 +263,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,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def stop_iou_node_capture(adapter_number: int, port_number: int, node: IOUVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
@@ -307,13 +275,9 @@ 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",
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def stream_pcap_file(
|
||||
adapter_number: int,
|
||||
port_number: int,
|
||||
node: IOUVM = Depends(dep_node)
|
||||
) -> StreamingResponse:
|
||||
async def stream_pcap_file(adapter_number: int, port_number: int, node: IOUVM = Depends(dep_node)) -> StreamingResponse:
|
||||
"""
|
||||
Stream the pcap capture file.
|
||||
"""
|
||||
@@ -327,8 +291,7 @@ async def stream_pcap_file(
|
||||
"/{node_id}/console/ws",
|
||||
)
|
||||
async def console_ws(
|
||||
websocket: Union[None, WebSocket] = Depends(ws_compute_authentication),
|
||||
node: IOUVM = Depends(dep_node)
|
||||
websocket: Union[None, WebSocket] = Depends(ws_compute_authentication), node: IOUVM = Depends(dep_node)
|
||||
) -> None:
|
||||
"""
|
||||
Console WebSocket.
|
||||
@@ -339,23 +302,16 @@ async def console_ws(
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/console/reset",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
"/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def reset_console(node: IOUVM = Depends(dep_node)) -> None:
|
||||
|
||||
await node.reset_console()
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{node_id}/markers/{marker_name}",
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
@router.put("/{node_id}/markers/{marker_name}", dependencies=[Depends(compute_authentication)])
|
||||
async def toggle_iou_marker(
|
||||
marker_name: str,
|
||||
toggle_data: schemas.MarkerToggle,
|
||||
node: IOUVM = Depends(dep_node)
|
||||
marker_name: str, toggle_data: schemas.MarkerToggle, node: IOUVM = Depends(dep_node)
|
||||
) -> dict:
|
||||
"""
|
||||
Toggle a marker filter on/off without an NIO rebuild (ubridge contract §3.2).
|
||||
@@ -371,9 +327,7 @@ async def toggle_iou_marker(
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/markers/pause",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
"/{node_id}/markers/pause", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def pause_iou_markers(node: IOUVM = Depends(dep_node)) -> None:
|
||||
|
||||
@@ -381,9 +335,7 @@ async def pause_iou_markers(node: IOUVM = Depends(dep_node)) -> None:
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/markers/resume",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
"/{node_id}/markers/resume", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def resume_iou_markers(node: IOUVM = Depends(dep_node)) -> None:
|
||||
|
||||
@@ -393,14 +345,10 @@ async def resume_iou_markers(node: IOUVM = Depends(dep_node)) -> None:
|
||||
@router.delete(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/markers/{marker_name}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def delete_iou_marker_capture(
|
||||
marker_name: str,
|
||||
adapter_number: int,
|
||||
port_number: int,
|
||||
link_id: str = "",
|
||||
node: IOUVM = Depends(dep_node)
|
||||
marker_name: str, adapter_number: int, port_number: int, link_id: str = "", node: IOUVM = Depends(dep_node)
|
||||
) -> None:
|
||||
"""
|
||||
Delete a marker's capture pcap (called by the controller when the marker is
|
||||
@@ -413,14 +361,9 @@ async def delete_iou_marker_capture(
|
||||
await node.delete_marker_capture(marker_name, link_id, nio)
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{node_id}/markers/{marker_name}/rebuild",
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
@router.put("/{node_id}/markers/{marker_name}/rebuild", dependencies=[Depends(compute_authentication)])
|
||||
async def rebuild_iou_marker(
|
||||
marker_name: str,
|
||||
rebuild_data: schemas.MarkerRebuild,
|
||||
node: IOUVM = Depends(dep_node)
|
||||
marker_name: str, rebuild_data: schemas.MarkerRebuild, node: IOUVM = Depends(dep_node)
|
||||
) -> dict:
|
||||
"""
|
||||
Re-install a single marker filter with new BPF/tag/direction (delete + add,
|
||||
@@ -428,7 +371,11 @@ async def rebuild_iou_marker(
|
||||
"""
|
||||
|
||||
await node.rebuild_marker_filter(
|
||||
marker_name, rebuild_data.link_id, rebuild_data.bpf,
|
||||
rebuild_data.tag, rebuild_data.direction, rebuild_data.enabled,
|
||||
marker_name,
|
||||
rebuild_data.link_id,
|
||||
rebuild_data.bpf,
|
||||
rebuild_data.tag,
|
||||
rebuild_data.direction,
|
||||
rebuild_data.enabled,
|
||||
)
|
||||
return {"marker_name": marker_name}
|
||||
@@ -117,10 +117,7 @@ async def stop_nat_node(node: Nat = Depends(dep_node)) -> None:
|
||||
Stop a NAT node.
|
||||
"""
|
||||
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_405_METHOD_NOT_ALLOWED,
|
||||
detail="Stop is not supported for NAT nodes"
|
||||
)
|
||||
raise HTTPException(status_code=status.HTTP_405_METHOD_NOT_ALLOWED, detail="Stop is not supported for NAT nodes")
|
||||
|
||||
|
||||
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
||||
@@ -129,10 +126,7 @@ async def suspend_nat_node(node: Nat = Depends(dep_node)) -> None:
|
||||
Suspend a NAT node.
|
||||
"""
|
||||
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_405_METHOD_NOT_ALLOWED,
|
||||
detail="Suspend is not supported for NAT nodes"
|
||||
)
|
||||
raise HTTPException(status_code=status.HTTP_405_METHOD_NOT_ALLOWED, detail="Suspend is not supported for NAT nodes")
|
||||
|
||||
|
||||
@router.post(
|
||||
@@ -141,11 +135,11 @@ async def suspend_nat_node(node: Nat = Depends(dep_node)) -> None:
|
||||
response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
|
||||
)
|
||||
async def create_nat_node_nio(
|
||||
*,
|
||||
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),
|
||||
*,
|
||||
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.
|
||||
@@ -163,11 +157,11 @@ async def create_nat_node_nio(
|
||||
response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
|
||||
)
|
||||
async def update_nat_node_nio(
|
||||
*,
|
||||
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),
|
||||
*,
|
||||
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.
|
||||
@@ -184,10 +178,7 @@ async def update_nat_node_nio(
|
||||
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_nat_node_nio(
|
||||
*,
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
port_number: int,
|
||||
node: Nat = Depends(dep_node)
|
||||
*, 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.
|
||||
@@ -199,11 +190,11 @@ async def delete_nat_node_nio(
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
||||
async def start_nat_node_capture(
|
||||
*,
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
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.
|
||||
@@ -219,10 +210,7 @@ async def start_nat_node_capture(
|
||||
"/{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 = Path(..., ge=0, le=0),
|
||||
port_number: int,
|
||||
node: Nat = Depends(dep_node)
|
||||
*, adapter_number: int = Path(..., ge=0, le=0), port_number: int, node: Nat = Depends(dep_node)
|
||||
) -> None:
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
@@ -234,10 +222,7 @@ async def stop_nat_node_capture(
|
||||
|
||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
|
||||
async def stream_pcap_file(
|
||||
*,
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
port_number: int,
|
||||
node: Nat = Depends(dep_node)
|
||||
*, adapter_number: int = Path(..., ge=0, le=0), port_number: int, node: Nat = Depends(dep_node)
|
||||
) -> StreamingResponse:
|
||||
"""
|
||||
Stream the pcap capture file.
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
API routes for compute notifications.
|
||||
"""
|
||||
|
||||
|
||||
from fastapi import APIRouter, Depends, WebSocket, WebSocketDisconnect
|
||||
from typing import Union
|
||||
from websockets.exceptions import ConnectionClosed, WebSocketException
|
||||
@@ -53,7 +52,6 @@ async def project_ws_notifications(websocket: Union[None, WebSocket] = Depends(w
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
import uvicorn
|
||||
from fastapi import FastAPI
|
||||
from starlette.responses import HTMLResponse
|
||||
|
||||
@@ -86,8 +86,7 @@ def create_compute_project(project_data: schemas.ProjectCreate) -> schemas.Proje
|
||||
|
||||
@router.put("/projects/{project_id}", response_model=schemas.Project)
|
||||
async def update_compute_project(
|
||||
project_data: schemas.ProjectUpdate,
|
||||
project: Project = Depends(dep_project)
|
||||
project_data: schemas.ProjectUpdate, project: Project = Depends(dep_project)
|
||||
) -> schemas.Project:
|
||||
"""
|
||||
Update project on the compute.
|
||||
@@ -195,6 +194,7 @@ def _get_existing_nio(node, adapter_number, port_number):
|
||||
# via get_nio(port).
|
||||
if hasattr(node, "get_nio"):
|
||||
import inspect as _inspect
|
||||
|
||||
if len(_inspect.signature(node.get_nio).parameters) >= 2:
|
||||
return node.get_nio(adapter_number, port_number)
|
||||
return node.get_nio(port_number)
|
||||
@@ -242,9 +242,9 @@ async def _update_nio_binding(node, adapter_number, port_number, nio):
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
)
|
||||
async def create_batch_nios(
|
||||
project_id: UUID,
|
||||
batch: schemas.BatchNIOCreate,
|
||||
project: Project = Depends(dep_project),
|
||||
project_id: UUID,
|
||||
batch: schemas.BatchNIOCreate,
|
||||
project: Project = Depends(dep_project),
|
||||
) -> dict:
|
||||
"""
|
||||
Create many NIO bindings across nodes in a single request.
|
||||
@@ -281,9 +281,7 @@ async def create_batch_nios(
|
||||
nio = node.manager.create_nio(nio_settings)
|
||||
await _add_nio_binding(node, entry.adapter_number, entry.port_number, nio)
|
||||
|
||||
await asyncio.gather(
|
||||
*[_create_one_node(nid, ents) for nid, ents in per_node.items()]
|
||||
)
|
||||
await asyncio.gather(*[_create_one_node(nid, ents) for nid, ents in per_node.items()])
|
||||
return {"added": len(batch.nios)}
|
||||
|
||||
|
||||
@@ -292,9 +290,9 @@ async def create_batch_nios(
|
||||
status_code=status.HTTP_200_OK,
|
||||
)
|
||||
async def update_batch_nios(
|
||||
project_id: UUID,
|
||||
batch: schemas.BatchNIOCreate,
|
||||
project: Project = Depends(dep_project),
|
||||
project_id: UUID,
|
||||
batch: schemas.BatchNIOCreate,
|
||||
project: Project = Depends(dep_project),
|
||||
) -> dict:
|
||||
"""
|
||||
Update many NIO bindings (filters + markers) across nodes in a single
|
||||
@@ -322,9 +320,7 @@ async def update_batch_nios(
|
||||
nio.markers = e.nio.markers or {}
|
||||
await _update_nio_binding(node, e.adapter_number, e.port_number, nio)
|
||||
|
||||
await asyncio.gather(
|
||||
*[_update_one_node(nid, ents) for nid, ents in per_node.items()]
|
||||
)
|
||||
await asyncio.gather(*[_update_one_node(nid, ents) for nid, ents in per_node.items()])
|
||||
return {"updated": len(batch.nios)}
|
||||
|
||||
|
||||
@@ -343,7 +339,7 @@ async def get_compute_node_files(
|
||||
node_id: str,
|
||||
project: Project = Depends(dep_project),
|
||||
path: str = Query("", description="Subdirectory path within node directory"),
|
||||
recursive: bool = Query(False, description="Recursively list all files")
|
||||
recursive: bool = Query(False, description="Recursively list all files"),
|
||||
) -> List[schemas.NodeFile]:
|
||||
"""
|
||||
Return files belonging to a specific node with detailed metadata.
|
||||
@@ -374,11 +370,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)
|
||||
) -> None:
|
||||
async def write_compute_project_file(file_path: str, request: Request, project: Project = Depends(dep_project)) -> None:
|
||||
|
||||
file_path = urllib.parse.unquote(file_path)
|
||||
path = os.path.normpath(file_path)
|
||||
@@ -405,10 +397,7 @@ async def write_compute_project_file(
|
||||
|
||||
|
||||
@router.delete("/projects/{project_id}/files/{file_path:path}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_compute_project_file(
|
||||
file_path: str,
|
||||
project: Project = Depends(dep_project)
|
||||
) -> None:
|
||||
async def delete_compute_project_file(file_path: str, project: Project = Depends(dep_project)) -> None:
|
||||
|
||||
file_path = urllib.parse.unquote(file_path)
|
||||
path = os.path.normpath(file_path)
|
||||
|
||||
@@ -33,6 +33,7 @@ from gns3server.compute.qemu.qemu_vm import QemuVM
|
||||
from .dependencies.authentication import compute_authentication, ws_compute_authentication
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or Qemu node"}}
|
||||
@@ -55,7 +56,7 @@ def dep_node(project_id: UUID, node_id: UUID) -> QemuVM:
|
||||
response_model=schemas.Qemu,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Qemu node"}},
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def create_qemu_node(project_id: UUID, node_data: schemas.QemuCreate) -> schemas.Qemu:
|
||||
"""
|
||||
@@ -117,11 +118,7 @@ async def create_qemu_node(project_id: UUID, node_data: schemas.QemuCreate) -> s
|
||||
return vm.asdict()
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{node_id}",
|
||||
response_model=schemas.Qemu,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
@router.get("/{node_id}", response_model=schemas.Qemu, dependencies=[Depends(compute_authentication)])
|
||||
def get_qemu_node(node: QemuVM = Depends(dep_node)) -> schemas.Qemu:
|
||||
"""
|
||||
Return a Qemu node.
|
||||
@@ -130,11 +127,7 @@ def get_qemu_node(node: QemuVM = Depends(dep_node)) -> schemas.Qemu:
|
||||
return node.asdict()
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{node_id}",
|
||||
response_model=schemas.Qemu,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
@router.put("/{node_id}", response_model=schemas.Qemu, dependencies=[Depends(compute_authentication)])
|
||||
async def update_qemu_node(node_data: schemas.QemuUpdate, node: QemuVM = Depends(dep_node)) -> schemas.Qemu:
|
||||
"""
|
||||
Update a Qemu node.
|
||||
@@ -150,11 +143,7 @@ async def update_qemu_node(node_data: schemas.QemuUpdate, node: QemuVM = Depends
|
||||
return node.asdict()
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{node_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)])
|
||||
async def delete_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Delete a Qemu node.
|
||||
@@ -167,11 +156,10 @@ async def delete_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
|
||||
"/{node_id}/duplicate",
|
||||
response_model=schemas.Qemu,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def duplicate_qemu_node(
|
||||
destination_node_id: UUID = Body(..., embed=True),
|
||||
node: QemuVM = Depends(dep_node)
|
||||
destination_node_id: UUID = Body(..., embed=True), node: QemuVM = Depends(dep_node)
|
||||
) -> schemas.Qemu:
|
||||
"""
|
||||
Duplicate a Qemu node.
|
||||
@@ -184,12 +172,10 @@ async def duplicate_qemu_node(
|
||||
@router.post(
|
||||
"/{node_id}/disk_image/{disk_name}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def create_qemu_disk_image(
|
||||
disk_name: str,
|
||||
disk_data: schemas.QemuDiskImageCreate,
|
||||
node: QemuVM = Depends(dep_node)
|
||||
disk_name: str, disk_data: schemas.QemuDiskImageCreate, node: QemuVM = Depends(dep_node)
|
||||
) -> None:
|
||||
"""
|
||||
Create a Qemu disk image.
|
||||
@@ -202,12 +188,10 @@ async def create_qemu_disk_image(
|
||||
@router.put(
|
||||
"/{node_id}/disk_image/{disk_name}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def update_qemu_disk_image(
|
||||
disk_name: str,
|
||||
disk_data: schemas.QemuDiskImageUpdate,
|
||||
node: QemuVM = Depends(dep_node)
|
||||
disk_name: str, disk_data: schemas.QemuDiskImageUpdate, node: QemuVM = Depends(dep_node)
|
||||
) -> None:
|
||||
"""
|
||||
Update a Qemu disk image.
|
||||
@@ -220,12 +204,9 @@ async def update_qemu_disk_image(
|
||||
@router.delete(
|
||||
"/{node_id}/disk_image/{disk_name}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def delete_qemu_disk_image(
|
||||
disk_name: str,
|
||||
node: QemuVM = Depends(dep_node)
|
||||
) -> None:
|
||||
async def delete_qemu_disk_image(disk_name: str, node: QemuVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Delete a Qemu disk image.
|
||||
"""
|
||||
@@ -233,11 +214,7 @@ async def delete_qemu_disk_image(
|
||||
node.delete_disk_image(disk_name)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/start",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)])
|
||||
async def start_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Start a Qemu node.
|
||||
@@ -246,11 +223,7 @@ async def start_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
|
||||
await node.start()
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)])
|
||||
async def stop_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Stop a Qemu node.
|
||||
@@ -260,9 +233,7 @@ async def stop_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/reload",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
"/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def reload_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
@@ -273,9 +244,7 @@ async def reload_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/suspend",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
"/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def suspend_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
@@ -286,9 +255,7 @@ async def suspend_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/resume",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
"/{node_id}/resume", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def resume_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
@@ -302,14 +269,14 @@ async def resume_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def create_qemu_node_nio(
|
||||
*,
|
||||
adapter_number: int,
|
||||
port_number: int = Path(..., ge=0, le=0),
|
||||
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.
|
||||
@@ -325,14 +292,14 @@ async def create_qemu_node_nio(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def update_qemu_node_nio(
|
||||
*,
|
||||
adapter_number: int,
|
||||
port_number: int = Path(..., ge=0, le=0),
|
||||
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.
|
||||
@@ -352,12 +319,10 @@ async def update_qemu_node_nio(
|
||||
@router.delete(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def delete_qemu_node_nio(
|
||||
adapter_number: int,
|
||||
port_number: int = Path(..., ge=0, le=0),
|
||||
node: QemuVM = Depends(dep_node)
|
||||
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.
|
||||
@@ -369,14 +334,14 @@ async def delete_qemu_node_nio(
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start",
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def start_qemu_node_capture(
|
||||
*,
|
||||
adapter_number: int,
|
||||
port_number: int = Path(..., ge=0, le=0),
|
||||
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.
|
||||
@@ -391,12 +356,10 @@ 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,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def stop_qemu_node_capture(
|
||||
adapter_number: int,
|
||||
port_number: int = Path(..., ge=0, le=0),
|
||||
node: QemuVM = Depends(dep_node)
|
||||
adapter_number: int, port_number: int = Path(..., ge=0, le=0), node: QemuVM = Depends(dep_node)
|
||||
) -> None:
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
@@ -408,12 +371,11 @@ async def stop_qemu_node_capture(
|
||||
|
||||
@router.get(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream",
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def stream_pcap_file(
|
||||
adapter_number: int,
|
||||
port_number: int = Path(..., ge=0, le=0),
|
||||
node: QemuVM = Depends(dep_node)) -> StreamingResponse:
|
||||
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.
|
||||
@@ -424,12 +386,9 @@ async def stream_pcap_file(
|
||||
return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap")
|
||||
|
||||
|
||||
@router.websocket(
|
||||
"/{node_id}/console/ws"
|
||||
)
|
||||
@router.websocket("/{node_id}/console/ws")
|
||||
async def console_ws(
|
||||
websocket: Union[None, WebSocket] = Depends(ws_compute_authentication),
|
||||
node: QemuVM = Depends(dep_node)
|
||||
websocket: Union[None, WebSocket] = Depends(ws_compute_authentication), node: QemuVM = Depends(dep_node)
|
||||
) -> None:
|
||||
"""
|
||||
Console WebSocket.
|
||||
@@ -439,12 +398,9 @@ async def console_ws(
|
||||
await node.start_websocket_console(websocket)
|
||||
|
||||
|
||||
@router.websocket(
|
||||
"/{node_id}/console/vnc"
|
||||
)
|
||||
@router.websocket("/{node_id}/console/vnc")
|
||||
async def vnc_console_ws(
|
||||
websocket: Union[None, WebSocket] = Depends(ws_compute_authentication),
|
||||
node: QemuVM = Depends(dep_node)
|
||||
websocket: Union[None, WebSocket] = Depends(ws_compute_authentication), node: QemuVM = Depends(dep_node)
|
||||
) -> None:
|
||||
"""
|
||||
VNC Console WebSocket.
|
||||
@@ -455,23 +411,16 @@ async def vnc_console_ws(
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/console/reset",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
"/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def reset_console(node: QemuVM = Depends(dep_node)) -> None:
|
||||
|
||||
await node.reset_console()
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{node_id}/markers/{marker_name}",
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
@router.put("/{node_id}/markers/{marker_name}", dependencies=[Depends(compute_authentication)])
|
||||
async def toggle_qemu_marker(
|
||||
marker_name: str,
|
||||
toggle_data: schemas.MarkerToggle,
|
||||
node: QemuVM = Depends(dep_node)
|
||||
marker_name: str, toggle_data: schemas.MarkerToggle, node: QemuVM = Depends(dep_node)
|
||||
) -> dict:
|
||||
"""
|
||||
Toggle a marker filter on/off without an NIO rebuild (ubridge contract §3.2).
|
||||
@@ -487,9 +436,7 @@ async def toggle_qemu_marker(
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/markers/pause",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
"/{node_id}/markers/pause", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def pause_qemu_markers(node: QemuVM = Depends(dep_node)) -> None:
|
||||
|
||||
@@ -497,9 +444,7 @@ async def pause_qemu_markers(node: QemuVM = Depends(dep_node)) -> None:
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/markers/resume",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
"/{node_id}/markers/resume", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def resume_qemu_markers(node: QemuVM = Depends(dep_node)) -> None:
|
||||
|
||||
@@ -509,14 +454,14 @@ async def resume_qemu_markers(node: QemuVM = Depends(dep_node)) -> None:
|
||||
@router.delete(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/markers/{marker_name}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def delete_qemu_marker_capture(
|
||||
marker_name: str,
|
||||
adapter_number: int,
|
||||
port_number: int = Path(..., ge=0, le=0),
|
||||
link_id: str = "",
|
||||
node: QemuVM = Depends(dep_node)
|
||||
node: QemuVM = Depends(dep_node),
|
||||
) -> None:
|
||||
"""
|
||||
Delete a marker's capture pcap (called by the controller when the marker is
|
||||
@@ -529,14 +474,9 @@ async def delete_qemu_marker_capture(
|
||||
await node.delete_marker_capture(marker_name, link_id, nio)
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{node_id}/markers/{marker_name}/rebuild",
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
@router.put("/{node_id}/markers/{marker_name}/rebuild", dependencies=[Depends(compute_authentication)])
|
||||
async def rebuild_qemu_marker(
|
||||
marker_name: str,
|
||||
rebuild_data: schemas.MarkerRebuild,
|
||||
node: QemuVM = Depends(dep_node)
|
||||
marker_name: str, rebuild_data: schemas.MarkerRebuild, node: QemuVM = Depends(dep_node)
|
||||
) -> dict:
|
||||
"""
|
||||
Re-install a single marker filter with new BPF/tag/direction (delete + add,
|
||||
@@ -544,7 +484,11 @@ async def rebuild_qemu_marker(
|
||||
"""
|
||||
|
||||
await node.rebuild_marker_filter(
|
||||
marker_name, rebuild_data.link_id, rebuild_data.bpf,
|
||||
rebuild_data.tag, rebuild_data.direction, rebuild_data.enabled,
|
||||
marker_name,
|
||||
rebuild_data.link_id,
|
||||
rebuild_data.bpf,
|
||||
rebuild_data.tag,
|
||||
rebuild_data.direction,
|
||||
rebuild_data.enabled,
|
||||
)
|
||||
return {"marker_name": marker_name}
|
||||
@@ -53,7 +53,7 @@ def dep_node(project_id: UUID, node_id: UUID) -> VirtualBoxVM:
|
||||
response_model=schemas.VirtualBox,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create VirtualBox node"}},
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def create_virtualbox_node(project_id: UUID, node_data: schemas.VirtualBoxCreate) -> schemas.VirtualBox:
|
||||
"""
|
||||
@@ -86,11 +86,7 @@ async def create_virtualbox_node(project_id: UUID, node_data: schemas.VirtualBox
|
||||
return vm.asdict()
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{node_id}",
|
||||
response_model=schemas.VirtualBox,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
@router.get("/{node_id}", response_model=schemas.VirtualBox, dependencies=[Depends(compute_authentication)])
|
||||
def get_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> schemas.VirtualBox:
|
||||
"""
|
||||
Return a VirtualBox node.
|
||||
@@ -99,14 +95,9 @@ def get_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> schemas.Virtu
|
||||
return node.asdict()
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{node_id}",
|
||||
response_model=schemas.VirtualBox,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
@router.put("/{node_id}", response_model=schemas.VirtualBox, dependencies=[Depends(compute_authentication)])
|
||||
async def update_virtualbox_node(
|
||||
node_data: schemas.VirtualBoxUpdate,
|
||||
node: VirtualBoxVM = Depends(dep_node)
|
||||
node_data: schemas.VirtualBoxUpdate, node: VirtualBoxVM = Depends(dep_node)
|
||||
) -> schemas.VirtualBox:
|
||||
"""
|
||||
Update a VirtualBox node.
|
||||
@@ -148,11 +139,7 @@ async def update_virtualbox_node(
|
||||
return node.asdict()
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{node_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)])
|
||||
async def delete_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Delete a VirtualBox node.
|
||||
@@ -161,11 +148,7 @@ async def delete_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None
|
||||
await VirtualBox.instance().delete_node(node.id)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/start",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)])
|
||||
async def start_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Start a VirtualBox node.
|
||||
@@ -174,11 +157,7 @@ async def start_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
|
||||
await node.start()
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)])
|
||||
async def stop_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Stop a VirtualBox node.
|
||||
@@ -188,9 +167,7 @@ async def stop_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/suspend",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
"/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def suspend_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
@@ -201,9 +178,7 @@ async def suspend_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> Non
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/resume",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
"/{node_id}/resume", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def resume_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
@@ -214,9 +189,7 @@ async def resume_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/reload",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
"/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def reload_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
@@ -230,14 +203,14 @@ async def reload_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def create_virtualbox_node_nio(
|
||||
*,
|
||||
adapter_number: int,
|
||||
port_number: int = Path(..., ge=0, le=0),
|
||||
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.
|
||||
@@ -253,14 +226,14 @@ async def create_virtualbox_node_nio(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def update_virtualbox_node_nio(
|
||||
*,
|
||||
adapter_number: int,
|
||||
port_number: int = Path(..., ge=0, le=0),
|
||||
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.
|
||||
@@ -279,12 +252,10 @@ async def update_virtualbox_node_nio(
|
||||
@router.delete(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def delete_virtualbox_node_nio(
|
||||
adapter_number: int,
|
||||
port_number: int = Path(..., ge=0, le=0),
|
||||
node: VirtualBoxVM = Depends(dep_node)
|
||||
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.
|
||||
@@ -296,14 +267,14 @@ async def delete_virtualbox_node_nio(
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start",
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def start_virtualbox_node_capture(
|
||||
*,
|
||||
adapter_number: int,
|
||||
port_number: int = Path(..., ge=0, le=0),
|
||||
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.
|
||||
@@ -318,12 +289,10 @@ 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,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def stop_virtualbox_node_capture(
|
||||
adapter_number: int,
|
||||
port_number: int = Path(..., ge=0, le=0),
|
||||
node: VirtualBoxVM = Depends(dep_node)
|
||||
adapter_number: int, port_number: int = Path(..., ge=0, le=0), node: VirtualBoxVM = Depends(dep_node)
|
||||
) -> None:
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
@@ -335,12 +304,10 @@ async def stop_virtualbox_node_capture(
|
||||
|
||||
@router.get(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream",
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def stream_pcap_file(
|
||||
adapter_number: int,
|
||||
port_number: int = Path(..., ge=0, le=0),
|
||||
node: VirtualBoxVM = Depends(dep_node)
|
||||
adapter_number: int, port_number: int = Path(..., ge=0, le=0), node: VirtualBoxVM = Depends(dep_node)
|
||||
) -> StreamingResponse:
|
||||
"""
|
||||
Stream the pcap capture file.
|
||||
@@ -352,12 +319,9 @@ async def stream_pcap_file(
|
||||
return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap")
|
||||
|
||||
|
||||
@router.websocket(
|
||||
"/{node_id}/console/ws"
|
||||
)
|
||||
@router.websocket("/{node_id}/console/ws")
|
||||
async def console_ws(
|
||||
websocket: Union[None, WebSocket] = Depends(ws_compute_authentication),
|
||||
node: VirtualBoxVM = Depends(dep_node)
|
||||
websocket: Union[None, WebSocket] = Depends(ws_compute_authentication), node: VirtualBoxVM = Depends(dep_node)
|
||||
) -> None:
|
||||
"""
|
||||
Console WebSocket.
|
||||
@@ -367,11 +331,8 @@ async def console_ws(
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/console/reset",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
"/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def reset_console(node: VirtualBoxVM = Depends(dep_node)) -> None:
|
||||
|
||||
await node.reset_console()
|
||||
|
||||
@@ -52,7 +52,7 @@ def dep_node(project_id: UUID, node_id: UUID) -> VMwareVM:
|
||||
response_model=schemas.VMware,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create VMware node"}},
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def create_vmware_node(project_id: UUID, node_data: schemas.VMwareCreate) -> schemas.VMware:
|
||||
"""
|
||||
@@ -79,11 +79,7 @@ async def create_vmware_node(project_id: UUID, node_data: schemas.VMwareCreate)
|
||||
return vm.asdict()
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{node_id}",
|
||||
response_model=schemas.VMware,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
@router.get("/{node_id}", response_model=schemas.VMware, dependencies=[Depends(compute_authentication)])
|
||||
def get_vmware_node(node: VMwareVM = Depends(dep_node)) -> schemas.VMware:
|
||||
"""
|
||||
Return a VMware node.
|
||||
@@ -92,11 +88,7 @@ def get_vmware_node(node: VMwareVM = Depends(dep_node)) -> schemas.VMware:
|
||||
return node.asdict()
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{node_id}",
|
||||
response_model=schemas.VMware,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
@router.put("/{node_id}", response_model=schemas.VMware, dependencies=[Depends(compute_authentication)])
|
||||
async def update_vmware_node(node_data: schemas.VMwareUpdate, node: VMwareVM = Depends(dep_node)) -> schemas.VMware:
|
||||
"""
|
||||
Update a VMware node.
|
||||
@@ -113,11 +105,7 @@ async def update_vmware_node(node_data: schemas.VMwareUpdate, node: VMwareVM = D
|
||||
return node.asdict()
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{node_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)])
|
||||
async def delete_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Delete a VMware node.
|
||||
@@ -126,11 +114,7 @@ async def delete_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
|
||||
await VMware.instance().delete_node(node.id)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/start",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)])
|
||||
async def start_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Start a VMware node.
|
||||
@@ -139,11 +123,7 @@ async def start_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
|
||||
await node.start()
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)])
|
||||
async def stop_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Stop a VMware node.
|
||||
@@ -153,9 +133,7 @@ async def stop_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/suspend",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
"/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def suspend_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
@@ -166,9 +144,7 @@ async def suspend_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/resume",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
"/{node_id}/resume", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def resume_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
@@ -179,9 +155,7 @@ async def resume_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/reload",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
"/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def reload_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
@@ -195,14 +169,14 @@ async def reload_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def create_vmware_node_nio(
|
||||
*,
|
||||
adapter_number: int,
|
||||
port_number: int = Path(..., ge=0, le=0),
|
||||
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.
|
||||
@@ -218,14 +192,14 @@ async def create_vmware_node_nio(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def update_vmware_node_nio(
|
||||
*,
|
||||
adapter_number: int,
|
||||
port_number: int = Path(..., ge=0, le=0),
|
||||
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.
|
||||
@@ -243,12 +217,10 @@ async def update_vmware_node_nio(
|
||||
@router.delete(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def delete_vmware_node_nio(
|
||||
adapter_number: int,
|
||||
port_number: int = Path(..., ge=0, le=0),
|
||||
node: VMwareVM = Depends(dep_node)
|
||||
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.
|
||||
@@ -260,14 +232,14 @@ async def delete_vmware_node_nio(
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start",
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def start_vmware_node_capture(
|
||||
*,
|
||||
adapter_number: int,
|
||||
port_number: int = Path(..., ge=0, le=0),
|
||||
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.
|
||||
@@ -282,12 +254,10 @@ 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,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def stop_vmware_node_capture(
|
||||
adapter_number: int,
|
||||
port_number: int = Path(..., ge=0, le=0),
|
||||
node: VMwareVM = Depends(dep_node)
|
||||
adapter_number: int, port_number: int = Path(..., ge=0, le=0), node: VMwareVM = Depends(dep_node)
|
||||
) -> None:
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
@@ -299,12 +269,10 @@ async def stop_vmware_node_capture(
|
||||
|
||||
@router.get(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream",
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def stream_pcap_file(
|
||||
adapter_number: int,
|
||||
port_number: int = Path(..., ge=0, le=0),
|
||||
node: VMwareVM = Depends(dep_node)
|
||||
adapter_number: int, port_number: int = Path(..., ge=0, le=0), node: VMwareVM = Depends(dep_node)
|
||||
) -> StreamingResponse:
|
||||
"""
|
||||
Stream the pcap capture file.
|
||||
@@ -317,9 +285,7 @@ async def stream_pcap_file(
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/interfaces/vmnet",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
"/{node_id}/interfaces/vmnet", status_code=status.HTTP_201_CREATED, dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
def allocate_vmnet(node: VMwareVM = Depends(dep_node)) -> dict:
|
||||
"""
|
||||
@@ -335,8 +301,7 @@ def allocate_vmnet(node: VMwareVM = Depends(dep_node)) -> dict:
|
||||
|
||||
@router.websocket("/{node_id}/console/ws")
|
||||
async def console_ws(
|
||||
websocket: Union[None, WebSocket] = Depends(ws_compute_authentication),
|
||||
node: VMwareVM = Depends(dep_node)
|
||||
websocket: Union[None, WebSocket] = Depends(ws_compute_authentication), node: VMwareVM = Depends(dep_node)
|
||||
) -> None:
|
||||
"""
|
||||
Console WebSocket.
|
||||
@@ -347,9 +312,7 @@ async def console_ws(
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/console/reset",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
"/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def reset_console(node: VMwareVM = Depends(dep_node)) -> None:
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ def dep_node(project_id: UUID, node_id: UUID) -> VPCSVM:
|
||||
response_model=schemas.VPCS,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create VMware node"}},
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def create_vpcs_node(project_id: UUID, node_data: schemas.VPCSCreate) -> schemas.VPCS:
|
||||
"""
|
||||
@@ -72,11 +72,7 @@ async def create_vpcs_node(project_id: UUID, node_data: schemas.VPCSCreate) -> s
|
||||
return vm.asdict()
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{node_id}",
|
||||
response_model=schemas.VPCS,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
@router.get("/{node_id}", response_model=schemas.VPCS, dependencies=[Depends(compute_authentication)])
|
||||
def get_vpcs_node(node: VPCSVM = Depends(dep_node)) -> schemas.VPCS:
|
||||
"""
|
||||
Return a VPCS node.
|
||||
@@ -85,11 +81,7 @@ def get_vpcs_node(node: VPCSVM = Depends(dep_node)) -> schemas.VPCS:
|
||||
return node.asdict()
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{node_id}",
|
||||
response_model=schemas.VPCS,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
@router.put("/{node_id}", response_model=schemas.VPCS, dependencies=[Depends(compute_authentication)])
|
||||
async def update_vpcs_node(node_data: schemas.VPCSUpdate, node: VPCSVM = Depends(dep_node)) -> schemas.VPCS:
|
||||
"""
|
||||
Update a VPCS node.
|
||||
@@ -109,11 +101,7 @@ async def update_vpcs_node(node_data: schemas.VPCSUpdate, node: VPCSVM = Depends
|
||||
return node.asdict()
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{node_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)])
|
||||
async def delete_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Delete a VPCS node.
|
||||
@@ -126,11 +114,11 @@ async def delete_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
|
||||
"/{node_id}/duplicate",
|
||||
response_model=schemas.VPCS,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def duplicate_vpcs_node(
|
||||
destination_node_id: UUID = Body(..., embed=True),
|
||||
node: VPCSVM = Depends(dep_node)) -> None:
|
||||
destination_node_id: UUID = Body(..., embed=True), node: VPCSVM = Depends(dep_node)
|
||||
) -> None:
|
||||
"""
|
||||
Duplicate a VPCS node.
|
||||
"""
|
||||
@@ -139,11 +127,7 @@ async def duplicate_vpcs_node(
|
||||
return new_node.asdict()
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/start",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)])
|
||||
async def start_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Start a VPCS node.
|
||||
@@ -152,11 +136,7 @@ async def start_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
|
||||
await node.start()
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)])
|
||||
async def stop_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Stop a VPCS node.
|
||||
@@ -166,9 +146,7 @@ async def stop_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/suspend",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
"/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def suspend_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
@@ -176,15 +154,12 @@ async def suspend_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_405_METHOD_NOT_ALLOWED,
|
||||
detail="Suspend is not supported for VPCS nodes"
|
||||
status_code=status.HTTP_405_METHOD_NOT_ALLOWED, detail="Suspend is not supported for VPCS nodes"
|
||||
)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/reload",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
"/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def reload_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
@@ -198,14 +173,14 @@ async def reload_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def create_vpcs_node_nio(
|
||||
*,
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
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.
|
||||
@@ -221,14 +196,14 @@ async def create_vpcs_node_nio(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def update_vpcs_node_nio(
|
||||
*,
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
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.
|
||||
@@ -247,13 +222,10 @@ async def update_vpcs_node_nio(
|
||||
@router.delete(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def delete_vpcs_node_nio(
|
||||
*,
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
port_number: int,
|
||||
node: VPCSVM = Depends(dep_node)
|
||||
*, 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.
|
||||
@@ -265,14 +237,14 @@ async def delete_vpcs_node_nio(
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start",
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def start_vpcs_node_capture(
|
||||
*,
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
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.
|
||||
@@ -287,13 +259,10 @@ 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,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def stop_vpcs_node_capture(
|
||||
*,
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
port_number: int,
|
||||
node: VPCSVM = Depends(dep_node)
|
||||
*, adapter_number: int = Path(..., ge=0, le=0), port_number: int, node: VPCSVM = Depends(dep_node)
|
||||
) -> None:
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
@@ -303,16 +272,12 @@ async def stop_vpcs_node_capture(
|
||||
await node.stop_capture(port_number)
|
||||
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream",
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def stream_pcap_file(
|
||||
*,
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
port_number: int,
|
||||
node: VPCSVM = Depends(dep_node)
|
||||
*, adapter_number: int = Path(..., ge=0, le=0), port_number: int, node: VPCSVM = Depends(dep_node)
|
||||
) -> StreamingResponse:
|
||||
"""
|
||||
Stream the pcap capture file.
|
||||
@@ -324,12 +289,10 @@ async def stream_pcap_file(
|
||||
return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap")
|
||||
|
||||
|
||||
@router.websocket(
|
||||
"/{node_id}/console/ws"
|
||||
)
|
||||
@router.websocket("/{node_id}/console/ws")
|
||||
async def console_ws(
|
||||
websocket: Union[None, WebSocket] = Depends(ws_compute_authentication),
|
||||
node: VPCSVM = Depends(dep_node)) -> None:
|
||||
websocket: Union[None, WebSocket] = Depends(ws_compute_authentication), node: VPCSVM = Depends(dep_node)
|
||||
) -> None:
|
||||
"""
|
||||
Console WebSocket.
|
||||
"""
|
||||
@@ -338,23 +301,16 @@ async def console_ws(
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/console/reset",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
"/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def reset_console(node: VPCSVM = Depends(dep_node)) -> None:
|
||||
|
||||
await node.reset_console()
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{node_id}/markers/{marker_name}",
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
@router.put("/{node_id}/markers/{marker_name}", dependencies=[Depends(compute_authentication)])
|
||||
async def toggle_vpcs_marker(
|
||||
marker_name: str,
|
||||
toggle_data: schemas.MarkerToggle,
|
||||
node: VPCSVM = Depends(dep_node)
|
||||
marker_name: str, toggle_data: schemas.MarkerToggle, node: VPCSVM = Depends(dep_node)
|
||||
) -> dict:
|
||||
"""
|
||||
Toggle a marker filter on/off without an NIO rebuild (ubridge contract §3.2).
|
||||
@@ -370,9 +326,7 @@ async def toggle_vpcs_marker(
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/markers/pause",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
"/{node_id}/markers/pause", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def pause_vpcs_markers(node: VPCSVM = Depends(dep_node)) -> None:
|
||||
|
||||
@@ -380,9 +334,7 @@ async def pause_vpcs_markers(node: VPCSVM = Depends(dep_node)) -> None:
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/markers/resume",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
"/{node_id}/markers/resume", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def resume_vpcs_markers(node: VPCSVM = Depends(dep_node)) -> None:
|
||||
|
||||
@@ -392,7 +344,7 @@ async def resume_vpcs_markers(node: VPCSVM = Depends(dep_node)) -> None:
|
||||
@router.delete(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/markers/{marker_name}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def delete_vpcs_marker_capture(
|
||||
*,
|
||||
@@ -400,7 +352,7 @@ async def delete_vpcs_marker_capture(
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
port_number: int,
|
||||
link_id: str = "",
|
||||
node: VPCSVM = Depends(dep_node)
|
||||
node: VPCSVM = Depends(dep_node),
|
||||
) -> None:
|
||||
"""
|
||||
Delete a marker's capture pcap (called by the controller when the marker is
|
||||
@@ -413,14 +365,9 @@ async def delete_vpcs_marker_capture(
|
||||
await node.delete_marker_capture(marker_name, link_id, nio)
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{node_id}/markers/{marker_name}/rebuild",
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
@router.put("/{node_id}/markers/{marker_name}/rebuild", dependencies=[Depends(compute_authentication)])
|
||||
async def rebuild_vpcs_marker(
|
||||
marker_name: str,
|
||||
rebuild_data: schemas.MarkerRebuild,
|
||||
node: VPCSVM = Depends(dep_node)
|
||||
marker_name: str, rebuild_data: schemas.MarkerRebuild, node: VPCSVM = Depends(dep_node)
|
||||
) -> dict:
|
||||
"""
|
||||
Re-install a single marker filter with new BPF/tag/direction (delete + add,
|
||||
@@ -428,7 +375,11 @@ async def rebuild_vpcs_marker(
|
||||
"""
|
||||
|
||||
await node.rebuild_marker_filter(
|
||||
marker_name, rebuild_data.link_id, rebuild_data.bpf,
|
||||
rebuild_data.tag, rebuild_data.direction, rebuild_data.enabled,
|
||||
marker_name,
|
||||
rebuild_data.link_id,
|
||||
rebuild_data.bpf,
|
||||
rebuild_data.tag,
|
||||
rebuild_data.direction,
|
||||
rebuild_data.enabled,
|
||||
)
|
||||
return {"marker_name": marker_name}
|
||||
@@ -24,6 +24,7 @@ if AI_COPILOT_AVAILABLE:
|
||||
from . import chat
|
||||
from . import copilot
|
||||
from . import llm_model_configs
|
||||
|
||||
_chat_router = chat.router
|
||||
_copilot_router = copilot.router
|
||||
_llm_router = llm_model_configs.router
|
||||
@@ -39,9 +40,10 @@ else:
|
||||
async def ai_not_available(path: str = ""):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_501_NOT_IMPLEMENTED,
|
||||
detail="AI Copilot is not available. Install AI dependencies with: pip install gns3-server[ai-features]"
|
||||
detail="AI Copilot is not available. Install AI dependencies with: pip install gns3-server[ai-features]",
|
||||
)
|
||||
|
||||
|
||||
from . import controller
|
||||
from . import appliances
|
||||
from . import computes
|
||||
@@ -68,148 +70,65 @@ from .dependencies.authentication import get_current_active_user
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
router.include_router(controller.router, tags=["Controller"])
|
||||
|
||||
router.include_router(settings.router, prefix="/settings", tags=["Server settings"])
|
||||
|
||||
router.include_router(users.router, prefix="/access/users", tags=["Users"])
|
||||
|
||||
router.include_router(groups.router, prefix="/access/groups", tags=["Users groups"])
|
||||
|
||||
router.include_router(roles.router, prefix="/access/roles", tags=["Roles"])
|
||||
|
||||
router.include_router(
|
||||
controller.router,
|
||||
tags=["Controller"]
|
||||
privileges.router, dependencies=[Depends(get_current_active_user)], prefix="/access/privileges", tags=["Privileges"]
|
||||
)
|
||||
|
||||
router.include_router(acl.router, prefix="/access/acl", tags=["ACL"])
|
||||
|
||||
router.include_router(images.router, prefix="/images", tags=["Images"])
|
||||
|
||||
router.include_router(templates.router, prefix="/templates", tags=["Templates"])
|
||||
|
||||
router.include_router(projects.router, prefix="/projects", tags=["Projects"])
|
||||
|
||||
router.include_router(nodes.router, prefix="/projects/{project_id}/nodes", tags=["Nodes"])
|
||||
|
||||
router.include_router(links.router, prefix="/projects/{project_id}/links", tags=["Links"])
|
||||
|
||||
router.include_router(drawings.router, prefix="/projects/{project_id}/drawings", tags=["Drawings"])
|
||||
|
||||
router.include_router(symbols.router, prefix="/symbols", tags=["Symbols"])
|
||||
|
||||
router.include_router(snapshots.router, prefix="/projects/{project_id}/snapshots", tags=["Snapshots"])
|
||||
|
||||
router.include_router(
|
||||
computes.router, dependencies=[Depends(get_current_active_user)], prefix="/computes", tags=["Computes"]
|
||||
)
|
||||
|
||||
router.include_router(appliances.router, prefix="/appliances", tags=["Appliances"])
|
||||
|
||||
router.include_router(netmiko.router, prefix="/netmiko", tags=["Netmiko"])
|
||||
|
||||
router.include_router(pools.router, prefix="/pools", tags=["Resource pools"])
|
||||
|
||||
router.include_router(
|
||||
gns3vm.router, dependencies=[Depends(get_current_active_user)], deprecated=True, prefix="/gns3vm", tags=["GNS3 VM"]
|
||||
)
|
||||
|
||||
router.include_router(
|
||||
settings.router,
|
||||
prefix="/settings",
|
||||
tags=["Server settings"]
|
||||
_llm_router, prefix="/access", dependencies=[Depends(get_current_active_user)], tags=["LLM Model Configurations"]
|
||||
)
|
||||
|
||||
router.include_router(
|
||||
users.router,
|
||||
prefix="/access/users",
|
||||
tags=["Users"]
|
||||
)
|
||||
|
||||
router.include_router(
|
||||
groups.router,
|
||||
prefix="/access/groups",
|
||||
tags=["Users groups"]
|
||||
)
|
||||
|
||||
router.include_router(
|
||||
roles.router,
|
||||
prefix="/access/roles",
|
||||
tags=["Roles"]
|
||||
)
|
||||
|
||||
router.include_router(
|
||||
privileges.router,
|
||||
dependencies=[Depends(get_current_active_user)],
|
||||
prefix="/access/privileges",
|
||||
tags=["Privileges"]
|
||||
)
|
||||
|
||||
router.include_router(
|
||||
acl.router,
|
||||
prefix="/access/acl",
|
||||
tags=["ACL"]
|
||||
)
|
||||
|
||||
router.include_router(
|
||||
images.router,
|
||||
prefix="/images",
|
||||
tags=["Images"]
|
||||
)
|
||||
|
||||
router.include_router(
|
||||
templates.router,
|
||||
prefix="/templates",
|
||||
tags=["Templates"]
|
||||
)
|
||||
|
||||
router.include_router(
|
||||
projects.router,
|
||||
prefix="/projects",
|
||||
tags=["Projects"])
|
||||
|
||||
router.include_router(
|
||||
nodes.router,
|
||||
prefix="/projects/{project_id}/nodes",
|
||||
tags=["Nodes"]
|
||||
)
|
||||
|
||||
router.include_router(
|
||||
links.router,
|
||||
prefix="/projects/{project_id}/links",
|
||||
tags=["Links"]
|
||||
)
|
||||
|
||||
router.include_router(
|
||||
drawings.router,
|
||||
prefix="/projects/{project_id}/drawings",
|
||||
tags=["Drawings"])
|
||||
|
||||
router.include_router(
|
||||
symbols.router,
|
||||
prefix="/symbols", tags=["Symbols"]
|
||||
)
|
||||
|
||||
router.include_router(
|
||||
snapshots.router,
|
||||
prefix="/projects/{project_id}/snapshots",
|
||||
tags=["Snapshots"])
|
||||
|
||||
router.include_router(
|
||||
computes.router,
|
||||
dependencies=[Depends(get_current_active_user)],
|
||||
prefix="/computes",
|
||||
tags=["Computes"]
|
||||
)
|
||||
|
||||
router.include_router(
|
||||
appliances.router,
|
||||
prefix="/appliances",
|
||||
tags=["Appliances"]
|
||||
)
|
||||
|
||||
router.include_router(
|
||||
netmiko.router,
|
||||
prefix="/netmiko",
|
||||
tags=["Netmiko"]
|
||||
)
|
||||
|
||||
router.include_router(
|
||||
pools.router,
|
||||
prefix="/pools",
|
||||
tags=["Resource pools"]
|
||||
)
|
||||
|
||||
router.include_router(
|
||||
gns3vm.router,
|
||||
dependencies=[Depends(get_current_active_user)],
|
||||
deprecated=True,
|
||||
prefix="/gns3vm",
|
||||
tags=["GNS3 VM"]
|
||||
)
|
||||
|
||||
router.include_router(
|
||||
_llm_router,
|
||||
prefix="/access",
|
||||
dependencies=[Depends(get_current_active_user)],
|
||||
tags=["LLM Model Configurations"]
|
||||
)
|
||||
|
||||
router.include_router(
|
||||
_copilot_router,
|
||||
prefix="/copilot",
|
||||
dependencies=[Depends(get_current_active_user)],
|
||||
tags=["GNS3 Copilot"]
|
||||
_copilot_router, prefix="/copilot", dependencies=[Depends(get_current_active_user)], tags=["GNS3 Copilot"]
|
||||
)
|
||||
|
||||
router.include_router(
|
||||
_chat_router,
|
||||
prefix="/copilot/projects/{project_id}/chat",
|
||||
dependencies=[Depends(get_current_active_user)],
|
||||
tags=["GNS3 Copilot"]
|
||||
tags=["GNS3 Copilot"],
|
||||
)
|
||||
|
||||
router.include_router(
|
||||
api_keys.router,
|
||||
dependencies=[Depends(get_current_active_user)],
|
||||
tags=["API Keys"]
|
||||
)
|
||||
router.include_router(api_keys.router, dependencies=[Depends(get_current_active_user)], tags=["API Keys"])
|
||||
@@ -29,10 +29,7 @@ from typing import Iterator, List, Sequence
|
||||
|
||||
|
||||
from gns3server import schemas
|
||||
from gns3server.controller.controller_error import (
|
||||
ControllerBadRequestError,
|
||||
ControllerNotFoundError
|
||||
)
|
||||
from gns3server.controller.controller_error import ControllerBadRequestError, ControllerNotFoundError
|
||||
|
||||
from gns3server.controller import Controller
|
||||
from gns3server.db.repositories.users import UsersRepository
|
||||
@@ -69,7 +66,9 @@ def _iter_route_paths(routes: Sequence[BaseRoute], prefix: str = "", include_mou
|
||||
for route in routes:
|
||||
if isinstance(route, _IncludedRouter):
|
||||
include_prefix = route.include_context.prefix or ""
|
||||
yield from _iter_route_paths(route.original_router.routes, _join_paths(prefix, include_prefix), include_mounted_routes)
|
||||
yield from _iter_route_paths(
|
||||
route.original_router.routes, _join_paths(prefix, include_prefix), include_mounted_routes
|
||||
)
|
||||
continue
|
||||
|
||||
if isinstance(route, APIRoute):
|
||||
@@ -82,16 +81,12 @@ def _iter_route_paths(routes: Sequence[BaseRoute], prefix: str = "", include_mou
|
||||
yield from _iter_route_paths(mounted_routes, _join_paths(prefix, route.path), include_mounted_routes)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/endpoints",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
dependencies=[Depends(has_privilege("ACE.Audit"))]
|
||||
)
|
||||
@router.get("/endpoints", status_code=status.HTTP_201_CREATED, dependencies=[Depends(has_privilege("ACE.Audit"))])
|
||||
async def endpoints(
|
||||
users_repo: UsersRepository = Depends(get_repository(UsersRepository)),
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
|
||||
images_repo: ImagesRepository = Depends(get_repository(ImagesRepository)),
|
||||
templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository))
|
||||
users_repo: UsersRepository = Depends(get_repository(UsersRepository)),
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
|
||||
images_repo: ImagesRepository = Depends(get_repository(ImagesRepository)),
|
||||
templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository)),
|
||||
) -> List[dict]:
|
||||
"""
|
||||
List all endpoints to be used in ACL entries.
|
||||
@@ -123,7 +118,7 @@ async def endpoints(
|
||||
add_to_endpoints(
|
||||
f"/projects/{project.id}/nodes/{node['node_id']}",
|
||||
f'Node "{node["name"]}" in project "{project.name}"',
|
||||
endpoint_type="node"
|
||||
endpoint_type="node",
|
||||
)
|
||||
|
||||
# links
|
||||
@@ -140,7 +135,7 @@ async def endpoints(
|
||||
add_to_endpoints(
|
||||
f"/projects/{project.id}/links/{link['link_id']}",
|
||||
f'Link from "{node_name_1}" to "{node_name_2}" in project "{project.name}"',
|
||||
endpoint_type="link"
|
||||
endpoint_type="link",
|
||||
)
|
||||
|
||||
# users
|
||||
@@ -179,14 +174,8 @@ async def endpoints(
|
||||
return endpoints
|
||||
|
||||
|
||||
@router.get(
|
||||
"",
|
||||
response_model=List[schemas.ACE],
|
||||
dependencies=[Depends(has_privilege("ACE.Audit"))]
|
||||
)
|
||||
async def get_aces(
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
|
||||
) -> List[schemas.ACE]:
|
||||
@router.get("", response_model=List[schemas.ACE], dependencies=[Depends(has_privilege("ACE.Audit"))])
|
||||
async def get_aces(rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))) -> List[schemas.ACE]:
|
||||
"""
|
||||
Get all ACL entries.
|
||||
|
||||
@@ -200,12 +189,10 @@ async def get_aces(
|
||||
"",
|
||||
response_model=schemas.ACE,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
dependencies=[Depends(has_privilege("ACE.Allocate"))]
|
||||
dependencies=[Depends(has_privilege("ACE.Allocate"))],
|
||||
)
|
||||
async def create_ace(
|
||||
request: Request,
|
||||
ace_create: schemas.ACECreate,
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
|
||||
request: Request, ace_create: schemas.ACECreate, rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
|
||||
) -> schemas.ACE:
|
||||
"""
|
||||
Create a new ACL entry.
|
||||
@@ -217,11 +204,12 @@ async def create_ace(
|
||||
print(route_path)
|
||||
|
||||
for route_path in _iter_route_paths(request.app.routes, include_mounted_routes=True):
|
||||
|
||||
# remove the prefix (e.g. "/v3") from the route path
|
||||
normalized_path = re.sub(r"^/v[0-9]", "", route_path)
|
||||
# replace route path ID parameters by a UUID regex
|
||||
normalized_path = re.sub(r"{\w+_id}", "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}", normalized_path)
|
||||
normalized_path = re.sub(
|
||||
r"{\w+_id}", "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}", normalized_path
|
||||
)
|
||||
# replace remaining route path parameters by a word matching regex
|
||||
normalized_path = re.sub(r"/{[\w:]+}", r"/\\w+", normalized_path)
|
||||
|
||||
@@ -232,14 +220,10 @@ async def create_ace(
|
||||
raise ControllerBadRequestError(f"Path '{ace_create.path}' doesn't match any existing endpoint")
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{ace_id}",
|
||||
response_model=schemas.ACE,
|
||||
dependencies=[Depends(has_privilege("ACE.Audit"))]
|
||||
)
|
||||
@router.get("/{ace_id}", response_model=schemas.ACE, dependencies=[Depends(has_privilege("ACE.Audit"))])
|
||||
async def get_ace(
|
||||
ace_id: UUID,
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
|
||||
ace_id: UUID,
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
|
||||
) -> schemas.ACE:
|
||||
"""
|
||||
Get an ACL entry.
|
||||
@@ -253,15 +237,9 @@ async def get_ace(
|
||||
return ace
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{ace_id}",
|
||||
response_model=schemas.ACE,
|
||||
dependencies=[Depends(has_privilege("ACE.Modify"))]
|
||||
)
|
||||
@router.put("/{ace_id}", response_model=schemas.ACE, dependencies=[Depends(has_privilege("ACE.Modify"))])
|
||||
async def update_ace(
|
||||
ace_id: UUID,
|
||||
ace_update: schemas.ACEUpdate,
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
|
||||
ace_id: UUID, ace_update: schemas.ACEUpdate, rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
|
||||
) -> schemas.ACE:
|
||||
"""
|
||||
Update an ACL entry.
|
||||
@@ -277,9 +255,7 @@ async def update_ace(
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{ace_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(has_privilege("ACE.Allocate"))]
|
||||
"/{ace_id}", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(has_privilege("ACE.Allocate"))]
|
||||
)
|
||||
async def delete_ace(
|
||||
ace_id: UUID,
|
||||
|
||||
@@ -26,11 +26,7 @@ from uuid import UUID
|
||||
|
||||
from gns3server import schemas
|
||||
from gns3server.controller import Controller
|
||||
from gns3server.controller.controller_error import (
|
||||
ControllerError,
|
||||
ControllerBadRequestError,
|
||||
ControllerNotFoundError
|
||||
)
|
||||
from gns3server.controller.controller_error import ControllerError, ControllerBadRequestError, ControllerNotFoundError
|
||||
|
||||
from gns3server.db.repositories.images import ImagesRepository
|
||||
from gns3server.db.repositories.templates import TemplatesRepository
|
||||
@@ -50,12 +46,9 @@ router = APIRouter()
|
||||
"",
|
||||
response_model=List[schemas.Appliance],
|
||||
response_model_exclude_unset=True,
|
||||
dependencies=[Depends(has_privilege("Appliance.Audit"))]
|
||||
dependencies=[Depends(has_privilege("Appliance.Audit"))],
|
||||
)
|
||||
async def get_appliances(
|
||||
update: Optional[bool] = False,
|
||||
symbol_theme: Optional[str] = None
|
||||
) -> List[schemas.Appliance]:
|
||||
async def get_appliances(update: Optional[bool] = False, symbol_theme: Optional[str] = None) -> List[schemas.Appliance]:
|
||||
"""
|
||||
Return all appliances known by the controller.
|
||||
|
||||
@@ -73,7 +66,7 @@ async def get_appliances(
|
||||
"/{appliance_id}",
|
||||
response_model=schemas.Appliance,
|
||||
response_model_exclude_unset=True,
|
||||
dependencies=[Depends(has_privilege("Appliance.Audit"))]
|
||||
dependencies=[Depends(has_privilege("Appliance.Audit"))],
|
||||
)
|
||||
def get_appliance(appliance_id: UUID) -> schemas.Appliance:
|
||||
"""
|
||||
@@ -92,9 +85,11 @@ def get_appliance(appliance_id: UUID) -> schemas.Appliance:
|
||||
@router.post(
|
||||
"/{appliance_id}/version",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
dependencies=[Depends(has_privilege("Appliance.Allocate"))]
|
||||
dependencies=[Depends(has_privilege("Appliance.Allocate"))],
|
||||
)
|
||||
def add_appliance_version(appliance_id: UUID, appliance_version: Union[schemas.ApplianceVersion, schemas.ApplianceVersionV8]) -> dict:
|
||||
def add_appliance_version(
|
||||
appliance_id: UUID, appliance_version: Union[schemas.ApplianceVersion, schemas.ApplianceVersionV8]
|
||||
) -> dict:
|
||||
"""
|
||||
Add a version to an appliance.
|
||||
|
||||
@@ -124,15 +119,15 @@ def add_appliance_version(appliance_id: UUID, appliance_version: Union[schemas.A
|
||||
"/{appliance_id}/install",
|
||||
response_model=schemas.Template,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
dependencies=[Depends(has_privilege("Appliance.Allocate"))]
|
||||
dependencies=[Depends(has_privilege("Appliance.Allocate"))],
|
||||
)
|
||||
async def install_appliance(
|
||||
appliance_id: UUID,
|
||||
version: Optional[str] = None,
|
||||
images_repo: ImagesRepository = Depends(get_repository(ImagesRepository)),
|
||||
templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository)),
|
||||
current_user: schemas.User = Depends(get_current_active_user),
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
|
||||
appliance_id: UUID,
|
||||
version: Optional[str] = None,
|
||||
images_repo: ImagesRepository = Depends(get_repository(ImagesRepository)),
|
||||
templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository)),
|
||||
current_user: schemas.User = Depends(get_current_active_user),
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
|
||||
) -> schemas.Template:
|
||||
"""
|
||||
Install an appliance and return the created template.
|
||||
@@ -142,10 +137,5 @@ async def install_appliance(
|
||||
|
||||
controller = Controller.instance()
|
||||
return await controller.appliance_manager.install_appliance(
|
||||
appliance_id,
|
||||
version,
|
||||
images_repo,
|
||||
templates_repo,
|
||||
rbac_repo,
|
||||
current_user
|
||||
appliance_id, version, images_repo, templates_repo, rbac_repo, current_user
|
||||
)
|
||||
@@ -69,7 +69,7 @@ def dep_project(project_id: UUID) -> Project:
|
||||
"/stream",
|
||||
response_model=None,
|
||||
summary="Stream chat responses from GNS3 Copilot",
|
||||
description="Send a message to GNS3 Copilot and stream the response via Server-Sent Events (SSE)."
|
||||
description="Send a message to GNS3 Copilot and stream the response via Server-Sent Events (SSE).",
|
||||
)
|
||||
async def stream_chat(
|
||||
request: schemas.ChatRequest,
|
||||
@@ -96,11 +96,11 @@ async def stream_chat(
|
||||
"Chat rejected: project not opened. user_id=%s, project_id=%s, status=%s",
|
||||
user_id,
|
||||
project.id,
|
||||
project.status
|
||||
project.status,
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=f"Project must be opened to use chat. Current status: {project.status}"
|
||||
detail=f"Project must be opened to use chat. Current status: {project.status}",
|
||||
)
|
||||
|
||||
log.info(
|
||||
@@ -124,7 +124,7 @@ async def stream_chat(
|
||||
log.warning("LLM config not found for user: %s", user_id)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="LLM configuration not found. Please configure your LLM settings first."
|
||||
detail="LLM configuration not found. Please configure your LLM settings first.",
|
||||
)
|
||||
|
||||
log.debug(
|
||||
@@ -161,7 +161,7 @@ async def stream_chat(
|
||||
user_id=user_id,
|
||||
jwt_token=jwt_token,
|
||||
mode=request.mode,
|
||||
llm_config=llm_config
|
||||
llm_config=llm_config,
|
||||
):
|
||||
try:
|
||||
# Validate and serialize chunk
|
||||
@@ -187,7 +187,7 @@ async def stream_chat(
|
||||
"Cache-Control": "no-cache",
|
||||
"X-Accel-Buffering": "no",
|
||||
"Connection": "keep-alive",
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -195,7 +195,7 @@ async def stream_chat(
|
||||
"/sessions",
|
||||
response_model=List[schemas.ChatSession],
|
||||
summary="List chat sessions",
|
||||
description="List all chat sessions for a project, optionally filtered by copilot_mode."
|
||||
description="List all chat sessions for a project, optionally filtered by copilot_mode.",
|
||||
)
|
||||
async def list_sessions(
|
||||
project: Project = Depends(dep_project),
|
||||
@@ -213,7 +213,7 @@ async def list_sessions(
|
||||
if project.status != "opened":
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=f"Project must be opened to access chat sessions. Current status: {project.status}"
|
||||
detail=f"Project must be opened to access chat sessions. Current status: {project.status}",
|
||||
)
|
||||
|
||||
# Get AgentService for this project
|
||||
@@ -221,10 +221,7 @@ async def list_sessions(
|
||||
agent_service = await agent_manager.get_agent(str(project.id), project.path)
|
||||
|
||||
# List sessions with optional copilot_mode filter
|
||||
sessions = await agent_service.list_sessions(
|
||||
user_id=str(current_user.user_id),
|
||||
copilot_mode=copilot_mode
|
||||
)
|
||||
sessions = await agent_service.list_sessions(user_id=str(current_user.user_id), copilot_mode=copilot_mode)
|
||||
|
||||
# Convert to schemas
|
||||
return [schemas.ChatSession(**s) for s in sessions]
|
||||
@@ -234,7 +231,7 @@ async def list_sessions(
|
||||
"/sessions/{session_id}/history",
|
||||
response_model=schemas.ConversationHistory,
|
||||
summary="Get conversation history",
|
||||
description="Retrieve the conversation history for a specific session/thread."
|
||||
description="Retrieve the conversation history for a specific session/thread.",
|
||||
)
|
||||
async def get_history(
|
||||
session_id: str,
|
||||
@@ -250,7 +247,7 @@ async def get_history(
|
||||
if project.status != "opened":
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=f"Project must be opened to access chat history. Current status: {project.status}"
|
||||
detail=f"Project must be opened to access chat history. Current status: {project.status}",
|
||||
)
|
||||
|
||||
# Get AgentService for this project
|
||||
@@ -267,7 +264,7 @@ async def get_history(
|
||||
"/sessions/{session_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
summary="Delete a chat session",
|
||||
description="Delete a specific chat session and its checkpoints."
|
||||
description="Delete a specific chat session and its checkpoints.",
|
||||
)
|
||||
async def delete_session(
|
||||
session_id: str,
|
||||
@@ -282,7 +279,7 @@ async def delete_session(
|
||||
if project.status != "opened":
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=f"Project must be opened to delete chat sessions. Current status: {project.status}"
|
||||
detail=f"Project must be opened to delete chat sessions. Current status: {project.status}",
|
||||
)
|
||||
|
||||
# Get AgentService for this project
|
||||
@@ -293,17 +290,14 @@ async def delete_session(
|
||||
deleted = await agent_service.delete_session(session_id)
|
||||
|
||||
if not deleted:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Session '{session_id}' not found"
|
||||
)
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Session '{session_id}' not found")
|
||||
|
||||
|
||||
@router.post(
|
||||
"/sessions/{session_id}/abort",
|
||||
status_code=status.HTTP_200_OK,
|
||||
summary="Abort a streaming session",
|
||||
description="Abort an ongoing streaming session for a specific session."
|
||||
description="Abort an ongoing streaming session for a specific session.",
|
||||
)
|
||||
async def abort_session(
|
||||
session_id: str,
|
||||
@@ -321,7 +315,7 @@ async def abort_session(
|
||||
if project.status != "opened":
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=f"Project must be opened to abort chat session. Current status: {project.status}"
|
||||
detail=f"Project must be opened to abort chat session. Current status: {project.status}",
|
||||
)
|
||||
|
||||
# Get AgentService for this project
|
||||
@@ -338,7 +332,7 @@ async def abort_session(
|
||||
"/sessions/{session_id}",
|
||||
response_model=schemas.ChatSession,
|
||||
summary="Rename a chat session",
|
||||
description="Rename a specific chat session."
|
||||
description="Rename a specific chat session.",
|
||||
)
|
||||
async def rename_session(
|
||||
session_id: str,
|
||||
@@ -354,7 +348,7 @@ async def rename_session(
|
||||
if project.status != "opened":
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=f"Project must be opened to rename chat sessions. Current status: {project.status}"
|
||||
detail=f"Project must be opened to rename chat sessions. Current status: {project.status}",
|
||||
)
|
||||
|
||||
# Get AgentService for this project
|
||||
@@ -365,10 +359,7 @@ async def rename_session(
|
||||
session = await agent_service.rename_session(session_id, request.title)
|
||||
|
||||
if not session:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Session '{session_id}' not found"
|
||||
)
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Session '{session_id}' not found")
|
||||
|
||||
return schemas.ChatSession(**session)
|
||||
|
||||
@@ -377,7 +368,7 @@ async def rename_session(
|
||||
"/sessions/{session_id}/pin",
|
||||
response_model=schemas.ChatSession,
|
||||
summary="Pin a chat session",
|
||||
description="Pin a chat session to the top of the list."
|
||||
description="Pin a chat session to the top of the list.",
|
||||
)
|
||||
async def pin_session(
|
||||
session_id: str,
|
||||
@@ -392,7 +383,7 @@ async def pin_session(
|
||||
if project.status != "opened":
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=f"Project must be opened to pin chat sessions. Current status: {project.status}"
|
||||
detail=f"Project must be opened to pin chat sessions. Current status: {project.status}",
|
||||
)
|
||||
|
||||
# Get AgentService for this project
|
||||
@@ -403,10 +394,7 @@ async def pin_session(
|
||||
session = await agent_service.pin_session(session_id, pinned=True)
|
||||
|
||||
if not session:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Session '{session_id}' not found"
|
||||
)
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Session '{session_id}' not found")
|
||||
|
||||
return schemas.ChatSession(**session)
|
||||
|
||||
@@ -415,7 +403,7 @@ async def pin_session(
|
||||
"/sessions/{session_id}/pin",
|
||||
response_model=schemas.ChatSession,
|
||||
summary="Unpin a chat session",
|
||||
description="Unpin a chat session from the top of the list."
|
||||
description="Unpin a chat session from the top of the list.",
|
||||
)
|
||||
async def unpin_session(
|
||||
session_id: str,
|
||||
@@ -430,7 +418,7 @@ async def unpin_session(
|
||||
if project.status != "opened":
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=f"Project must be opened to unpin chat sessions. Current status: {project.status}"
|
||||
detail=f"Project must be opened to unpin chat sessions. Current status: {project.status}",
|
||||
)
|
||||
|
||||
# Get AgentService for this project
|
||||
@@ -441,10 +429,7 @@ async def unpin_session(
|
||||
session = await agent_service.pin_session(session_id, pinned=False)
|
||||
|
||||
if not session:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Session '{session_id}' not found"
|
||||
)
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Session '{session_id}' not found")
|
||||
|
||||
return schemas.ChatSession(**session)
|
||||
|
||||
@@ -453,7 +438,7 @@ async def unpin_session(
|
||||
"/inject",
|
||||
response_model=None,
|
||||
summary="Inject a network fault for troubleshooting practice",
|
||||
description="Inject a realistic network fault into the GNS3 lab for troubleshooting training."
|
||||
description="Inject a realistic network fault into the GNS3 lab for troubleshooting training.",
|
||||
)
|
||||
async def inject_issue(
|
||||
request: schemas.ChatRequest,
|
||||
@@ -482,11 +467,11 @@ async def inject_issue(
|
||||
"Fault injection rejected: project not opened. user_id=%s, project_id=%s, status=%s",
|
||||
user_id,
|
||||
project.id,
|
||||
project.status
|
||||
project.status,
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=f"Project must be opened to use fault injection. Current status: {project.status}"
|
||||
detail=f"Project must be opened to use fault injection. Current status: {project.status}",
|
||||
)
|
||||
|
||||
log.info(
|
||||
@@ -510,7 +495,7 @@ async def inject_issue(
|
||||
log.warning("LLM config not found for user: %s", user_id)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="LLM configuration not found. Please configure your LLM settings first."
|
||||
detail="LLM configuration not found. Please configure your LLM settings first.",
|
||||
)
|
||||
|
||||
# Override copilot_mode to troubleshooting_injection
|
||||
@@ -546,7 +531,7 @@ async def inject_issue(
|
||||
user_id=user_id,
|
||||
jwt_token=jwt_token,
|
||||
mode=request.mode,
|
||||
llm_config=llm_config
|
||||
llm_config=llm_config,
|
||||
):
|
||||
try:
|
||||
# Validate and serialize chunk
|
||||
@@ -572,5 +557,5 @@ async def inject_issue(
|
||||
"Cache-Control": "no-cache",
|
||||
"X-Accel-Buffering": "no",
|
||||
"Connection": "keep-alive",
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -45,12 +45,12 @@ router = APIRouter(responses=responses)
|
||||
409: {"model": schemas.ErrorMessage, "description": "Could not create compute"},
|
||||
401: {"model": schemas.ErrorMessage, "description": "Invalid authentication for compute"},
|
||||
},
|
||||
dependencies=[Depends(has_privilege("Compute.Allocate"))]
|
||||
dependencies=[Depends(has_privilege("Compute.Allocate"))],
|
||||
)
|
||||
async def create_compute(
|
||||
compute_create: schemas.ComputeCreate,
|
||||
computes_repo: ComputesRepository = Depends(get_repository(ComputesRepository)),
|
||||
connect: Optional[bool] = False
|
||||
connect: Optional[bool] = False,
|
||||
) -> schemas.Compute:
|
||||
"""
|
||||
Create a new compute on the controller.
|
||||
@@ -64,7 +64,7 @@ async def create_compute(
|
||||
@router.post(
|
||||
"/{compute_id}/connect",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
#dependencies=[Depends(has_privilege("Compute.Audit"))] # FIXME: this is a temporary workaround due to a bug in the web-ui
|
||||
# dependencies=[Depends(has_privilege("Compute.Audit"))] # FIXME: this is a temporary workaround due to a bug in the web-ui
|
||||
)
|
||||
async def connect_compute(compute_id: Union[str, UUID]) -> None:
|
||||
"""
|
||||
@@ -82,7 +82,7 @@ async def connect_compute(compute_id: Union[str, UUID]) -> None:
|
||||
"/{compute_id}",
|
||||
response_model=schemas.Compute,
|
||||
response_model_exclude_unset=True,
|
||||
#dependencies=[Depends(has_privilege("Compute.Audit"))] # FIXME: this is a temporary workaround due to a bug in the web-ui
|
||||
# dependencies=[Depends(has_privilege("Compute.Audit"))] # FIXME: this is a temporary workaround due to a bug in the web-ui
|
||||
)
|
||||
async def get_compute(
|
||||
compute_id: Union[str, UUID], computes_repo: ComputesRepository = Depends(get_repository(ComputesRepository))
|
||||
@@ -100,7 +100,7 @@ async def get_compute(
|
||||
"",
|
||||
response_model=List[schemas.Compute],
|
||||
response_model_exclude_unset=True,
|
||||
#dependencies=[Depends(has_privilege("Compute.Audit"))] # FIXME: this is a temporary workaround due to a bug in the web-ui
|
||||
# dependencies=[Depends(has_privilege("Compute.Audit"))] # FIXME: this is a temporary workaround due to a bug in the web-ui
|
||||
)
|
||||
async def get_computes(
|
||||
computes_repo: ComputesRepository = Depends(get_repository(ComputesRepository)),
|
||||
@@ -118,7 +118,7 @@ async def get_computes(
|
||||
"/{compute_id}",
|
||||
response_model=schemas.Compute,
|
||||
response_model_exclude_unset=True,
|
||||
dependencies=[Depends(has_privilege("Compute.Modify"))]
|
||||
dependencies=[Depends(has_privilege("Compute.Modify"))],
|
||||
)
|
||||
async def update_compute(
|
||||
compute_id: Union[str, UUID],
|
||||
@@ -135,14 +135,12 @@ async def update_compute(
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{compute_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(has_privilege("Compute.Allocate"))]
|
||||
"/{compute_id}", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(has_privilege("Compute.Allocate"))]
|
||||
)
|
||||
async def delete_compute(
|
||||
compute_id: Union[str, UUID],
|
||||
computes_repo: ComputesRepository = Depends(get_repository(ComputesRepository)),
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
|
||||
compute_id: Union[str, UUID],
|
||||
computes_repo: ComputesRepository = Depends(get_repository(ComputesRepository)),
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
|
||||
) -> None:
|
||||
"""
|
||||
Delete a compute from the controller.
|
||||
@@ -168,11 +166,10 @@ async def docker_get_images(compute_id: Union[str, UUID]) -> List[schemas.Comput
|
||||
@router.post(
|
||||
"/{compute_id}/docker/images/pull",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(has_privilege("Compute.Modify"))]
|
||||
dependencies=[Depends(has_privilege("Compute.Modify"))],
|
||||
)
|
||||
async def docker_pull_image(
|
||||
compute_id: Union[str, UUID],
|
||||
image: str = Body(..., embed=True, min_length=1, pattern=r"^\S+$")
|
||||
compute_id: Union[str, UUID], image: str = Body(..., embed=True, min_length=1, pattern=r"^\S+$")
|
||||
) -> None:
|
||||
"""
|
||||
Pull or update a Docker image on a compute.
|
||||
|
||||
@@ -68,11 +68,7 @@ def get_version(request: Request) -> dict:
|
||||
controller_host = route.app.state.controller_host
|
||||
|
||||
local_server = Config.instance().settings.Server.local
|
||||
return {
|
||||
"controller_host": controller_host,
|
||||
"version": __version__,
|
||||
"local": local_server
|
||||
}
|
||||
return {"controller_host": controller_host, "version": __version__, "local": local_server}
|
||||
|
||||
|
||||
@router.post(
|
||||
@@ -140,11 +136,7 @@ async def shutdown() -> None:
|
||||
os.kill(os.getpid(), signal.SIGTERM)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/iou_license",
|
||||
dependencies=[Depends(get_current_active_user)],
|
||||
response_model=schemas.IOULicense
|
||||
)
|
||||
@router.get("/iou_license", dependencies=[Depends(get_current_active_user)], response_model=schemas.IOULicense)
|
||||
def get_iou_license() -> schemas.IOULicense:
|
||||
"""
|
||||
Return the IOU license settings
|
||||
@@ -157,7 +149,7 @@ def get_iou_license() -> schemas.IOULicense:
|
||||
"/iou_license",
|
||||
dependencies=[Depends(get_current_active_user)],
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.IOULicense
|
||||
response_model=schemas.IOULicense,
|
||||
)
|
||||
async def update_iou_license(iou_license: schemas.IOULicense) -> schemas.IOULicense:
|
||||
"""
|
||||
@@ -271,8 +263,10 @@ async def controller_http_notifications(request: Request) -> StreamingResponse:
|
||||
"""
|
||||
|
||||
from gns3server.api.server import app
|
||||
log.info(f"New client {request.client.host}:{request.client.port} has connected to controller HTTP "
|
||||
f"notification stream")
|
||||
|
||||
log.info(
|
||||
f"New client {request.client.host}:{request.client.port} has connected to controller HTTP notification stream"
|
||||
)
|
||||
|
||||
async def event_stream():
|
||||
try:
|
||||
@@ -281,15 +275,17 @@ async def controller_http_notifications(request: Request) -> StreamingResponse:
|
||||
msg = await queue.get_json(5)
|
||||
yield f"{msg}\n".encode("utf-8")
|
||||
finally:
|
||||
log.info(f"Client {request.client.host}:{request.client.port} has disconnected from controller HTTP "
|
||||
f"notification stream")
|
||||
log.info(
|
||||
f"Client {request.client.host}:{request.client.port} has disconnected from controller HTTP "
|
||||
f"notification stream"
|
||||
)
|
||||
|
||||
return StreamingResponse(event_stream(), media_type="application/json")
|
||||
|
||||
|
||||
@router.websocket("/notifications/ws")
|
||||
async def controller_ws_notifications(
|
||||
websocket: WebSocket,
|
||||
current_user: schemas.User = Depends(get_current_active_user_from_websocket)
|
||||
websocket: WebSocket, current_user: schemas.User = Depends(get_current_active_user_from_websocket)
|
||||
) -> None:
|
||||
"""
|
||||
Receive project notifications about the controller from WebSocket.
|
||||
|
||||
@@ -50,14 +50,13 @@ def _reject_refresh_token(token_data) -> None:
|
||||
|
||||
|
||||
async def get_user_from_token(
|
||||
request: Request,
|
||||
bearer_token: str = Depends(oauth2_scheme),
|
||||
user_repo: UsersRepository = Depends(get_repository(UsersRepository)),
|
||||
api_keys_repo: ApiKeysRepository = Depends(get_repository(ApiKeysRepository)),
|
||||
token: Optional[str] = Query(None, include_in_schema=False)
|
||||
request: Request,
|
||||
bearer_token: str = Depends(oauth2_scheme),
|
||||
user_repo: UsersRepository = Depends(get_repository(UsersRepository)),
|
||||
api_keys_repo: ApiKeysRepository = Depends(get_repository(ApiKeysRepository)),
|
||||
token: Optional[str] = Query(None, include_in_schema=False),
|
||||
) -> schemas.User:
|
||||
|
||||
|
||||
if bearer_token:
|
||||
# bearer token is used first, then any token passed as a URL parameter
|
||||
token = bearer_token
|
||||
@@ -147,9 +146,9 @@ async def get_user_from_token(
|
||||
|
||||
|
||||
async def get_current_active_user(
|
||||
request: Request,
|
||||
current_user: schemas.User = Depends(get_user_from_token),
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
|
||||
request: Request,
|
||||
current_user: schemas.User = Depends(get_user_from_token),
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
|
||||
) -> schemas.User:
|
||||
|
||||
# Super admin is always authorized
|
||||
@@ -167,9 +166,9 @@ async def get_current_active_user(
|
||||
|
||||
|
||||
async def get_current_active_user_from_websocket(
|
||||
websocket: WebSocket,
|
||||
token: str = Query(...),
|
||||
user_repo: UsersRepository = Depends(get_repository(UsersRepository)),
|
||||
websocket: WebSocket,
|
||||
token: str = Query(...),
|
||||
user_repo: UsersRepository = Depends(get_repository(UsersRepository)),
|
||||
) -> Optional[schemas.User]:
|
||||
|
||||
# Extract requested subprotocols from headers for proper WebSocket negotiation
|
||||
@@ -209,12 +208,11 @@ async def get_current_active_user_from_websocket(
|
||||
if user is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail=f"Could not validate credentials for '{token_data.username}'"
|
||||
detail=f"Could not validate credentials for '{token_data.username}'",
|
||||
)
|
||||
if token_data.token_version != user.token_version:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail=f"Token has been revoked for '{token_data.username}'"
|
||||
status_code=status.HTTP_401_UNAUTHORIZED, detail=f"Token has been revoked for '{token_data.username}'"
|
||||
)
|
||||
|
||||
# Super admin is always authorized
|
||||
@@ -223,8 +221,7 @@ async def get_current_active_user_from_websocket(
|
||||
|
||||
if not user.is_active:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail=f"'{token_data.username}' is not an active user"
|
||||
status_code=status.HTTP_401_UNAUTHORIZED, detail=f"'{token_data.username}' is not an active user"
|
||||
)
|
||||
|
||||
return user
|
||||
@@ -242,4 +239,3 @@ async def get_current_active_user_from_websocket(
|
||||
await websocket.send_json(websocket_error)
|
||||
log.error(err_msg)
|
||||
return await websocket.close(code=1008)
|
||||
|
||||
@@ -22,8 +22,6 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from gns3server.db.repositories.base import BaseRepository
|
||||
|
||||
|
||||
|
||||
|
||||
async def get_db_session(request: HTTPConnection) -> AsyncSession:
|
||||
|
||||
async with AsyncSession(request.app.state._db_engine, expire_on_commit=False) as session:
|
||||
|
||||
@@ -27,30 +27,29 @@ import logging
|
||||
log = logging.getLogger()
|
||||
|
||||
|
||||
def has_privilege(
|
||||
privilege_name: str
|
||||
):
|
||||
def has_privilege(privilege_name: str):
|
||||
async def get_user_and_check_privilege(
|
||||
request: Request,
|
||||
current_user: schemas.User = Depends(get_current_active_user),
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
|
||||
request: Request,
|
||||
current_user: schemas.User = Depends(get_current_active_user),
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
|
||||
):
|
||||
if not current_user.is_superadmin:
|
||||
path = re.sub(r"^/v[0-9]", "", request.url.path) # remove the prefix (e.g. "/v3") from URL path
|
||||
log.debug(f"Checking user {current_user.username} has privilege {privilege_name} on '{path}'")
|
||||
if not await rbac_repo.check_user_has_privilege(current_user.user_id, path, privilege_name):
|
||||
raise HTTPException(status_code=403, detail=f"Permission denied (privilege {privilege_name} is required)")
|
||||
raise HTTPException(
|
||||
status_code=403, detail=f"Permission denied (privilege {privilege_name} is required)"
|
||||
)
|
||||
return current_user
|
||||
|
||||
return get_user_and_check_privilege
|
||||
|
||||
|
||||
def has_privilege_on_websocket(
|
||||
privilege_name: str
|
||||
):
|
||||
def has_privilege_on_websocket(privilege_name: str):
|
||||
async def get_user_and_check_privilege(
|
||||
websocket: WebSocket,
|
||||
current_user: schemas.User = Depends(get_current_active_user_from_websocket),
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
|
||||
websocket: WebSocket,
|
||||
current_user: schemas.User = Depends(get_current_active_user_from_websocket),
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
|
||||
):
|
||||
# Authentication may have failed and closed the socket inside the auth
|
||||
# dependency, returning None — bail out before touching the user object.
|
||||
@@ -60,10 +59,14 @@ def has_privilege_on_websocket(
|
||||
path = re.sub(r"^/v[0-9]", "", websocket.url.path) # remove the prefix (e.g. "/v3") from URL path
|
||||
log.debug(f"Checking user {current_user.username} has privilege {privilege_name} on '{path}'")
|
||||
if not await rbac_repo.check_user_has_privilege(current_user.user_id, path, privilege_name):
|
||||
raise HTTPException(status_code=403, detail=f"Permission denied (privilege {privilege_name} is required)")
|
||||
raise HTTPException(
|
||||
status_code=403, detail=f"Permission denied (privilege {privilege_name} is required)"
|
||||
)
|
||||
return current_user
|
||||
|
||||
return get_user_and_check_privilege
|
||||
|
||||
|
||||
# class PrivilegeChecker:
|
||||
#
|
||||
# def __init__(self, required_privilege: str) -> None:
|
||||
|
||||
@@ -39,7 +39,7 @@ router = APIRouter(responses=responses)
|
||||
"",
|
||||
response_model=List[schemas.Drawing],
|
||||
response_model_exclude_unset=True,
|
||||
dependencies=[Depends(has_privilege("Drawing.Audit"))]
|
||||
dependencies=[Depends(has_privilege("Drawing.Audit"))],
|
||||
)
|
||||
async def get_drawings(project_id: UUID) -> List[schemas.Drawing]:
|
||||
"""
|
||||
@@ -59,7 +59,7 @@ async def get_drawings(project_id: UUID) -> List[schemas.Drawing]:
|
||||
"",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.Drawing,
|
||||
dependencies=[Depends(has_privilege("Drawing.Allocate"))]
|
||||
dependencies=[Depends(has_privilege("Drawing.Allocate"))],
|
||||
)
|
||||
async def create_drawing(project_id: UUID, drawing_data: schemas.Drawing) -> schemas.Drawing:
|
||||
"""
|
||||
@@ -77,7 +77,7 @@ async def create_drawing(project_id: UUID, drawing_data: schemas.Drawing) -> sch
|
||||
"/{drawing_id}",
|
||||
response_model=schemas.Drawing,
|
||||
response_model_exclude_unset=True,
|
||||
dependencies=[Depends(has_privilege("Drawing.Audit"))]
|
||||
dependencies=[Depends(has_privilege("Drawing.Audit"))],
|
||||
)
|
||||
async def get_drawing(project_id: UUID, drawing_id: UUID) -> schemas.Drawing:
|
||||
"""
|
||||
@@ -95,7 +95,7 @@ async def get_drawing(project_id: UUID, drawing_id: UUID) -> schemas.Drawing:
|
||||
"/{drawing_id}",
|
||||
response_model=schemas.Drawing,
|
||||
response_model_exclude_unset=True,
|
||||
dependencies=[Depends(has_privilege("Drawing.Modify"))]
|
||||
dependencies=[Depends(has_privilege("Drawing.Modify"))],
|
||||
)
|
||||
async def update_drawing(project_id: UUID, drawing_id: UUID, drawing_data: schemas.Drawing) -> schemas.Drawing:
|
||||
"""
|
||||
@@ -111,14 +111,10 @@ async def update_drawing(project_id: UUID, drawing_id: UUID, drawing_data: schem
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{drawing_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(has_privilege("Drawing.Allocate"))]
|
||||
"/{drawing_id}", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(has_privilege("Drawing.Allocate"))]
|
||||
)
|
||||
async def delete_drawing(
|
||||
project_id: UUID,
|
||||
drawing_id: UUID,
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
|
||||
project_id: UUID, drawing_id: UUID, rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
|
||||
) -> None:
|
||||
"""
|
||||
Delete a drawing.
|
||||
|
||||
@@ -44,13 +44,9 @@ log = logging.getLogger(__name__)
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get(
|
||||
"",
|
||||
response_model=List[schemas.UserGroup],
|
||||
dependencies=[Depends(has_privilege("Group.Audit"))]
|
||||
)
|
||||
@router.get("", response_model=List[schemas.UserGroup], dependencies=[Depends(has_privilege("Group.Audit"))])
|
||||
async def get_user_groups(
|
||||
users_repo: UsersRepository = Depends(get_repository(UsersRepository))
|
||||
users_repo: UsersRepository = Depends(get_repository(UsersRepository)),
|
||||
) -> List[schemas.UserGroup]:
|
||||
"""
|
||||
Get all user groups.
|
||||
@@ -65,11 +61,10 @@ async def get_user_groups(
|
||||
"",
|
||||
response_model=schemas.UserGroup,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
dependencies=[Depends(has_privilege("Group.Allocate"))]
|
||||
dependencies=[Depends(has_privilege("Group.Allocate"))],
|
||||
)
|
||||
async def create_user_group(
|
||||
user_group_create: schemas.UserGroupCreate,
|
||||
users_repo: UsersRepository = Depends(get_repository(UsersRepository))
|
||||
user_group_create: schemas.UserGroupCreate, users_repo: UsersRepository = Depends(get_repository(UsersRepository))
|
||||
) -> schemas.UserGroup:
|
||||
"""
|
||||
Create a new user group.
|
||||
@@ -83,14 +78,10 @@ async def create_user_group(
|
||||
return await users_repo.create_user_group(user_group_create)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{user_group_id}",
|
||||
response_model=schemas.UserGroup,
|
||||
dependencies=[Depends(has_privilege("Group.Audit"))]
|
||||
)
|
||||
@router.get("/{user_group_id}", response_model=schemas.UserGroup, dependencies=[Depends(has_privilege("Group.Audit"))])
|
||||
async def get_user_group(
|
||||
user_group_id: UUID,
|
||||
users_repo: UsersRepository = Depends(get_repository(UsersRepository)),
|
||||
user_group_id: UUID,
|
||||
users_repo: UsersRepository = Depends(get_repository(UsersRepository)),
|
||||
) -> schemas.UserGroup:
|
||||
"""
|
||||
Get a user group.
|
||||
@@ -104,15 +95,11 @@ async def get_user_group(
|
||||
return user_group
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{user_group_id}",
|
||||
response_model=schemas.UserGroup,
|
||||
dependencies=[Depends(has_privilege("Group.Modify"))]
|
||||
)
|
||||
@router.put("/{user_group_id}", response_model=schemas.UserGroup, dependencies=[Depends(has_privilege("Group.Modify"))])
|
||||
async def update_user_group(
|
||||
user_group_id: UUID,
|
||||
user_group_update: schemas.UserGroupUpdate,
|
||||
users_repo: UsersRepository = Depends(get_repository(UsersRepository))
|
||||
user_group_id: UUID,
|
||||
user_group_update: schemas.UserGroupUpdate,
|
||||
users_repo: UsersRepository = Depends(get_repository(UsersRepository)),
|
||||
) -> schemas.UserGroup:
|
||||
"""
|
||||
Update a user group.
|
||||
@@ -130,14 +117,12 @@ async def update_user_group(
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{user_group_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(has_privilege("Group.Allocate"))]
|
||||
"/{user_group_id}", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(has_privilege("Group.Allocate"))]
|
||||
)
|
||||
async def delete_user_group(
|
||||
user_group_id: UUID,
|
||||
users_repo: UsersRepository = Depends(get_repository(UsersRepository)),
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
|
||||
user_group_id: UUID,
|
||||
users_repo: UsersRepository = Depends(get_repository(UsersRepository)),
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
|
||||
) -> None:
|
||||
"""
|
||||
Delete a user group.
|
||||
@@ -159,13 +144,10 @@ async def delete_user_group(
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{user_group_id}/members",
|
||||
response_model=List[schemas.User],
|
||||
dependencies=[Depends(has_privilege("Group.Audit"))]
|
||||
"/{user_group_id}/members", response_model=List[schemas.User], dependencies=[Depends(has_privilege("Group.Audit"))]
|
||||
)
|
||||
async def get_user_group_members(
|
||||
user_group_id: UUID,
|
||||
users_repo: UsersRepository = Depends(get_repository(UsersRepository))
|
||||
user_group_id: UUID, users_repo: UsersRepository = Depends(get_repository(UsersRepository))
|
||||
) -> List[schemas.User]:
|
||||
"""
|
||||
Get all user group members.
|
||||
@@ -179,12 +161,10 @@ async def get_user_group_members(
|
||||
@router.put(
|
||||
"/{user_group_id}/members/{user_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(has_privilege("Group.Modify"))]
|
||||
dependencies=[Depends(has_privilege("Group.Modify"))],
|
||||
)
|
||||
async def add_member_to_group(
|
||||
user_group_id: UUID,
|
||||
user_id: UUID,
|
||||
users_repo: UsersRepository = Depends(get_repository(UsersRepository))
|
||||
user_group_id: UUID, user_id: UUID, users_repo: UsersRepository = Depends(get_repository(UsersRepository))
|
||||
) -> None:
|
||||
"""
|
||||
Add member to a user group.
|
||||
@@ -209,7 +189,7 @@ async def add_member_to_group(
|
||||
@router.delete(
|
||||
"/{user_group_id}/members/{user_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(has_privilege("Group.Modify"))]
|
||||
dependencies=[Depends(has_privilege("Group.Modify"))],
|
||||
)
|
||||
async def remove_member_from_group(
|
||||
user_group_id: UUID,
|
||||
|
||||
@@ -31,7 +31,13 @@ from typing import List, Optional
|
||||
from gns3server import schemas
|
||||
from gns3server.config import Config
|
||||
from gns3server.compute.qemu import Qemu
|
||||
from gns3server.utils.images import InvalidImageError, write_image, read_image_info, default_images_directory, get_builtin_disks
|
||||
from gns3server.utils.images import (
|
||||
InvalidImageError,
|
||||
write_image,
|
||||
read_image_info,
|
||||
default_images_directory,
|
||||
get_builtin_disks,
|
||||
)
|
||||
from gns3server.db.repositories.images import ImagesRepository
|
||||
from gns3server.db.repositories.templates import TemplatesRepository
|
||||
from gns3server.db.repositories.rbac import RbacRepository
|
||||
@@ -40,7 +46,7 @@ from gns3server.controller.controller_error import (
|
||||
ControllerError,
|
||||
ControllerNotFoundError,
|
||||
ControllerForbiddenError,
|
||||
ControllerBadRequestError
|
||||
ControllerBadRequestError,
|
||||
)
|
||||
|
||||
from .dependencies.authentication import get_current_active_user
|
||||
@@ -51,17 +57,17 @@ log = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post(
|
||||
"/qemu/{image_path:path}",
|
||||
response_model=schemas.Image,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
dependencies=[Depends(has_privilege("Image.Allocate"))]
|
||||
dependencies=[Depends(has_privilege("Image.Allocate"))],
|
||||
)
|
||||
async def create_qemu_image(
|
||||
image_path: str,
|
||||
image_data: schemas.QemuDiskImageCreate,
|
||||
images_repo: ImagesRepository = Depends(get_repository(ImagesRepository)),
|
||||
|
||||
image_path: str,
|
||||
image_data: schemas.QemuDiskImageCreate,
|
||||
images_repo: ImagesRepository = Depends(get_repository(ImagesRepository)),
|
||||
) -> schemas.Image:
|
||||
"""
|
||||
Create a new blank Qemu image.
|
||||
@@ -104,14 +110,11 @@ async def create_qemu_image(
|
||||
else:
|
||||
return await images_repo.add_image(**image_info)
|
||||
|
||||
@router.get(
|
||||
"",
|
||||
response_model=List[schemas.Image],
|
||||
dependencies=[Depends(has_privilege("Image.Audit"))]
|
||||
)
|
||||
|
||||
@router.get("", response_model=List[schemas.Image], dependencies=[Depends(has_privilege("Image.Audit"))])
|
||||
async def get_images(
|
||||
images_repo: ImagesRepository = Depends(get_repository(ImagesRepository)),
|
||||
image_type: Optional[schemas.ImageType] = None
|
||||
images_repo: ImagesRepository = Depends(get_repository(ImagesRepository)),
|
||||
image_type: Optional[schemas.ImageType] = None,
|
||||
) -> List[schemas.Image]:
|
||||
"""
|
||||
Return all images.
|
||||
@@ -126,16 +129,16 @@ async def get_images(
|
||||
"/upload/{image_path:path}",
|
||||
response_model=schemas.Image,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
dependencies=[Depends(has_privilege("Image.Allocate"))]
|
||||
dependencies=[Depends(has_privilege("Image.Allocate"))],
|
||||
)
|
||||
async def upload_image(
|
||||
image_path: str,
|
||||
request: Request,
|
||||
images_repo: ImagesRepository = Depends(get_repository(ImagesRepository)),
|
||||
templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository)),
|
||||
current_user: schemas.User = Depends(get_current_active_user),
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
|
||||
install_appliances: Optional[bool] = False,
|
||||
image_path: str,
|
||||
request: Request,
|
||||
images_repo: ImagesRepository = Depends(get_repository(ImagesRepository)),
|
||||
templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository)),
|
||||
current_user: schemas.User = Depends(get_current_active_user),
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
|
||||
install_appliances: Optional[bool] = False,
|
||||
) -> schemas.Image:
|
||||
"""
|
||||
Upload an image.
|
||||
@@ -161,9 +164,7 @@ async def upload_image(
|
||||
duplicate = await images_repo.get_image_by_checksum(checksum_header, check_dir)
|
||||
if duplicate:
|
||||
location = f" in '{check_dir}'" if check_dir else ""
|
||||
raise ControllerError(
|
||||
f"Image '{duplicate.filename}' with the same checksum already exists{location}"
|
||||
)
|
||||
raise ControllerError(f"Image '{duplicate.filename}' with the same checksum already exists{location}")
|
||||
|
||||
try:
|
||||
allow_raw_image = Config.instance().settings.Server.allow_raw_images
|
||||
@@ -180,19 +181,17 @@ async def upload_image(
|
||||
templates_repo,
|
||||
rbac_repo,
|
||||
current_user,
|
||||
os.path.dirname(image.path)
|
||||
os.path.dirname(image.path),
|
||||
)
|
||||
|
||||
return image
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/prune",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(has_privilege("Image.Allocate"))]
|
||||
"/prune", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(has_privilege("Image.Allocate"))]
|
||||
)
|
||||
async def prune_images(
|
||||
images_repo: ImagesRepository = Depends(get_repository(ImagesRepository)),
|
||||
images_repo: ImagesRepository = Depends(get_repository(ImagesRepository)),
|
||||
) -> None:
|
||||
"""
|
||||
Prune images not attached to any template.
|
||||
@@ -209,14 +208,10 @@ async def prune_images(
|
||||
await images_repo.prune_images(list(skip_images) + list(referenced_filenames))
|
||||
|
||||
|
||||
@router.post(
|
||||
"/install",
|
||||
status_code=status.HTTP_200_OK,
|
||||
dependencies=[Depends(has_privilege("Image.Allocate"))]
|
||||
)
|
||||
@router.post("/install", status_code=status.HTTP_200_OK, dependencies=[Depends(has_privilege("Image.Allocate"))])
|
||||
async def install_images(
|
||||
images_repo: ImagesRepository = Depends(get_repository(ImagesRepository)),
|
||||
templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository))
|
||||
images_repo: ImagesRepository = Depends(get_repository(ImagesRepository)),
|
||||
templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository)),
|
||||
) -> dict:
|
||||
"""
|
||||
Attempt to automatically create templates based on image checksums.
|
||||
@@ -239,19 +234,15 @@ async def install_images(
|
||||
if templates:
|
||||
# the image is already used by a template
|
||||
log.warning(f"Image '{image.path}' is used by one or more templates")
|
||||
skipped.append({
|
||||
"name": image.filename,
|
||||
"reason": "image is already used by one or more templates",
|
||||
})
|
||||
skipped.append(
|
||||
{
|
||||
"name": image.filename,
|
||||
"reason": "image is already used by one or more templates",
|
||||
}
|
||||
)
|
||||
continue
|
||||
results = await Controller.instance().appliance_manager.install_appliances_from_image(
|
||||
image.path,
|
||||
image.checksum,
|
||||
images_repo,
|
||||
templates_repo,
|
||||
None,
|
||||
None,
|
||||
os.path.dirname(image.path)
|
||||
image.path, image.checksum, images_repo, templates_repo, None, None, os.path.dirname(image.path)
|
||||
)
|
||||
for result in results:
|
||||
if result.get("status") == "created":
|
||||
@@ -261,14 +252,10 @@ async def install_images(
|
||||
return {"created": created, "skipped": skipped}
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{image_path:path}",
|
||||
response_model=schemas.Image,
|
||||
dependencies=[Depends(has_privilege("Image.Audit"))]
|
||||
)
|
||||
@router.get("/{image_path:path}", response_model=schemas.Image, dependencies=[Depends(has_privilege("Image.Audit"))])
|
||||
async def get_image(
|
||||
image_path: str,
|
||||
images_repo: ImagesRepository = Depends(get_repository(ImagesRepository)),
|
||||
image_path: str,
|
||||
images_repo: ImagesRepository = Depends(get_repository(ImagesRepository)),
|
||||
) -> schemas.Image:
|
||||
"""
|
||||
Return an image.
|
||||
@@ -286,11 +273,11 @@ async def get_image(
|
||||
@router.delete(
|
||||
"/{image_path:path}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(has_privilege("Image.Allocate"))]
|
||||
dependencies=[Depends(has_privilege("Image.Allocate"))],
|
||||
)
|
||||
async def delete_image(
|
||||
image_path: str,
|
||||
images_repo: ImagesRepository = Depends(get_repository(ImagesRepository)),
|
||||
image_path: str,
|
||||
images_repo: ImagesRepository = Depends(get_repository(ImagesRepository)),
|
||||
) -> None:
|
||||
"""
|
||||
Delete an image.
|
||||
@@ -303,8 +290,9 @@ async def delete_image(
|
||||
try:
|
||||
image = await images_repo.get_image(image_path)
|
||||
except MultipleResultsFound:
|
||||
raise ControllerBadRequestError(f"Image '{image_path}' matches multiple images. "
|
||||
f"Please include the absolute path of the image")
|
||||
raise ControllerBadRequestError(
|
||||
f"Image '{image_path}' matches multiple images. Please include the absolute path of the image"
|
||||
)
|
||||
|
||||
if not image:
|
||||
raise ControllerNotFoundError(f"Image '{image_path}' not found")
|
||||
|
||||
@@ -65,7 +65,7 @@ async def dep_link(project_id: UUID, link_id: UUID) -> Link:
|
||||
"",
|
||||
response_model=List[schemas.Link],
|
||||
response_model_exclude_unset=True,
|
||||
dependencies=[Depends(has_privilege("Link.Audit"))]
|
||||
dependencies=[Depends(has_privilege("Link.Audit"))],
|
||||
)
|
||||
async def get_links(project_id: UUID) -> List[schemas.Link]:
|
||||
"""
|
||||
@@ -89,7 +89,7 @@ async def get_links(project_id: UUID) -> List[schemas.Link]:
|
||||
404: {"model": schemas.ErrorMessage, "description": "Could not find project"},
|
||||
409: {"model": schemas.ErrorMessage, "description": "Could not create link"},
|
||||
},
|
||||
dependencies=[Depends(has_privilege("Link.Allocate"))]
|
||||
dependencies=[Depends(has_privilege("Link.Allocate"))],
|
||||
)
|
||||
async def create_link(project_id: UUID, link_data: schemas.LinkCreate) -> schemas.Link:
|
||||
"""
|
||||
@@ -123,10 +123,7 @@ async def create_link(project_id: UUID, link_data: schemas.LinkCreate) -> schema
|
||||
return link.asdict()
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{link_id}/available_filters",
|
||||
dependencies=[Depends(has_privilege("Link.Audit"))]
|
||||
)
|
||||
@router.get("/{link_id}/available_filters", dependencies=[Depends(has_privilege("Link.Audit"))])
|
||||
async def get_filters(link: Link = Depends(dep_link)) -> List[dict]:
|
||||
"""
|
||||
Return all filters available for a given link.
|
||||
@@ -141,7 +138,7 @@ async def get_filters(link: Link = Depends(dep_link)) -> List[dict]:
|
||||
"/{link_id}",
|
||||
response_model=schemas.Link,
|
||||
response_model_exclude_unset=True,
|
||||
dependencies=[Depends(has_privilege("Link.Audit"))]
|
||||
dependencies=[Depends(has_privilege("Link.Audit"))],
|
||||
)
|
||||
async def get_link(link: Link = Depends(dep_link)) -> schemas.Link:
|
||||
"""
|
||||
@@ -157,7 +154,7 @@ async def get_link(link: Link = Depends(dep_link)) -> schemas.Link:
|
||||
"/{link_id}",
|
||||
response_model=schemas.Link,
|
||||
response_model_exclude_unset=True,
|
||||
dependencies=[Depends(has_privilege("Link.Modify"))]
|
||||
dependencies=[Depends(has_privilege("Link.Modify"))],
|
||||
)
|
||||
async def update_link(link_data: schemas.LinkUpdate, link: Link = Depends(dep_link)) -> schemas.Link:
|
||||
"""
|
||||
@@ -181,14 +178,12 @@ async def update_link(link_data: schemas.LinkUpdate, link: Link = Depends(dep_li
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{link_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(has_privilege("Link.Allocate"))]
|
||||
"/{link_id}", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(has_privilege("Link.Allocate"))]
|
||||
)
|
||||
async def delete_link(
|
||||
project_id: UUID,
|
||||
link: Link = Depends(dep_link),
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
|
||||
project_id: UUID,
|
||||
link: Link = Depends(dep_link),
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
|
||||
) -> None:
|
||||
"""
|
||||
Delete a link.
|
||||
@@ -201,11 +196,7 @@ async def delete_link(
|
||||
await rbac_repo.delete_all_ace_starting_with_path(f"/links/{link.id}")
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{link_id}/reset",
|
||||
response_model=schemas.Link,
|
||||
dependencies=[Depends(has_privilege("Link.Modify"))]
|
||||
)
|
||||
@router.post("/{link_id}/reset", response_model=schemas.Link, dependencies=[Depends(has_privilege("Link.Modify"))])
|
||||
async def reset_link(link: Link = Depends(dep_link)) -> schemas.Link:
|
||||
"""
|
||||
Reset a link.
|
||||
@@ -221,12 +212,10 @@ async def reset_link(link: Link = Depends(dep_link)) -> schemas.Link:
|
||||
"/{link_id}/capture/start",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.Link,
|
||||
dependencies=[Depends(has_privilege("Link.Capture"))]
|
||||
dependencies=[Depends(has_privilege("Link.Capture"))],
|
||||
)
|
||||
async def start_capture(
|
||||
capture_data: schemas.LinkCapture,
|
||||
http_request: Request,
|
||||
link: Link = Depends(dep_link)
|
||||
capture_data: schemas.LinkCapture, http_request: Request, link: Link = Depends(dep_link)
|
||||
) -> schemas.Link:
|
||||
"""
|
||||
Start packet capture on the link.
|
||||
@@ -242,7 +231,7 @@ async def start_capture(
|
||||
data_link_type=capture_data.data_link_type,
|
||||
capture_file_name=capture_data.capture_file_name,
|
||||
wireshark=capture_data.wireshark,
|
||||
jwt_token=jwt_token
|
||||
jwt_token=jwt_token,
|
||||
)
|
||||
return link.asdict()
|
||||
|
||||
@@ -250,7 +239,7 @@ async def start_capture(
|
||||
@router.post(
|
||||
"/{link_id}/capture/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(has_privilege("Link.Capture"))]
|
||||
dependencies=[Depends(has_privilege("Link.Capture"))],
|
||||
)
|
||||
async def stop_capture(link: Link = Depends(dep_link)) -> None:
|
||||
"""
|
||||
@@ -265,12 +254,9 @@ async def stop_capture(link: Link = Depends(dep_link)) -> None:
|
||||
@router.post(
|
||||
"/{link_id}/capture/wireshark/restart",
|
||||
status_code=status.HTTP_200_OK,
|
||||
dependencies=[Depends(has_privilege("Link.Capture"))]
|
||||
dependencies=[Depends(has_privilege("Link.Capture"))],
|
||||
)
|
||||
async def restart_wireshark(
|
||||
http_request: Request,
|
||||
link: Link = Depends(dep_link)
|
||||
) -> dict:
|
||||
async def restart_wireshark(http_request: Request, link: Link = Depends(dep_link)) -> dict:
|
||||
"""
|
||||
Restart Wireshark window without stopping the capture.
|
||||
|
||||
@@ -289,10 +275,7 @@ async def restart_wireshark(
|
||||
return {"status": "restarted"}
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{link_id}/capture/stream",
|
||||
dependencies=[Depends(has_privilege("Link.Capture"))]
|
||||
)
|
||||
@router.get("/{link_id}/capture/stream", dependencies=[Depends(has_privilege("Link.Capture"))])
|
||||
async def stream_pcap(request: Request, link: Link = Depends(dep_link)) -> StreamingResponse:
|
||||
"""
|
||||
Stream the PCAP capture file from compute.
|
||||
@@ -318,13 +301,13 @@ async def stream_pcap(request: Request, link: Link = Depends(dep_link)) -> Strea
|
||||
try:
|
||||
ssl_context = Controller.instance().ssl_context()
|
||||
async with HTTPClient.request(
|
||||
request.method,
|
||||
pcap_streaming_url,
|
||||
user=compute.user,
|
||||
password=compute.password,
|
||||
ssl_context=ssl_context,
|
||||
timeout=None,
|
||||
data=body
|
||||
request.method,
|
||||
pcap_streaming_url,
|
||||
user=compute.user,
|
||||
password=compute.password,
|
||||
ssl_context=ssl_context,
|
||||
timeout=None,
|
||||
data=body,
|
||||
) as response:
|
||||
async for data in response.content.iter_any():
|
||||
if not data:
|
||||
@@ -337,9 +320,7 @@ async def stream_pcap(request: Request, link: Link = Depends(dep_link)) -> Strea
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{link_id}/capture/file",
|
||||
dependencies=[Depends(has_privilege("Link.Capture"))],
|
||||
response_class=FileResponse
|
||||
"/{link_id}/capture/file", dependencies=[Depends(has_privilege("Link.Capture"))], response_class=FileResponse
|
||||
)
|
||||
async def download_capture_file(link: Link = Depends(dep_link)):
|
||||
"""
|
||||
@@ -359,7 +340,7 @@ async def download_capture_file(link: Link = Depends(dep_link)):
|
||||
return FileResponse(
|
||||
path=link.capture_file_path,
|
||||
filename=os.path.basename(link.capture_file_path),
|
||||
media_type="application/vnd.tcpdump.pcap"
|
||||
media_type="application/vnd.tcpdump.pcap",
|
||||
)
|
||||
|
||||
|
||||
@@ -368,7 +349,7 @@ async def web_wireshark_websocket(
|
||||
websocket: WebSocket,
|
||||
link_id: str,
|
||||
project_id: str,
|
||||
current_user: schemas.User = Depends(has_privilege_on_websocket("Link.Capture"))
|
||||
current_user: schemas.User = Depends(has_privilege_on_websocket("Link.Capture")),
|
||||
):
|
||||
"""
|
||||
WebSocket proxy endpoint for xpra container (Web Wireshark).
|
||||
@@ -424,10 +405,7 @@ async def web_wireshark_websocket(
|
||||
pass
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{link_id}/markers",
|
||||
dependencies=[Depends(has_privilege("Link.Audit"))]
|
||||
)
|
||||
@router.get("/{link_id}/markers", dependencies=[Depends(has_privilege("Link.Audit"))])
|
||||
async def get_markers(link: Link = Depends(dep_link)) -> dict:
|
||||
"""
|
||||
Return all traffic-insight markers configured on this link.
|
||||
@@ -439,14 +417,9 @@ async def get_markers(link: Link = Depends(dep_link)) -> dict:
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{link_id}/markers",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
dependencies=[Depends(has_privilege("Link.Modify"))]
|
||||
"/{link_id}/markers", status_code=status.HTTP_201_CREATED, dependencies=[Depends(has_privilege("Link.Modify"))]
|
||||
)
|
||||
async def create_marker(
|
||||
marker_data: schemas.MarkerCreate,
|
||||
link: Link = Depends(dep_link)
|
||||
) -> dict:
|
||||
async def create_marker(marker_data: schemas.MarkerCreate, link: Link = Depends(dep_link)) -> dict:
|
||||
"""
|
||||
Attach a traffic-insight marker to the link.
|
||||
On BPF match uBridge emits MARK signals and appends packets to a pcap.
|
||||
@@ -477,12 +450,9 @@ async def create_marker(
|
||||
@router.delete(
|
||||
"/{link_id}/markers/{marker_name}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(has_privilege("Link.Modify"))]
|
||||
dependencies=[Depends(has_privilege("Link.Modify"))],
|
||||
)
|
||||
async def delete_marker(
|
||||
marker_name: str,
|
||||
link: Link = Depends(dep_link)
|
||||
) -> None:
|
||||
async def delete_marker(marker_name: str, link: Link = Depends(dep_link)) -> None:
|
||||
"""
|
||||
Remove a traffic-insight marker from the link.
|
||||
|
||||
@@ -492,15 +462,8 @@ async def delete_marker(
|
||||
await link.stop_marker(marker_name)
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{link_id}/markers/{marker_name}",
|
||||
dependencies=[Depends(has_privilege("Link.Modify"))]
|
||||
)
|
||||
async def update_marker(
|
||||
marker_name: str,
|
||||
marker_data: schemas.MarkerUpdate,
|
||||
link: Link = Depends(dep_link)
|
||||
) -> dict:
|
||||
@router.put("/{link_id}/markers/{marker_name}", dependencies=[Depends(has_privilege("Link.Modify"))])
|
||||
async def update_marker(marker_name: str, marker_data: schemas.MarkerUpdate, link: Link = Depends(dep_link)) -> dict:
|
||||
"""
|
||||
Update a traffic-insight marker (change BPF, tag, or enabled).
|
||||
|
||||
@@ -522,7 +485,7 @@ async def update_marker(
|
||||
@router.get(
|
||||
"/{link_id}/iface",
|
||||
response_model=Union[schemas.UDPPortInfo, schemas.EthernetPortInfo],
|
||||
dependencies=[Depends(has_privilege("Link.Audit"))]
|
||||
dependencies=[Depends(has_privilege("Link.Audit"))],
|
||||
)
|
||||
async def get_iface(link: Link = Depends(dep_link)) -> Union[schemas.UDPPortInfo, schemas.EthernetPortInfo]:
|
||||
"""
|
||||
@@ -544,7 +507,7 @@ async def get_iface(link: Link = Depends(dep_link)) -> Union[schemas.UDPPortInfo
|
||||
if "ports_mapping" not in response.json:
|
||||
continue
|
||||
ports_mapping = response.json["ports_mapping"]
|
||||
|
||||
|
||||
for port in ports_mapping:
|
||||
port_num = port.get("port_number")
|
||||
|
||||
@@ -556,7 +519,7 @@ async def get_iface(link: Link = Depends(dep_link)) -> Union[schemas.UDPPortInfo
|
||||
"type": f"{port_type}",
|
||||
"lport": port["lport"],
|
||||
"rhost": port["rhost"],
|
||||
"rport": port["rport"]
|
||||
"rport": port["rport"],
|
||||
}
|
||||
else:
|
||||
ifaces_info = {
|
||||
@@ -564,7 +527,7 @@ async def get_iface(link: Link = Depends(dep_link)) -> Union[schemas.UDPPortInfo
|
||||
"type": f"{port_type}",
|
||||
"interface": port["interface"],
|
||||
}
|
||||
|
||||
|
||||
if not ifaces_info:
|
||||
raise ControllerError("Link not connected to Cloud/NAT")
|
||||
return ifaces_info
|
||||
@@ -60,14 +60,12 @@ def _filter_api_key_from_config(config: dict) -> dict:
|
||||
# User LLM Model Configuration Endpoints
|
||||
# ============================================================================
|
||||
|
||||
@router.get(
|
||||
"/users/{user_id}/llm-model-configs",
|
||||
response_model=schemas.LLMModelConfigInheritedResponse
|
||||
)
|
||||
|
||||
@router.get("/users/{user_id}/llm-model-configs", response_model=schemas.LLMModelConfigInheritedResponse)
|
||||
async def get_user_llm_model_configs(
|
||||
user_id: UUID,
|
||||
current_user: schemas.User = Depends(has_privilege("LLMConfig.Audit")),
|
||||
llm_repo: LLMModelConfigsRepository = Depends(get_repository(LLMModelConfigsRepository))
|
||||
user_id: UUID,
|
||||
current_user: schemas.User = Depends(has_privilege("LLMConfig.Audit")),
|
||||
llm_repo: LLMModelConfigsRepository = Depends(get_repository(LLMModelConfigsRepository)),
|
||||
) -> schemas.LLMModelConfigInheritedResponse:
|
||||
"""
|
||||
Get user's effective LLM model configurations (own + inherited from groups).
|
||||
@@ -77,31 +75,25 @@ async def get_user_llm_model_configs(
|
||||
|
||||
try:
|
||||
result = await llm_repo.get_user_effective_configs(
|
||||
user_id,
|
||||
current_user_id=current_user.user_id,
|
||||
current_user_is_superadmin=current_user.is_superadmin
|
||||
user_id, current_user_id=current_user.user_id, current_user_is_superadmin=current_user.is_superadmin
|
||||
)
|
||||
return schemas.LLMModelConfigInheritedResponse(
|
||||
configs=result["configs"],
|
||||
default_config=result.get("default_config"),
|
||||
total=len(result["configs"])
|
||||
configs=result["configs"], default_config=result.get("default_config"), total=len(result["configs"])
|
||||
)
|
||||
except Exception as e:
|
||||
log.error(f"Failed to retrieve user LLM model configs: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to retrieve LLM model configurations"
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to retrieve LLM model configurations"
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/users/{user_id}/llm-model-configs/own",
|
||||
response_model=List[schemas.LLMModelConfigResponse],
|
||||
dependencies=[Depends(has_privilege("LLMConfig.Audit"))]
|
||||
dependencies=[Depends(has_privilege("LLMConfig.Audit"))],
|
||||
)
|
||||
async def get_user_own_llm_model_configs(
|
||||
user_id: UUID,
|
||||
llm_repo: LLMModelConfigsRepository = Depends(get_repository(LLMModelConfigsRepository))
|
||||
user_id: UUID, llm_repo: LLMModelConfigsRepository = Depends(get_repository(LLMModelConfigsRepository))
|
||||
) -> List[schemas.LLMModelConfigResponse]:
|
||||
"""
|
||||
Get user's own LLM model configurations (excluding inherited ones).
|
||||
@@ -122,26 +114,24 @@ async def get_user_own_llm_model_configs(
|
||||
is_default=config.is_default,
|
||||
version=config.version,
|
||||
created_at=config.created_at,
|
||||
updated_at=config.updated_at
|
||||
updated_at=config.updated_at,
|
||||
)
|
||||
for config in configs
|
||||
]
|
||||
except Exception as e:
|
||||
log.error(f"Failed to retrieve user's own LLM model configs: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to retrieve LLM model configurations"
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to retrieve LLM model configurations"
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/users/{user_id}/llm-model-configs/default",
|
||||
response_model=schemas.LLMModelConfigResponse,
|
||||
dependencies=[Depends(has_privilege("LLMConfig.Audit"))]
|
||||
dependencies=[Depends(has_privilege("LLMConfig.Audit"))],
|
||||
)
|
||||
async def get_user_default_llm_model_config(
|
||||
user_id: UUID,
|
||||
llm_repo: LLMModelConfigsRepository = Depends(get_repository(LLMModelConfigsRepository))
|
||||
user_id: UUID, llm_repo: LLMModelConfigsRepository = Depends(get_repository(LLMModelConfigsRepository))
|
||||
) -> schemas.LLMModelConfigResponse:
|
||||
"""
|
||||
Get user's default LLM model configuration.
|
||||
@@ -154,7 +144,7 @@ async def get_user_default_llm_model_config(
|
||||
if not config:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"No default LLM model configuration found for user '{user_id}'"
|
||||
detail=f"No default LLM model configuration found for user '{user_id}'",
|
||||
)
|
||||
|
||||
return schemas.LLMModelConfigResponse(
|
||||
@@ -167,15 +157,14 @@ async def get_user_default_llm_model_config(
|
||||
is_default=config.is_default,
|
||||
version=config.version,
|
||||
created_at=config.created_at,
|
||||
updated_at=config.updated_at
|
||||
updated_at=config.updated_at,
|
||||
)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
log.error(f"Failed to retrieve user's default LLM model config: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to retrieve LLM model configuration"
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to retrieve LLM model configuration"
|
||||
)
|
||||
|
||||
|
||||
@@ -183,13 +172,13 @@ async def get_user_default_llm_model_config(
|
||||
"/users/{user_id}/llm-model-configs",
|
||||
response_model=schemas.LLMModelConfigResponse,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
dependencies=[Depends(has_privilege("LLMConfig.Modify"))]
|
||||
dependencies=[Depends(has_privilege("LLMConfig.Modify"))],
|
||||
)
|
||||
async def create_user_llm_model_config(
|
||||
user_id: UUID,
|
||||
config_create: schemas.LLMModelConfigCreate,
|
||||
users_repo: UsersRepository = Depends(get_repository(UsersRepository)),
|
||||
llm_repo: LLMModelConfigsRepository = Depends(get_repository(LLMModelConfigsRepository))
|
||||
user_id: UUID,
|
||||
config_create: schemas.LLMModelConfigCreate,
|
||||
users_repo: UsersRepository = Depends(get_repository(UsersRepository)),
|
||||
llm_repo: LLMModelConfigsRepository = Depends(get_repository(LLMModelConfigsRepository)),
|
||||
) -> schemas.LLMModelConfigResponse:
|
||||
"""
|
||||
Create a new LLM model configuration for a user.
|
||||
@@ -206,7 +195,7 @@ async def create_user_llm_model_config(
|
||||
raise ControllerNotFoundError(f"User '{user_id}' not found")
|
||||
|
||||
# Validate context_limit is provided
|
||||
if not hasattr(config_create, 'context_limit') or config_create.context_limit is None:
|
||||
if not hasattr(config_create, "context_limit") or config_create.context_limit is None:
|
||||
raise ControllerBadRequestError(
|
||||
"context_limit is required (unit: K tokens, e.g., 128 = 128K = 128,000 tokens). "
|
||||
"Please check your model provider's documentation for the current context window size "
|
||||
@@ -217,11 +206,7 @@ async def create_user_llm_model_config(
|
||||
# Extract config fields (excluding table-level fields)
|
||||
config_fields = config_create.model_dump(exclude={"name", "model_type", "is_default"})
|
||||
new_config = await llm_repo.create_user_config(
|
||||
user_id,
|
||||
config_create.name,
|
||||
config_create.model_type,
|
||||
config_fields,
|
||||
is_default=config_create.is_default
|
||||
user_id, config_create.name, config_create.model_type, config_fields, is_default=config_create.is_default
|
||||
)
|
||||
|
||||
return schemas.LLMModelConfigResponse(
|
||||
@@ -234,28 +219,27 @@ async def create_user_llm_model_config(
|
||||
is_default=new_config.is_default,
|
||||
version=new_config.version,
|
||||
created_at=new_config.created_at,
|
||||
updated_at=new_config.updated_at
|
||||
updated_at=new_config.updated_at,
|
||||
)
|
||||
except ValueError as e:
|
||||
raise ControllerBadRequestError(str(e))
|
||||
except Exception as e:
|
||||
log.error(f"Failed to create LLM model config: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Failed to create LLM model configuration: {e}"
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to create LLM model configuration: {e}"
|
||||
)
|
||||
|
||||
|
||||
@router.put(
|
||||
"/users/{user_id}/llm-model-configs/{config_id}",
|
||||
response_model=schemas.LLMModelConfigResponse,
|
||||
dependencies=[Depends(has_privilege("LLMConfig.Modify"))]
|
||||
dependencies=[Depends(has_privilege("LLMConfig.Modify"))],
|
||||
)
|
||||
async def update_user_llm_model_config(
|
||||
user_id: UUID,
|
||||
config_id: UUID,
|
||||
config_update: schemas.LLMModelConfigUpdate,
|
||||
llm_repo: LLMModelConfigsRepository = Depends(get_repository(LLMModelConfigsRepository))
|
||||
user_id: UUID,
|
||||
config_id: UUID,
|
||||
config_update: schemas.LLMModelConfigUpdate,
|
||||
llm_repo: LLMModelConfigsRepository = Depends(get_repository(LLMModelConfigsRepository)),
|
||||
) -> schemas.LLMModelConfigResponse:
|
||||
"""
|
||||
Update a user's LLM model configuration.
|
||||
@@ -272,16 +256,12 @@ async def update_user_llm_model_config(
|
||||
expected_version = updates.pop("expected_version", None)
|
||||
|
||||
updated_config = await llm_repo.update_user_config(
|
||||
config_id,
|
||||
user_id,
|
||||
updates,
|
||||
expected_version=expected_version
|
||||
config_id, user_id, updates, expected_version=expected_version
|
||||
)
|
||||
|
||||
if not updated_config:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"LLM model configuration '{config_id}' not found"
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail=f"LLM model configuration '{config_id}' not found"
|
||||
)
|
||||
|
||||
return schemas.LLMModelConfigResponse(
|
||||
@@ -294,35 +274,31 @@ async def update_user_llm_model_config(
|
||||
is_default=updated_config.is_default,
|
||||
version=updated_config.version,
|
||||
created_at=updated_config.created_at,
|
||||
updated_at=updated_config.updated_at
|
||||
updated_at=updated_config.updated_at,
|
||||
)
|
||||
except HTTPException:
|
||||
raise
|
||||
except ValueError as e:
|
||||
# Handle optimistic lock errors
|
||||
if "Concurrent modification" in str(e):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail=str(e)
|
||||
)
|
||||
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=str(e))
|
||||
raise ControllerBadRequestError(str(e))
|
||||
except Exception as e:
|
||||
log.error(f"Failed to update LLM model config: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to update LLM model configuration"
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to update LLM model configuration"
|
||||
)
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/users/{user_id}/llm-model-configs/{config_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(has_privilege("LLMConfig.Modify"))]
|
||||
dependencies=[Depends(has_privilege("LLMConfig.Modify"))],
|
||||
)
|
||||
async def delete_user_llm_model_config(
|
||||
user_id: UUID,
|
||||
config_id: UUID,
|
||||
llm_repo: LLMModelConfigsRepository = Depends(get_repository(LLMModelConfigsRepository))
|
||||
user_id: UUID,
|
||||
config_id: UUID,
|
||||
llm_repo: LLMModelConfigsRepository = Depends(get_repository(LLMModelConfigsRepository)),
|
||||
) -> None:
|
||||
"""
|
||||
Delete a user's LLM model configuration.
|
||||
@@ -334,28 +310,26 @@ async def delete_user_llm_model_config(
|
||||
success = await llm_repo.delete_user_config(config_id, user_id)
|
||||
if not success:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"LLM model configuration '{config_id}' not found"
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail=f"LLM model configuration '{config_id}' not found"
|
||||
)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
log.error(f"Failed to delete LLM model config: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to delete LLM model configuration"
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to delete LLM model configuration"
|
||||
)
|
||||
|
||||
|
||||
@router.put(
|
||||
"/users/{user_id}/llm-model-configs/default/{config_id}",
|
||||
response_model=schemas.LLMModelConfigResponse,
|
||||
dependencies=[Depends(has_privilege("LLMConfig.Modify"))]
|
||||
dependencies=[Depends(has_privilege("LLMConfig.Modify"))],
|
||||
)
|
||||
async def set_user_default_llm_model_config(
|
||||
user_id: UUID,
|
||||
config_id: UUID,
|
||||
llm_repo: LLMModelConfigsRepository = Depends(get_repository(LLMModelConfigsRepository))
|
||||
user_id: UUID,
|
||||
config_id: UUID,
|
||||
llm_repo: LLMModelConfigsRepository = Depends(get_repository(LLMModelConfigsRepository)),
|
||||
) -> schemas.LLMModelConfigResponse:
|
||||
"""
|
||||
Set a user's default LLM model configuration.
|
||||
@@ -367,8 +341,7 @@ async def set_user_default_llm_model_config(
|
||||
success = await llm_repo.set_user_default_config(user_id, config_id)
|
||||
if not success:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"LLM model configuration '{config_id}' not found"
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail=f"LLM model configuration '{config_id}' not found"
|
||||
)
|
||||
|
||||
# Get the updated config
|
||||
@@ -383,15 +356,14 @@ async def set_user_default_llm_model_config(
|
||||
is_default=config.is_default,
|
||||
version=config.version,
|
||||
created_at=config.created_at,
|
||||
updated_at=config.updated_at
|
||||
updated_at=config.updated_at,
|
||||
)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
log.error(f"Failed to set default LLM model config: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to set default LLM model configuration"
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to set default LLM model configuration"
|
||||
)
|
||||
|
||||
|
||||
@@ -399,14 +371,14 @@ async def set_user_default_llm_model_config(
|
||||
# Group LLM Model Configuration Endpoints
|
||||
# ============================================================================
|
||||
|
||||
|
||||
@router.get(
|
||||
"/groups/{group_id}/llm-model-configs",
|
||||
response_model=schemas.LLMModelConfigListResponse,
|
||||
dependencies=[Depends(has_privilege("Group.Audit"))]
|
||||
dependencies=[Depends(has_privilege("Group.Audit"))],
|
||||
)
|
||||
async def get_group_llm_model_configs(
|
||||
group_id: UUID,
|
||||
llm_repo: LLMModelConfigsRepository = Depends(get_repository(LLMModelConfigsRepository))
|
||||
group_id: UUID, llm_repo: LLMModelConfigsRepository = Depends(get_repository(LLMModelConfigsRepository))
|
||||
) -> schemas.LLMModelConfigListResponse:
|
||||
"""
|
||||
Get all LLM model configurations for a user group.
|
||||
@@ -427,7 +399,7 @@ async def get_group_llm_model_configs(
|
||||
is_default=config.is_default,
|
||||
version=config.version,
|
||||
created_at=config.created_at,
|
||||
updated_at=config.updated_at
|
||||
updated_at=config.updated_at,
|
||||
)
|
||||
for config in configs
|
||||
]
|
||||
@@ -444,26 +416,22 @@ async def get_group_llm_model_configs(
|
||||
default_config = config_responses[0]
|
||||
|
||||
return schemas.LLMModelConfigListResponse(
|
||||
configs=config_responses,
|
||||
default_config=default_config,
|
||||
total=len(config_responses)
|
||||
configs=config_responses, default_config=default_config, total=len(config_responses)
|
||||
)
|
||||
except Exception as e:
|
||||
log.error(f"Failed to retrieve group LLM model configs: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to retrieve LLM model configurations"
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to retrieve LLM model configurations"
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/groups/{group_id}/llm-model-configs/default",
|
||||
response_model=schemas.LLMModelConfigResponse,
|
||||
dependencies=[Depends(has_privilege("Group.Audit"))]
|
||||
dependencies=[Depends(has_privilege("Group.Audit"))],
|
||||
)
|
||||
async def get_group_default_llm_model_config(
|
||||
group_id: UUID,
|
||||
llm_repo: LLMModelConfigsRepository = Depends(get_repository(LLMModelConfigsRepository))
|
||||
group_id: UUID, llm_repo: LLMModelConfigsRepository = Depends(get_repository(LLMModelConfigsRepository))
|
||||
) -> schemas.LLMModelConfigResponse:
|
||||
"""
|
||||
Get group's default LLM model configuration.
|
||||
@@ -476,7 +444,7 @@ async def get_group_default_llm_model_config(
|
||||
if not config:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"No default LLM model configuration found for group '{group_id}'"
|
||||
detail=f"No default LLM model configuration found for group '{group_id}'",
|
||||
)
|
||||
|
||||
return schemas.LLMModelConfigResponse(
|
||||
@@ -489,15 +457,14 @@ async def get_group_default_llm_model_config(
|
||||
is_default=config.is_default,
|
||||
version=config.version,
|
||||
created_at=config.created_at,
|
||||
updated_at=config.updated_at
|
||||
updated_at=config.updated_at,
|
||||
)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
log.error(f"Failed to retrieve group's default LLM model config: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to retrieve LLM model configuration"
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to retrieve LLM model configuration"
|
||||
)
|
||||
|
||||
|
||||
@@ -505,13 +472,13 @@ async def get_group_default_llm_model_config(
|
||||
"/groups/{group_id}/llm-model-configs",
|
||||
response_model=schemas.LLMModelConfigResponse,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
dependencies=[Depends(has_privilege("Group.Modify"))]
|
||||
dependencies=[Depends(has_privilege("Group.Modify"))],
|
||||
)
|
||||
async def create_group_llm_model_config(
|
||||
group_id: UUID,
|
||||
config_create: schemas.LLMModelConfigCreate,
|
||||
users_repo: UsersRepository = Depends(get_repository(UsersRepository)),
|
||||
llm_repo: LLMModelConfigsRepository = Depends(get_repository(LLMModelConfigsRepository))
|
||||
group_id: UUID,
|
||||
config_create: schemas.LLMModelConfigCreate,
|
||||
users_repo: UsersRepository = Depends(get_repository(UsersRepository)),
|
||||
llm_repo: LLMModelConfigsRepository = Depends(get_repository(LLMModelConfigsRepository)),
|
||||
) -> schemas.LLMModelConfigResponse:
|
||||
"""
|
||||
Create a new LLM model configuration for a user group.
|
||||
@@ -528,7 +495,7 @@ async def create_group_llm_model_config(
|
||||
raise ControllerNotFoundError(f"User group '{group_id}' not found")
|
||||
|
||||
# Validate context_limit is provided
|
||||
if not hasattr(config_create, 'context_limit') or config_create.context_limit is None:
|
||||
if not hasattr(config_create, "context_limit") or config_create.context_limit is None:
|
||||
raise ControllerBadRequestError(
|
||||
"context_limit is required (unit: K tokens, e.g., 128 = 128K = 128,000 tokens). "
|
||||
"Please check your model provider's documentation for the current context window size "
|
||||
@@ -539,11 +506,7 @@ async def create_group_llm_model_config(
|
||||
# Extract config fields (excluding table-level fields)
|
||||
config_fields = config_create.model_dump(exclude={"name", "model_type", "is_default"})
|
||||
new_config = await llm_repo.create_group_config(
|
||||
group_id,
|
||||
config_create.name,
|
||||
config_create.model_type,
|
||||
config_fields,
|
||||
is_default=config_create.is_default
|
||||
group_id, config_create.name, config_create.model_type, config_fields, is_default=config_create.is_default
|
||||
)
|
||||
|
||||
return schemas.LLMModelConfigResponse(
|
||||
@@ -556,28 +519,27 @@ async def create_group_llm_model_config(
|
||||
is_default=new_config.is_default,
|
||||
version=new_config.version,
|
||||
created_at=new_config.created_at,
|
||||
updated_at=new_config.updated_at
|
||||
updated_at=new_config.updated_at,
|
||||
)
|
||||
except ValueError as e:
|
||||
raise ControllerBadRequestError(str(e))
|
||||
except Exception as e:
|
||||
log.error(f"Failed to create LLM model config: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Failed to create LLM model configuration: {e}"
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to create LLM model configuration: {e}"
|
||||
)
|
||||
|
||||
|
||||
@router.put(
|
||||
"/groups/{group_id}/llm-model-configs/{config_id}",
|
||||
response_model=schemas.LLMModelConfigResponse,
|
||||
dependencies=[Depends(has_privilege("Group.Modify"))]
|
||||
dependencies=[Depends(has_privilege("Group.Modify"))],
|
||||
)
|
||||
async def update_group_llm_model_config(
|
||||
group_id: UUID,
|
||||
config_id: UUID,
|
||||
config_update: schemas.LLMModelConfigUpdate,
|
||||
llm_repo: LLMModelConfigsRepository = Depends(get_repository(LLMModelConfigsRepository))
|
||||
group_id: UUID,
|
||||
config_id: UUID,
|
||||
config_update: schemas.LLMModelConfigUpdate,
|
||||
llm_repo: LLMModelConfigsRepository = Depends(get_repository(LLMModelConfigsRepository)),
|
||||
) -> schemas.LLMModelConfigResponse:
|
||||
"""
|
||||
Update a group's LLM model configuration.
|
||||
@@ -594,16 +556,12 @@ async def update_group_llm_model_config(
|
||||
expected_version = updates.pop("expected_version", None)
|
||||
|
||||
updated_config = await llm_repo.update_group_config(
|
||||
config_id,
|
||||
group_id,
|
||||
updates,
|
||||
expected_version=expected_version
|
||||
config_id, group_id, updates, expected_version=expected_version
|
||||
)
|
||||
|
||||
if not updated_config:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"LLM model configuration '{config_id}' not found"
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail=f"LLM model configuration '{config_id}' not found"
|
||||
)
|
||||
|
||||
return schemas.LLMModelConfigResponse(
|
||||
@@ -616,35 +574,31 @@ async def update_group_llm_model_config(
|
||||
is_default=updated_config.is_default,
|
||||
version=updated_config.version,
|
||||
created_at=updated_config.created_at,
|
||||
updated_at=updated_config.updated_at
|
||||
updated_at=updated_config.updated_at,
|
||||
)
|
||||
except HTTPException:
|
||||
raise
|
||||
except ValueError as e:
|
||||
# Handle optimistic lock errors
|
||||
if "Concurrent modification" in str(e):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail=str(e)
|
||||
)
|
||||
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=str(e))
|
||||
raise ControllerBadRequestError(str(e))
|
||||
except Exception as e:
|
||||
log.error(f"Failed to update LLM model config: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to update LLM model configuration"
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to update LLM model configuration"
|
||||
)
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/groups/{group_id}/llm-model-configs/{config_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(has_privilege("Group.Modify"))]
|
||||
dependencies=[Depends(has_privilege("Group.Modify"))],
|
||||
)
|
||||
async def delete_group_llm_model_config(
|
||||
group_id: UUID,
|
||||
config_id: UUID,
|
||||
llm_repo: LLMModelConfigsRepository = Depends(get_repository(LLMModelConfigsRepository))
|
||||
group_id: UUID,
|
||||
config_id: UUID,
|
||||
llm_repo: LLMModelConfigsRepository = Depends(get_repository(LLMModelConfigsRepository)),
|
||||
) -> None:
|
||||
"""
|
||||
Delete a group's LLM model configuration.
|
||||
@@ -656,28 +610,26 @@ async def delete_group_llm_model_config(
|
||||
success = await llm_repo.delete_group_config(config_id, group_id)
|
||||
if not success:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"LLM model configuration '{config_id}' not found"
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail=f"LLM model configuration '{config_id}' not found"
|
||||
)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
log.error(f"Failed to delete LLM model config: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to delete LLM model configuration"
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to delete LLM model configuration"
|
||||
)
|
||||
|
||||
|
||||
@router.put(
|
||||
"/groups/{group_id}/llm-model-configs/default/{config_id}",
|
||||
response_model=schemas.LLMModelConfigResponse,
|
||||
dependencies=[Depends(has_privilege("Group.Modify"))]
|
||||
dependencies=[Depends(has_privilege("Group.Modify"))],
|
||||
)
|
||||
async def set_group_default_llm_model_config(
|
||||
group_id: UUID,
|
||||
config_id: UUID,
|
||||
llm_repo: LLMModelConfigsRepository = Depends(get_repository(LLMModelConfigsRepository))
|
||||
group_id: UUID,
|
||||
config_id: UUID,
|
||||
llm_repo: LLMModelConfigsRepository = Depends(get_repository(LLMModelConfigsRepository)),
|
||||
) -> schemas.LLMModelConfigResponse:
|
||||
"""
|
||||
Set a group's default LLM model configuration.
|
||||
@@ -689,8 +641,7 @@ async def set_group_default_llm_model_config(
|
||||
success = await llm_repo.set_group_default_config(group_id, config_id)
|
||||
if not success:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"LLM model configuration '{config_id}' not found"
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail=f"LLM model configuration '{config_id}' not found"
|
||||
)
|
||||
|
||||
# Get the updated config
|
||||
@@ -705,13 +656,12 @@ async def set_group_default_llm_model_config(
|
||||
is_default=config.is_default,
|
||||
version=config.version,
|
||||
created_at=config.created_at,
|
||||
updated_at=config.updated_at
|
||||
updated_at=config.updated_at,
|
||||
)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
log.error(f"Failed to set default LLM model config: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to set default LLM model configuration"
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to set default LLM model configuration"
|
||||
)
|
||||
@@ -55,6 +55,7 @@ def _load_netmiko_device_types() -> schemas.NetmikoDeviceTypeList:
|
||||
import importlib
|
||||
|
||||
import netmiko
|
||||
|
||||
# "from netmiko import ssh_dispatcher" is shadowed by a function of the same
|
||||
# name in netmiko's __init__, so import the module through importlib
|
||||
sd = importlib.import_module("netmiko.ssh_dispatcher")
|
||||
@@ -79,9 +80,7 @@ def _load_netmiko_device_types() -> schemas.NetmikoDeviceTypeList:
|
||||
|
||||
|
||||
@router.get(
|
||||
"/device_types",
|
||||
response_model=schemas.NetmikoDeviceTypeList,
|
||||
dependencies=[Depends(get_current_active_user)]
|
||||
"/device_types", response_model=schemas.NetmikoDeviceTypeList, dependencies=[Depends(get_current_active_user)]
|
||||
)
|
||||
def get_netmiko_device_types() -> schemas.NetmikoDeviceTypeList:
|
||||
"""
|
||||
@@ -97,6 +96,6 @@ def get_netmiko_device_types() -> schemas.NetmikoDeviceTypeList:
|
||||
except ImportError:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_501_NOT_IMPLEMENTED,
|
||||
detail="Netmiko is not available. Install AI dependencies with: pip install gns3-server[ai-features]"
|
||||
detail="Netmiko is not available. Install AI dependencies with: pip install gns3-server[ai-features]",
|
||||
)
|
||||
return _device_types_cache
|
||||
@@ -125,7 +125,7 @@ def _check_node_type(node: Node, *required_types: str) -> None:
|
||||
404: {"model": schemas.ErrorMessage, "description": "Could not find project"},
|
||||
409: {"model": schemas.ErrorMessage, "description": "Could not create node"},
|
||||
},
|
||||
dependencies=[Depends(has_privilege("Node.Allocate"))]
|
||||
dependencies=[Depends(has_privilege("Node.Allocate"))],
|
||||
)
|
||||
async def create_node(node_data: schemas.NodeCreate, project: Project = Depends(dep_project)) -> schemas.Node:
|
||||
"""
|
||||
@@ -145,11 +145,11 @@ async def create_node(node_data: schemas.NodeCreate, project: Project = Depends(
|
||||
"",
|
||||
response_model=List[schemas.Node],
|
||||
response_model_exclude_unset=True,
|
||||
dependencies=[Depends(has_privilege("Node.Audit"))]
|
||||
dependencies=[Depends(has_privilege("Node.Audit"))],
|
||||
)
|
||||
def get_nodes(
|
||||
project: Project = Depends(dep_project),
|
||||
tags: Optional[List[str]] = Query(None, description="Filter by tags (e.g. tags=vendor:cisco&tags=model:7200)")
|
||||
tags: Optional[List[str]] = Query(None, description="Filter by tags (e.g. tags=vendor:cisco&tags=model:7200)"),
|
||||
) -> List[schemas.Node]:
|
||||
"""
|
||||
Return all nodes belonging to a given project.
|
||||
@@ -213,7 +213,9 @@ async def stop_all_nodes(project: Project = Depends(dep_project)) -> None:
|
||||
raise
|
||||
|
||||
|
||||
@router.post("/suspend", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(has_privilege("Node.PowerMgmt"))])
|
||||
@router.post(
|
||||
"/suspend", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(has_privilege("Node.PowerMgmt"))]
|
||||
)
|
||||
async def suspend_all_nodes(project: Project = Depends(dep_project)) -> None:
|
||||
"""
|
||||
Suspend all nodes belonging to a given project.
|
||||
@@ -270,7 +272,7 @@ async def get_node(node: Node = Depends(dep_node)) -> schemas.Node:
|
||||
"/{node_id}",
|
||||
response_model=schemas.Node,
|
||||
response_model_exclude_unset=True,
|
||||
dependencies=[Depends(has_privilege("Node.Modify"))]
|
||||
dependencies=[Depends(has_privilege("Node.Modify"))],
|
||||
)
|
||||
async def update_node(node_data: schemas.NodeUpdate, node: Node = Depends(dep_node)) -> schemas.Node:
|
||||
"""
|
||||
@@ -294,11 +296,12 @@ async def update_node(node_data: schemas.NodeUpdate, node: Node = Depends(dep_no
|
||||
"/{node_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={**responses, 409: {"model": schemas.ErrorMessage, "description": "Cannot delete node"}},
|
||||
dependencies=[Depends(has_privilege("Node.Allocate"))]
|
||||
dependencies=[Depends(has_privilege("Node.Allocate"))],
|
||||
)
|
||||
async def delete_node(
|
||||
node_id: UUID, project: Project = Depends(dep_project),
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
|
||||
node_id: UUID,
|
||||
project: Project = Depends(dep_project),
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
|
||||
) -> None:
|
||||
"""
|
||||
Delete a node from a project.
|
||||
@@ -314,7 +317,7 @@ async def delete_node(
|
||||
"/{node_id}/duplicate",
|
||||
response_model=schemas.Node,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
dependencies=[Depends(has_privilege("Node.Allocate"))]
|
||||
dependencies=[Depends(has_privilege("Node.Allocate"))],
|
||||
)
|
||||
async def duplicate_node(duplicate_data: schemas.NodeDuplicate, node: Node = Depends(dep_node)) -> schemas.Node:
|
||||
"""
|
||||
@@ -328,9 +331,7 @@ async def duplicate_node(duplicate_data: schemas.NodeDuplicate, node: Node = Dep
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/start",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(has_privilege("Node.PowerMgmt"))]
|
||||
"/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(has_privilege("Node.PowerMgmt"))]
|
||||
)
|
||||
async def start_node(start_data: Optional[dict] = None, node: Node = Depends(dep_node)) -> None:
|
||||
"""
|
||||
@@ -345,10 +346,9 @@ async def start_node(start_data: Optional[dict] = None, node: Node = Depends(dep
|
||||
if not e.status_code == status.HTTP_405_METHOD_NOT_ALLOWED:
|
||||
raise
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(has_privilege("Node.PowerMgmt"))]
|
||||
"/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(has_privilege("Node.PowerMgmt"))]
|
||||
)
|
||||
async def stop_node(node: Node = Depends(dep_node)) -> None:
|
||||
"""
|
||||
@@ -367,7 +367,7 @@ async def stop_node(node: Node = Depends(dep_node)) -> None:
|
||||
@router.post(
|
||||
"/{node_id}/suspend",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(has_privilege("Node.PowerMgmt"))]
|
||||
dependencies=[Depends(has_privilege("Node.PowerMgmt"))],
|
||||
)
|
||||
async def suspend_node(node: Node = Depends(dep_node)) -> None:
|
||||
"""
|
||||
@@ -383,9 +383,7 @@ async def suspend_node(node: Node = Depends(dep_node)) -> None:
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/reload",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(has_privilege("Node.PowerMgmt"))]
|
||||
"/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(has_privilege("Node.PowerMgmt"))]
|
||||
)
|
||||
async def reload_node(node: Node = Depends(dep_node)) -> None:
|
||||
"""
|
||||
@@ -400,10 +398,9 @@ async def reload_node(node: Node = Depends(dep_node)) -> None:
|
||||
if not e.status_code == status.HTTP_405_METHOD_NOT_ALLOWED:
|
||||
raise
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/isolate",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(has_privilege("Link.Modify"))]
|
||||
"/{node_id}/isolate", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(has_privilege("Link.Modify"))]
|
||||
)
|
||||
async def isolate_node(node: Node = Depends(dep_node)) -> None:
|
||||
"""
|
||||
@@ -417,9 +414,7 @@ async def isolate_node(node: Node = Depends(dep_node)) -> None:
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/unisolate",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(has_privilege("Link.Modify"))]
|
||||
"/{node_id}/unisolate", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(has_privilege("Link.Modify"))]
|
||||
)
|
||||
async def unisolate_node(node: Node = Depends(dep_node)) -> None:
|
||||
"""
|
||||
@@ -436,7 +431,7 @@ async def unisolate_node(node: Node = Depends(dep_node)) -> None:
|
||||
"/{node_id}/links",
|
||||
response_model=List[schemas.Link],
|
||||
response_model_exclude_unset=True,
|
||||
dependencies = [Depends(has_privilege("Link.Audit"))]
|
||||
dependencies=[Depends(has_privilege("Link.Audit"))],
|
||||
)
|
||||
async def get_node_links(node: Node = Depends(dep_node)) -> List[schemas.Link]:
|
||||
"""
|
||||
@@ -478,12 +473,10 @@ async def idlepc_proposals(node: Node = Depends(dep_node)) -> List[str]:
|
||||
@router.post(
|
||||
"/{node_id}/qemu/disk_image/{disk_name}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(has_privilege("Node.Allocate"))]
|
||||
dependencies=[Depends(has_privilege("Node.Allocate"))],
|
||||
)
|
||||
async def create_disk_image(
|
||||
disk_name: str,
|
||||
disk_data: schemas.QemuDiskImageCreate,
|
||||
node: Node = Depends(dep_node)
|
||||
disk_name: str, disk_data: schemas.QemuDiskImageCreate, node: Node = Depends(dep_node)
|
||||
) -> None:
|
||||
"""
|
||||
Create a Qemu disk image.
|
||||
@@ -498,12 +491,10 @@ async def create_disk_image(
|
||||
@router.put(
|
||||
"/{node_id}/qemu/disk_image/{disk_name}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(has_privilege("Node.Allocate"))]
|
||||
dependencies=[Depends(has_privilege("Node.Allocate"))],
|
||||
)
|
||||
async def update_disk_image(
|
||||
disk_name: str,
|
||||
disk_data: schemas.QemuDiskImageUpdate,
|
||||
node: Node = Depends(dep_node)
|
||||
disk_name: str, disk_data: schemas.QemuDiskImageUpdate, node: Node = Depends(dep_node)
|
||||
) -> None:
|
||||
"""
|
||||
Update a Qemu disk image.
|
||||
@@ -518,12 +509,9 @@ async def update_disk_image(
|
||||
@router.delete(
|
||||
"/{node_id}/qemu/disk_image/{disk_name}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(has_privilege("Node.Allocate"))]
|
||||
dependencies=[Depends(has_privilege("Node.Allocate"))],
|
||||
)
|
||||
async def delete_disk_image(
|
||||
disk_name: str,
|
||||
node: Node = Depends(dep_node)
|
||||
) -> None:
|
||||
async def delete_disk_image(disk_name: str, node: Node = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Delete a Qemu disk image.
|
||||
|
||||
@@ -534,11 +522,13 @@ async def delete_disk_image(
|
||||
await node.delete(f"/disk_image/{disk_name}")
|
||||
|
||||
|
||||
@router.get("/{node_id}/files", response_model=List[schemas.NodeFile], dependencies=[Depends(has_privilege("Node.Audit"))])
|
||||
@router.get(
|
||||
"/{node_id}/files", response_model=List[schemas.NodeFile], dependencies=[Depends(has_privilege("Node.Audit"))]
|
||||
)
|
||||
async def list_node_files(
|
||||
node: Node = Depends(dep_node),
|
||||
path: str = Query("", description="Subdirectory path within node directory"),
|
||||
recursive: bool = Query(False, description="Recursively list all files")
|
||||
recursive: bool = Query(False, description="Recursively list all files"),
|
||||
) -> List[schemas.NodeFile]:
|
||||
"""
|
||||
List files in a node directory with detailed metadata.
|
||||
@@ -556,11 +546,7 @@ async def list_node_files(
|
||||
params["path"] = path
|
||||
if recursive:
|
||||
params["recursive"] = "true"
|
||||
res = await node.compute.http_query(
|
||||
"GET", url,
|
||||
params=params if params else None,
|
||||
timeout=None
|
||||
)
|
||||
res = await node.compute.http_query("GET", url, params=params if params else None, timeout=None)
|
||||
return res.json
|
||||
|
||||
|
||||
@@ -582,8 +568,7 @@ async def get_file(file_path: str, node: Node = Depends(dep_node)) -> Response:
|
||||
path = f"/project-files/{node_type}/{node.id}/{path}"
|
||||
|
||||
compute_resp = await node.compute.http_query(
|
||||
"GET", f"/projects/{node.project.id}/files{path}",
|
||||
timeout=None, stream=True
|
||||
"GET", f"/projects/{node.project.id}/files{path}", timeout=None, stream=True
|
||||
)
|
||||
|
||||
async def streamer():
|
||||
@@ -606,7 +591,7 @@ async def get_file(file_path: str, node: Node = Depends(dep_node)) -> Response:
|
||||
@router.post(
|
||||
"/{node_id}/files/{file_path:path}",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
dependencies=[Depends(has_privilege("Node.Modify"))]
|
||||
dependencies=[Depends(has_privilege("Node.Modify"))],
|
||||
)
|
||||
async def post_file(file_path: str, request: Request, node: Node = Depends(dep_node)):
|
||||
"""
|
||||
@@ -626,15 +611,14 @@ async def post_file(file_path: str, request: Request, node: Node = Depends(dep_n
|
||||
|
||||
# Stream request body directly to compute node
|
||||
await node.compute.http_query(
|
||||
"POST", f"/projects/{node.project.id}/files{path}",
|
||||
data=request.stream(), timeout=None
|
||||
"POST", f"/projects/{node.project.id}/files{path}", data=request.stream(), timeout=None
|
||||
)
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{node_id}/files/{file_path:path}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(has_privilege("Node.Modify"))]
|
||||
dependencies=[Depends(has_privilege("Node.Modify"))],
|
||||
)
|
||||
async def delete_node_file(file_path: str, node: Node = Depends(dep_node)) -> None:
|
||||
"""
|
||||
@@ -651,17 +635,14 @@ async def delete_node_file(file_path: str, node: Node = Depends(dep_node)) -> No
|
||||
node_type = node.node_type
|
||||
path = f"/project-files/{node_type}/{node.id}/{path}"
|
||||
|
||||
await node.compute.http_query(
|
||||
"DELETE", f"/projects/{node.project.id}/files{path}",
|
||||
timeout=None
|
||||
)
|
||||
await node.compute.http_query("DELETE", f"/projects/{node.project.id}/files{path}", timeout=None)
|
||||
|
||||
|
||||
@router.websocket("/{node_id}/console/ws")
|
||||
async def ws_console(
|
||||
websocket: WebSocket,
|
||||
current_user: schemas.User = Depends(has_privilege_on_websocket("Node.Console")),
|
||||
node: Node = Depends(dep_node)
|
||||
websocket: WebSocket,
|
||||
current_user: schemas.User = Depends(has_privilege_on_websocket("Node.Console")),
|
||||
node: Node = Depends(dep_node),
|
||||
) -> None:
|
||||
"""
|
||||
WebSocket console.
|
||||
@@ -682,7 +663,7 @@ async def ws_console(
|
||||
# handle IPv6 address
|
||||
ip = ipaddress.ip_address(compute_host)
|
||||
if isinstance(ip, ipaddress.IPv6Address):
|
||||
compute_host = '[' + compute_host + ']'
|
||||
compute_host = "[" + compute_host + "]"
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
@@ -710,8 +691,7 @@ async def ws_console(
|
||||
except WebSocketDisconnect:
|
||||
pass
|
||||
log.info(
|
||||
f"Client {websocket.client.host}:{websocket.client.port} has disconnected from controller"
|
||||
f" console WebSocket"
|
||||
f"Client {websocket.client.host}:{websocket.client.port} has disconnected from controller console WebSocket"
|
||||
)
|
||||
|
||||
async def ws_send(ws_console_compute):
|
||||
@@ -766,11 +746,12 @@ async def ws_console(
|
||||
except aiohttp.ClientError as e:
|
||||
log.error(f"Client error received when forwarding to compute console WebSocket: {e}")
|
||||
|
||||
|
||||
@router.websocket("/{node_id}/console/vnc")
|
||||
async def vnc_console(
|
||||
websocket: WebSocket,
|
||||
current_user: schemas.User = Depends(has_privilege_on_websocket("Node.Console")),
|
||||
node: Node = Depends(dep_node)
|
||||
websocket: WebSocket,
|
||||
current_user: schemas.User = Depends(has_privilege_on_websocket("Node.Console")),
|
||||
node: Node = Depends(dep_node),
|
||||
) -> None:
|
||||
"""
|
||||
VNC WebSocket console.
|
||||
@@ -791,7 +772,7 @@ async def vnc_console(
|
||||
# handle IPv6 address
|
||||
ip = ipaddress.ip_address(compute_host)
|
||||
if isinstance(ip, ipaddress.IPv6Address):
|
||||
compute_host = '[' + compute_host + ']'
|
||||
compute_host = "[" + compute_host + "]"
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
@@ -852,7 +833,9 @@ async def vnc_console(
|
||||
else:
|
||||
auth = aiohttp.BasicAuth(user, "")
|
||||
ssl_context = Controller.instance().ssl_context()
|
||||
async with HTTPClient.get_client().ws_connect(vnc_console_compute_url, auth=auth, ssl_context=ssl_context) as ws:
|
||||
async with HTTPClient.get_client().ws_connect(
|
||||
vnc_console_compute_url, auth=auth, ssl_context=ssl_context
|
||||
) as ws:
|
||||
tasks = [
|
||||
asyncio.ensure_future(vnc_receive(ws)),
|
||||
asyncio.ensure_future(vnc_send(ws)),
|
||||
@@ -870,10 +853,9 @@ async def vnc_console(
|
||||
except aiohttp.ClientError as e:
|
||||
log.error(f"Client error received when forwarding to compute VNC console WebSocket: {e}")
|
||||
|
||||
|
||||
@router.post(
|
||||
"/console/reset",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(has_privilege("Node.Console"))]
|
||||
"/console/reset", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(has_privilege("Node.Console"))]
|
||||
)
|
||||
async def reset_console_all_nodes(project: Project = Depends(dep_project)) -> None:
|
||||
"""
|
||||
@@ -888,7 +870,7 @@ async def reset_console_all_nodes(project: Project = Depends(dep_project)) -> No
|
||||
@router.post(
|
||||
"/{node_id}/console/reset",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(has_privilege("Node.Console"))]
|
||||
dependencies=[Depends(has_privilege("Node.Console"))],
|
||||
)
|
||||
async def console_reset(node: Node = Depends(dep_node)) -> None:
|
||||
"""
|
||||
|
||||
@@ -24,11 +24,7 @@ from uuid import UUID
|
||||
from typing import List
|
||||
|
||||
from gns3server import schemas
|
||||
from gns3server.controller.controller_error import (
|
||||
ControllerError,
|
||||
ControllerBadRequestError,
|
||||
ControllerNotFoundError
|
||||
)
|
||||
from gns3server.controller.controller_error import ControllerError, ControllerBadRequestError, ControllerNotFoundError
|
||||
|
||||
from gns3server.controller import Controller
|
||||
from gns3server.db.repositories.rbac import RbacRepository
|
||||
@@ -44,13 +40,9 @@ log = logging.getLogger(__name__)
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get(
|
||||
"",
|
||||
response_model=List[schemas.ResourcePool],
|
||||
dependencies=[Depends(has_privilege("Pool.Audit"))]
|
||||
)
|
||||
@router.get("", response_model=List[schemas.ResourcePool], dependencies=[Depends(has_privilege("Pool.Audit"))])
|
||||
async def get_resource_pools(
|
||||
pools_repo: ResourcePoolsRepository = Depends(get_repository(ResourcePoolsRepository))
|
||||
pools_repo: ResourcePoolsRepository = Depends(get_repository(ResourcePoolsRepository)),
|
||||
) -> List[schemas.ResourcePool]:
|
||||
"""
|
||||
Get all resource pools.
|
||||
@@ -65,11 +57,11 @@ async def get_resource_pools(
|
||||
"",
|
||||
response_model=schemas.ResourcePool,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
dependencies=[Depends(has_privilege("Pool.Allocate"))]
|
||||
dependencies=[Depends(has_privilege("Pool.Allocate"))],
|
||||
)
|
||||
async def create_resource_pool(
|
||||
resource_pool_create: schemas.ResourcePoolCreate,
|
||||
pools_repo: ResourcePoolsRepository = Depends(get_repository(ResourcePoolsRepository))
|
||||
resource_pool_create: schemas.ResourcePoolCreate,
|
||||
pools_repo: ResourcePoolsRepository = Depends(get_repository(ResourcePoolsRepository)),
|
||||
) -> schemas.ResourcePool:
|
||||
"""
|
||||
Create a new resource pool
|
||||
@@ -78,19 +70,16 @@ async def create_resource_pool(
|
||||
"""
|
||||
|
||||
if await pools_repo.get_resource_pool_by_name(resource_pool_create.name):
|
||||
raise ControllerBadRequestError(f"Resource pool '{resource_pool_create.name}' already exists")
|
||||
raise ControllerBadRequestError(f"Resource pool '{resource_pool_create.name}' already exists")
|
||||
|
||||
return await pools_repo.create_resource_pool(resource_pool_create)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{resource_pool_id}",
|
||||
response_model=schemas.ResourcePool,
|
||||
dependencies=[Depends(has_privilege("Pool.Audit"))]
|
||||
"/{resource_pool_id}", response_model=schemas.ResourcePool, dependencies=[Depends(has_privilege("Pool.Audit"))]
|
||||
)
|
||||
async def get_resource_pool(
|
||||
resource_pool_id: UUID,
|
||||
pools_repo: ResourcePoolsRepository = Depends(get_repository(ResourcePoolsRepository))
|
||||
resource_pool_id: UUID, pools_repo: ResourcePoolsRepository = Depends(get_repository(ResourcePoolsRepository))
|
||||
) -> schemas.ResourcePool:
|
||||
"""
|
||||
Get a resource pool.
|
||||
@@ -105,14 +94,12 @@ async def get_resource_pool(
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{resource_pool_id}",
|
||||
response_model=schemas.ResourcePool,
|
||||
dependencies=[Depends(has_privilege("Pool.Modify"))]
|
||||
"/{resource_pool_id}", response_model=schemas.ResourcePool, dependencies=[Depends(has_privilege("Pool.Modify"))]
|
||||
)
|
||||
async def update_resource_pool(
|
||||
resource_pool_id: UUID,
|
||||
resource_pool_update: schemas.ResourcePoolUpdate,
|
||||
pools_repo: ResourcePoolsRepository = Depends(get_repository(ResourcePoolsRepository))
|
||||
resource_pool_id: UUID,
|
||||
resource_pool_update: schemas.ResourcePoolUpdate,
|
||||
pools_repo: ResourcePoolsRepository = Depends(get_repository(ResourcePoolsRepository)),
|
||||
) -> schemas.ResourcePool:
|
||||
"""
|
||||
Update a resource pool.
|
||||
@@ -130,12 +117,12 @@ async def update_resource_pool(
|
||||
@router.delete(
|
||||
"/{resource_pool_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(has_privilege("Pool.Allocate"))]
|
||||
dependencies=[Depends(has_privilege("Pool.Allocate"))],
|
||||
)
|
||||
async def delete_resource_pool(
|
||||
resource_pool_id: UUID,
|
||||
pools_repo: ResourcePoolsRepository = Depends(get_repository(ResourcePoolsRepository)),
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
|
||||
resource_pool_id: UUID,
|
||||
pools_repo: ResourcePoolsRepository = Depends(get_repository(ResourcePoolsRepository)),
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
|
||||
) -> None:
|
||||
"""
|
||||
Delete a resource pool.
|
||||
@@ -184,11 +171,11 @@ async def delete_resource_pool(
|
||||
@router.get(
|
||||
"/{resource_pool_id}/resources",
|
||||
response_model=List[schemas.Resource],
|
||||
dependencies=[Depends(has_privilege("Pool.Audit"))]
|
||||
dependencies=[Depends(has_privilege("Pool.Audit"))],
|
||||
)
|
||||
async def get_pool_resources(
|
||||
resource_pool_id: UUID,
|
||||
pools_repo: ResourcePoolsRepository = Depends(get_repository(ResourcePoolsRepository)),
|
||||
resource_pool_id: UUID,
|
||||
pools_repo: ResourcePoolsRepository = Depends(get_repository(ResourcePoolsRepository)),
|
||||
) -> List[schemas.Resource]:
|
||||
"""
|
||||
Get all resource in a pool.
|
||||
@@ -202,12 +189,12 @@ async def get_pool_resources(
|
||||
@router.put(
|
||||
"/{resource_pool_id}/resources/{resource_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(has_privilege("Pool.Modify"))]
|
||||
dependencies=[Depends(has_privilege("Pool.Modify"))],
|
||||
)
|
||||
async def add_resource_to_pool(
|
||||
resource_pool_id: UUID,
|
||||
resource_id: UUID,
|
||||
pools_repo: ResourcePoolsRepository = Depends(get_repository(ResourcePoolsRepository)),
|
||||
resource_pool_id: UUID,
|
||||
resource_id: UUID,
|
||||
pools_repo: ResourcePoolsRepository = Depends(get_repository(ResourcePoolsRepository)),
|
||||
) -> None:
|
||||
"""
|
||||
Add resource to a resource pool.
|
||||
@@ -240,7 +227,7 @@ async def add_resource_to_pool(
|
||||
@router.delete(
|
||||
"/{resource_pool_id}/resources/{resource_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(has_privilege("Pool.Modify"))]
|
||||
dependencies=[Depends(has_privilege("Pool.Modify"))],
|
||||
)
|
||||
async def remove_resource_from_pool(
|
||||
resource_pool_id: UUID,
|
||||
|
||||
@@ -32,7 +32,7 @@ router = APIRouter()
|
||||
response_model=List[schemas.Privilege],
|
||||
)
|
||||
async def get_privileges(
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
|
||||
) -> List[schemas.Privilege]:
|
||||
"""
|
||||
Get all privileges.
|
||||
|
||||
@@ -71,14 +71,10 @@ def dep_project(project_id: UUID) -> Project:
|
||||
return project
|
||||
|
||||
|
||||
@router.get(
|
||||
"",
|
||||
response_model=List[schemas.Project],
|
||||
response_model_exclude_unset=True
|
||||
)
|
||||
@router.get("", response_model=List[schemas.Project], response_model_exclude_unset=True)
|
||||
async def get_projects(
|
||||
current_user: schemas.User = Depends(get_current_active_user),
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
|
||||
current_user: schemas.User = Depends(get_current_active_user),
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
|
||||
) -> List[schemas.Project]:
|
||||
"""
|
||||
Return all projects.
|
||||
@@ -124,11 +120,11 @@ async def get_projects(
|
||||
response_model=schemas.Project,
|
||||
response_model_exclude_unset=True,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create project"}},
|
||||
dependencies=[Depends(has_privilege("Project.Allocate"))]
|
||||
dependencies=[Depends(has_privilege("Project.Allocate"))],
|
||||
)
|
||||
async def create_project(
|
||||
project_data: schemas.ProjectCreate,
|
||||
current_user: schemas.User = Depends(get_current_active_user),
|
||||
project_data: schemas.ProjectCreate,
|
||||
current_user: schemas.User = Depends(get_current_active_user),
|
||||
) -> schemas.Project:
|
||||
"""
|
||||
Create a new project.
|
||||
@@ -158,11 +154,10 @@ def get_project(project: Project = Depends(dep_project)) -> schemas.Project:
|
||||
"/{project_id}",
|
||||
response_model=schemas.Project,
|
||||
response_model_exclude_unset=True,
|
||||
dependencies=[Depends(has_privilege("Project.Modify"))]
|
||||
dependencies=[Depends(has_privilege("Project.Modify"))],
|
||||
)
|
||||
async def update_project(
|
||||
project_data: schemas.ProjectUpdate,
|
||||
project: Project = Depends(dep_project)
|
||||
project_data: schemas.ProjectUpdate, project: Project = Depends(dep_project)
|
||||
) -> schemas.Project:
|
||||
"""
|
||||
Update a project.
|
||||
@@ -175,13 +170,11 @@ async def update_project(
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{project_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(has_privilege("Project.Allocate"))]
|
||||
"/{project_id}", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(has_privilege("Project.Allocate"))]
|
||||
)
|
||||
async def delete_project(
|
||||
project: Project = Depends(dep_project),
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
|
||||
project: Project = Depends(dep_project),
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
|
||||
) -> None:
|
||||
"""
|
||||
Delete a project.
|
||||
@@ -272,9 +265,7 @@ async def replay_tag_range(
|
||||
Required privilege: Project.Audit
|
||||
"""
|
||||
|
||||
return await _replay_response(
|
||||
marker_replay.build_timeline(project, tag, filter_expr=filter, link_id=link)
|
||||
)
|
||||
return await _replay_response(marker_replay.build_timeline(project, tag, filter_expr=filter, link_id=link))
|
||||
|
||||
|
||||
@router.get(
|
||||
@@ -306,8 +297,13 @@ async def replay_tag_frames(
|
||||
|
||||
return await _replay_response(
|
||||
marker_replay.query_frames(
|
||||
project, tag, ts, window_ms=window_ms, limit=limit,
|
||||
filter_expr=filter, link_id=link,
|
||||
project,
|
||||
tag,
|
||||
ts,
|
||||
window_ms=window_ms,
|
||||
limit=limit,
|
||||
filter_expr=filter,
|
||||
link_id=link,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -343,9 +339,7 @@ async def replay_tag_frame_detail(
|
||||
"""
|
||||
|
||||
return await _replay_response(
|
||||
marker_replay.decode_frame(
|
||||
project, tag, ts, node_id, link_id, marker, frame_number=frame_number
|
||||
)
|
||||
marker_replay.decode_frame(project, tag, ts, node_id, link_id, marker, frame_number=frame_number)
|
||||
)
|
||||
|
||||
|
||||
@@ -353,10 +347,8 @@ async def replay_tag_frame_detail(
|
||||
# Project-level marker definitions (global rules inherited by every link)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@router.get(
|
||||
"/{project_id}/marker-definitions",
|
||||
dependencies=[Depends(has_privilege("Project.Audit"))]
|
||||
)
|
||||
|
||||
@router.get("/{project_id}/marker-definitions", dependencies=[Depends(has_privilege("Project.Audit"))])
|
||||
def get_marker_definitions(project: Project = Depends(dep_project)) -> dict:
|
||||
"""
|
||||
Return all project-level marker definitions with their bound link IDs.
|
||||
@@ -368,9 +360,9 @@ def get_marker_definitions(project: Project = Depends(dep_project)) -> dict:
|
||||
for name, d in project.marker_definitions.items():
|
||||
# Collect which links currently carry an inherited copy.
|
||||
bound = [
|
||||
lid for lid, link in project.links.items()
|
||||
if f"global-{name}" in link.markers
|
||||
and link.markers[f"global-{name}"].get("inherited_from") == name
|
||||
lid
|
||||
for lid, link in project.links.items()
|
||||
if f"global-{name}" in link.markers and link.markers[f"global-{name}"].get("inherited_from") == name
|
||||
]
|
||||
result[name] = {**d, "link_ids": bound}
|
||||
return result
|
||||
@@ -379,11 +371,10 @@ def get_marker_definitions(project: Project = Depends(dep_project)) -> dict:
|
||||
@router.post(
|
||||
"/{project_id}/marker-definitions",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
dependencies=[Depends(has_privilege("Project.Modify"))]
|
||||
dependencies=[Depends(has_privilege("Project.Modify"))],
|
||||
)
|
||||
async def create_marker_definition(
|
||||
def_data: schemas.MarkerDefinitionCreate,
|
||||
project: Project = Depends(dep_project)
|
||||
def_data: schemas.MarkerDefinitionCreate, project: Project = Depends(dep_project)
|
||||
) -> dict:
|
||||
"""
|
||||
Create a project-level marker definition and fan out to every link.
|
||||
@@ -406,14 +397,9 @@ async def create_marker_definition(
|
||||
return project.marker_definitions.get(name, {})
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{project_id}/marker-definitions/{def_name}",
|
||||
dependencies=[Depends(has_privilege("Project.Modify"))]
|
||||
)
|
||||
@router.put("/{project_id}/marker-definitions/{def_name}", dependencies=[Depends(has_privilege("Project.Modify"))])
|
||||
async def update_marker_definition(
|
||||
def_name: str,
|
||||
def_data: schemas.MarkerDefinitionCreate,
|
||||
project: Project = Depends(dep_project)
|
||||
def_name: str, def_data: schemas.MarkerDefinitionCreate, project: Project = Depends(dep_project)
|
||||
) -> dict:
|
||||
"""
|
||||
Update a marker definition and sync all inherited copies on every link.
|
||||
@@ -436,12 +422,9 @@ async def update_marker_definition(
|
||||
@router.post(
|
||||
"/{project_id}/marker-definitions/{def_name}/pause",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(has_privilege("Project.Modify"))]
|
||||
dependencies=[Depends(has_privilege("Project.Modify"))],
|
||||
)
|
||||
async def pause_marker_definition(
|
||||
def_name: str,
|
||||
project: Project = Depends(dep_project)
|
||||
) -> None:
|
||||
async def pause_marker_definition(def_name: str, project: Project = Depends(dep_project)) -> None:
|
||||
"""
|
||||
Pause a definition: toggle off every inherited ``global-{def_name}`` copy
|
||||
on every link (uBridge ``enable_packet_filter off``, instant — no NIO
|
||||
@@ -457,12 +440,9 @@ async def pause_marker_definition(
|
||||
@router.post(
|
||||
"/{project_id}/marker-definitions/{def_name}/resume",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(has_privilege("Project.Modify"))]
|
||||
dependencies=[Depends(has_privilege("Project.Modify"))],
|
||||
)
|
||||
async def resume_marker_definition(
|
||||
def_name: str,
|
||||
project: Project = Depends(dep_project)
|
||||
) -> None:
|
||||
async def resume_marker_definition(def_name: str, project: Project = Depends(dep_project)) -> None:
|
||||
"""Resume a paused definition (toggle on every inherited copy).
|
||||
|
||||
Required privilege: Project.Modify
|
||||
@@ -474,12 +454,9 @@ async def resume_marker_definition(
|
||||
@router.delete(
|
||||
"/{project_id}/marker-definitions/{def_name}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(has_privilege("Project.Modify"))]
|
||||
dependencies=[Depends(has_privilege("Project.Modify"))],
|
||||
)
|
||||
async def delete_marker_definition(
|
||||
def_name: str,
|
||||
project: Project = Depends(dep_project)
|
||||
) -> None:
|
||||
async def delete_marker_definition(def_name: str, project: Project = Depends(dep_project)) -> None:
|
||||
"""
|
||||
Delete a marker definition and remove all inherited copies from every link.
|
||||
|
||||
@@ -493,7 +470,7 @@ async def delete_marker_definition(
|
||||
"/{project_id}/close",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={**responses, 409: {"model": schemas.ErrorMessage, "description": "Could not close project"}},
|
||||
dependencies=[Depends(has_privilege("Project.Allocate"))]
|
||||
dependencies=[Depends(has_privilege("Project.Allocate"))],
|
||||
)
|
||||
async def close_project(project: Project = Depends(dep_project)) -> None:
|
||||
"""
|
||||
@@ -510,7 +487,7 @@ async def close_project(project: Project = Depends(dep_project)) -> None:
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.Project,
|
||||
responses={**responses, 409: {"model": schemas.ErrorMessage, "description": "Could not open project"}},
|
||||
dependencies=[Depends(has_privilege("Project.Allocate"))]
|
||||
dependencies=[Depends(has_privilege("Project.Allocate"))],
|
||||
)
|
||||
async def open_project(project: Project = Depends(dep_project)) -> schemas.Project:
|
||||
"""
|
||||
@@ -528,7 +505,7 @@ async def open_project(project: Project = Depends(dep_project)) -> schemas.Proje
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.Project,
|
||||
responses={**responses, 409: {"model": schemas.ErrorMessage, "description": "Could not load project"}},
|
||||
dependencies=[Depends(has_privilege("Project.Allocate"))]
|
||||
dependencies=[Depends(has_privilege("Project.Allocate"))],
|
||||
)
|
||||
async def load_project(path: str = Body(..., embed=True)) -> schemas.Project:
|
||||
"""
|
||||
@@ -552,6 +529,7 @@ async def project_http_notifications(project_id: UUID) -> StreamingResponse:
|
||||
"""
|
||||
|
||||
from gns3server.api.server import app
|
||||
|
||||
controller = Controller.instance()
|
||||
project = controller.get_project(str(project_id))
|
||||
|
||||
@@ -579,9 +557,9 @@ async def project_http_notifications(project_id: UUID) -> StreamingResponse:
|
||||
|
||||
@router.websocket("/{project_id}/notifications/ws")
|
||||
async def project_ws_notifications(
|
||||
project_id: UUID,
|
||||
websocket: WebSocket,
|
||||
current_user: schemas.User = Depends(has_privilege_on_websocket("Project.Audit"))
|
||||
project_id: UUID,
|
||||
websocket: WebSocket,
|
||||
current_user: schemas.User = Depends(has_privilege_on_websocket("Project.Audit")),
|
||||
) -> None:
|
||||
"""
|
||||
Receive project notifications about the controller from WebSocket.
|
||||
@@ -617,9 +595,9 @@ async def project_ws_notifications(
|
||||
|
||||
@router.websocket("/{project_id}/notifications/markers/ws")
|
||||
async def project_marker_ws_notifications(
|
||||
project_id: UUID,
|
||||
websocket: WebSocket,
|
||||
current_user: schemas.User = Depends(has_privilege_on_websocket("Project.Audit"))
|
||||
project_id: UUID,
|
||||
websocket: WebSocket,
|
||||
current_user: schemas.User = Depends(has_privilege_on_websocket("Project.Audit")),
|
||||
) -> None:
|
||||
"""
|
||||
Receive marker notifications (e.g. marker.match) for a project on a
|
||||
@@ -635,14 +613,18 @@ async def project_marker_ws_notifications(
|
||||
controller = Controller.instance()
|
||||
project = controller.get_project(str(project_id))
|
||||
|
||||
log.info(f"New client has connected to the marker notification stream for project ID '{project.id}' (WebSocket method)")
|
||||
log.info(
|
||||
f"New client has connected to the marker notification stream for project ID '{project.id}' (WebSocket method)"
|
||||
)
|
||||
try:
|
||||
with controller.notification.project_marker_queue(project.id) as queue:
|
||||
while True:
|
||||
notification = await queue.get_json(5)
|
||||
await websocket.send_text(notification)
|
||||
except (ConnectionClosed, WebSocketDisconnect):
|
||||
log.info(f"Client has disconnected from the marker notification stream for project ID '{project.id}' (WebSocket method)")
|
||||
log.info(
|
||||
f"Client has disconnected from the marker notification stream for project ID '{project.id}' (WebSocket method)"
|
||||
)
|
||||
except WebSocketException as e:
|
||||
log.warning(f"Error while sending marker event to WebSocket client: {e}")
|
||||
|
||||
@@ -685,7 +667,9 @@ async def export_project(
|
||||
raise ControllerBadRequestError("Compression level must be between 1 and 22 for Zstandard compression")
|
||||
|
||||
if compression_level is not None and compression_query in ("none", "lzma"):
|
||||
raise ControllerBadRequestError(f"Compression level is not supported for '{compression_query}' compression method")
|
||||
raise ControllerBadRequestError(
|
||||
f"Compression level is not supported for '{compression_query}' compression method"
|
||||
)
|
||||
|
||||
try:
|
||||
begin = time.time()
|
||||
@@ -693,8 +677,9 @@ async def export_project(
|
||||
working_dir = os.path.abspath(os.path.join(project.path, os.pardir))
|
||||
|
||||
async def streamer():
|
||||
log.info(f"Exporting project '{project.name}' with '{compression_query}' compression "
|
||||
f"(level {compression_level})")
|
||||
log.info(
|
||||
f"Exporting project '{project.name}' with '{compression_query}' compression (level {compression_level})"
|
||||
)
|
||||
with tempfile.TemporaryDirectory(dir=working_dir) as tmpdir:
|
||||
with aiozipstream.ZipFile(compression=compression, compresslevel=compression_level) as zstream:
|
||||
await export_controller_project(
|
||||
@@ -720,7 +705,7 @@ async def export_project(
|
||||
encoded = urllib.parse.quote(project.name, safe="")
|
||||
headers = {
|
||||
"Content-Disposition": (
|
||||
f'attachment; filename="{fallback}.gns3project"; filename*=UTF-8\'\'{encoded}.gns3project'
|
||||
f"attachment; filename=\"{fallback}.gns3project\"; filename*=UTF-8''{encoded}.gns3project"
|
||||
)
|
||||
}
|
||||
return StreamingResponse(streamer(), media_type="application/gns3project", headers=headers)
|
||||
@@ -730,13 +715,9 @@ async def export_project(
|
||||
"/{project_id}/import",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.Project,
|
||||
dependencies=[Depends(has_privilege("Project.Allocate"))]
|
||||
dependencies=[Depends(has_privilege("Project.Allocate"))],
|
||||
)
|
||||
async def import_project(
|
||||
project_id: UUID,
|
||||
request: Request,
|
||||
name: Optional[str] = None
|
||||
) -> schemas.Project:
|
||||
async def import_project(project_id: UUID, request: Request, name: Optional[str] = None) -> schemas.Project:
|
||||
"""
|
||||
Import a project from a portable archive.
|
||||
|
||||
@@ -768,14 +749,14 @@ async def import_project(
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.Project,
|
||||
responses={**responses, 409: {"model": schemas.ErrorMessage, "description": "Could not duplicate project"}},
|
||||
dependencies=[Depends(has_privilege("Project.Audit"))]
|
||||
dependencies=[Depends(has_privilege("Project.Audit"))],
|
||||
)
|
||||
async def duplicate_project(
|
||||
project_data: schemas.ProjectDuplicate,
|
||||
project: Project = Depends(dep_project),
|
||||
current_user: schemas.User = Depends(get_current_active_user),
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
|
||||
pools_repo: ResourcePoolsRepository = Depends(get_repository(ResourcePoolsRepository))
|
||||
project_data: schemas.ProjectDuplicate,
|
||||
project: Project = Depends(dep_project),
|
||||
current_user: schemas.User = Depends(get_current_active_user),
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
|
||||
pools_repo: ResourcePoolsRepository = Depends(get_repository(ResourcePoolsRepository)),
|
||||
) -> schemas.Project:
|
||||
"""
|
||||
Duplicate a project.
|
||||
@@ -790,22 +771,26 @@ async def duplicate_project(
|
||||
can_be_duplicated_somewhere = False
|
||||
if pool_memberships:
|
||||
for pool in pool_memberships:
|
||||
if await rbac_repo.check_user_has_privilege(current_user.user_id, f"/pools/{pool.resource_pool_id}", "Project.Allocate"):
|
||||
if await rbac_repo.check_user_has_privilege(
|
||||
current_user.user_id, f"/pools/{pool.resource_pool_id}", "Project.Allocate"
|
||||
):
|
||||
can_be_duplicated_somewhere = True
|
||||
break
|
||||
|
||||
if not can_be_duplicated_somewhere and not await rbac_repo.check_user_has_privilege(current_user.user_id, "/projects", "Project.Allocate"):
|
||||
if not can_be_duplicated_somewhere and not await rbac_repo.check_user_has_privilege(
|
||||
current_user.user_id, "/projects", "Project.Allocate"
|
||||
):
|
||||
log.warning(f"Project {project.name} cannot be duplicated anywhere")
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
|
||||
|
||||
reset_mac_addresses = project_data.reset_mac_addresses
|
||||
new_project = await project.duplicate(
|
||||
name=project_data.name, reset_mac_addresses=reset_mac_addresses
|
||||
)
|
||||
new_project = await project.duplicate(name=project_data.name, reset_mac_addresses=reset_mac_addresses)
|
||||
|
||||
# Add the new project in the same resource pools if the duplicated project belongs to any
|
||||
if pool_memberships:
|
||||
resource_create = schemas.ResourceCreate(resource_id=new_project.id, resource_type="project", name=new_project.name)
|
||||
resource_create = schemas.ResourceCreate(
|
||||
resource_id=new_project.id, resource_type="project", name=new_project.name
|
||||
)
|
||||
resource = await pools_repo.create_resource(resource_create)
|
||||
for pool in pool_memberships:
|
||||
await pools_repo.add_resource_to_pool(pool.resource_pool_id, resource)
|
||||
@@ -827,7 +812,7 @@ async def locked_project(project: Project = Depends(dep_project)) -> bool:
|
||||
@router.post(
|
||||
"/{project_id}/lock",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(has_privilege("Project.Modify"))]
|
||||
dependencies=[Depends(has_privilege("Project.Modify"))],
|
||||
)
|
||||
async def lock_project(project: Project = Depends(dep_project)) -> None:
|
||||
"""
|
||||
@@ -842,7 +827,7 @@ async def lock_project(project: Project = Depends(dep_project)) -> None:
|
||||
@router.post(
|
||||
"/{project_id}/unlock",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(has_privilege("Project.Modify"))]
|
||||
dependencies=[Depends(has_privilege("Project.Modify"))],
|
||||
)
|
||||
async def unlock_project(project: Project = Depends(dep_project)) -> None:
|
||||
"""
|
||||
@@ -894,7 +879,7 @@ async def get_project_gns3_file(project: Project = Depends(dep_project)) -> File
|
||||
@router.post(
|
||||
"/{project_id}/files/{file_path:path}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(has_privilege("Project.Modify"))]
|
||||
dependencies=[Depends(has_privilege("Project.Modify"))],
|
||||
)
|
||||
async def write_file(file_path: str, request: Request, project: Project = Depends(dep_project)) -> None:
|
||||
"""
|
||||
@@ -929,7 +914,7 @@ async def write_file(file_path: str, request: Request, project: Project = Depend
|
||||
response_model=schemas.Node,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or template"}},
|
||||
dependencies=[Depends(has_privilege("Node.Allocate"))]
|
||||
dependencies=[Depends(has_privilege("Node.Allocate"))],
|
||||
)
|
||||
async def create_node_from_template(
|
||||
project_id: UUID,
|
||||
|
||||
@@ -42,14 +42,8 @@ log = logging.getLogger(__name__)
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get(
|
||||
"",
|
||||
response_model=List[schemas.Role],
|
||||
dependencies=[Depends(has_privilege("Role.Audit"))]
|
||||
)
|
||||
async def get_roles(
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
|
||||
) -> List[schemas.Role]:
|
||||
@router.get("", response_model=List[schemas.Role], dependencies=[Depends(has_privilege("Role.Audit"))])
|
||||
async def get_roles(rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))) -> List[schemas.Role]:
|
||||
"""
|
||||
Get all roles.
|
||||
|
||||
@@ -63,11 +57,10 @@ async def get_roles(
|
||||
"",
|
||||
response_model=schemas.Role,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
dependencies=[Depends(has_privilege("Role.Allocate"))]
|
||||
dependencies=[Depends(has_privilege("Role.Allocate"))],
|
||||
)
|
||||
async def create_role(
|
||||
role_create: schemas.RoleCreate,
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
|
||||
role_create: schemas.RoleCreate, rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
|
||||
) -> schemas.Role:
|
||||
"""
|
||||
Create a new role.
|
||||
@@ -81,14 +74,10 @@ async def create_role(
|
||||
return await rbac_repo.create_role(role_create)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{role_id}",
|
||||
response_model=schemas.Role,
|
||||
dependencies=[Depends(has_privilege("Role.Audit"))]
|
||||
)
|
||||
@router.get("/{role_id}", response_model=schemas.Role, dependencies=[Depends(has_privilege("Role.Audit"))])
|
||||
async def get_role(
|
||||
role_id: UUID,
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
|
||||
role_id: UUID,
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
|
||||
) -> schemas.Role:
|
||||
"""
|
||||
Get a role.
|
||||
@@ -102,15 +91,9 @@ async def get_role(
|
||||
return role
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{role_id}",
|
||||
response_model=schemas.Role,
|
||||
dependencies=[Depends(has_privilege("Role.Modify"))]
|
||||
)
|
||||
@router.put("/{role_id}", response_model=schemas.Role, dependencies=[Depends(has_privilege("Role.Modify"))])
|
||||
async def update_role(
|
||||
role_id: UUID,
|
||||
role_update: schemas.RoleUpdate,
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
|
||||
role_id: UUID, role_update: schemas.RoleUpdate, rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
|
||||
) -> schemas.Role:
|
||||
"""
|
||||
Update a role.
|
||||
@@ -129,9 +112,7 @@ async def update_role(
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{role_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(has_privilege("Role.Allocate"))]
|
||||
"/{role_id}", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(has_privilege("Role.Allocate"))]
|
||||
)
|
||||
async def delete_role(
|
||||
role_id: UUID,
|
||||
@@ -157,13 +138,10 @@ async def delete_role(
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{role_id}/privileges",
|
||||
response_model=List[schemas.Privilege],
|
||||
dependencies=[Depends(has_privilege("Role.Audit"))]
|
||||
"/{role_id}/privileges", response_model=List[schemas.Privilege], dependencies=[Depends(has_privilege("Role.Audit"))]
|
||||
)
|
||||
async def get_role_privileges(
|
||||
role_id: UUID,
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
|
||||
role_id: UUID, rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
|
||||
) -> List[schemas.Privilege]:
|
||||
"""
|
||||
Get all role privileges.
|
||||
@@ -177,12 +155,10 @@ async def get_role_privileges(
|
||||
@router.put(
|
||||
"/{role_id}/privileges/{privilege_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(has_privilege("Role.Modify"))]
|
||||
dependencies=[Depends(has_privilege("Role.Modify"))],
|
||||
)
|
||||
async def add_privilege_to_role(
|
||||
role_id: UUID,
|
||||
privilege_id: UUID,
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
|
||||
role_id: UUID, privilege_id: UUID, rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
|
||||
) -> None:
|
||||
"""
|
||||
Add a privilege to a role.
|
||||
@@ -202,7 +178,7 @@ async def add_privilege_to_role(
|
||||
@router.delete(
|
||||
"/{role_id}/privileges/{privilege_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(has_privilege("Role.Modify"))]
|
||||
dependencies=[Depends(has_privilege("Role.Modify"))],
|
||||
)
|
||||
async def remove_privilege_from_role(
|
||||
role_id: UUID,
|
||||
|
||||
@@ -44,35 +44,37 @@ router = APIRouter()
|
||||
# - port ranges are read once by the PortManager singleton
|
||||
# - default admin credentials are only used to seed the users database
|
||||
# - builtin templates/appliances and the skills repository are installed at startup
|
||||
RESTART_REQUIRED = frozenset({
|
||||
"Server.host",
|
||||
"Server.port",
|
||||
"Server.protocol",
|
||||
"Server.enable_ssl",
|
||||
"Server.certfile",
|
||||
"Server.certkey",
|
||||
"Server.secrets_dir",
|
||||
"Server.images_path",
|
||||
"Server.projects_path",
|
||||
"Server.appliances_path",
|
||||
"Server.symbols_path",
|
||||
"Server.configs_path",
|
||||
"Server.resources_path",
|
||||
"Server.console_start_port_range",
|
||||
"Server.console_end_port_range",
|
||||
"Server.vnc_console_start_port_range",
|
||||
"Server.vnc_console_end_port_range",
|
||||
"Server.udp_start_port_range",
|
||||
"Server.udp_end_port_range",
|
||||
"Server.enable_builtin_templates",
|
||||
"Server.install_builtin_appliances",
|
||||
"Server.skills_repo_url",
|
||||
"Server.skills_repo_branch",
|
||||
"Server.skills_auto_update",
|
||||
"Server.ubridge_path",
|
||||
"Controller.default_admin_username",
|
||||
"Controller.default_admin_password",
|
||||
})
|
||||
RESTART_REQUIRED = frozenset(
|
||||
{
|
||||
"Server.host",
|
||||
"Server.port",
|
||||
"Server.protocol",
|
||||
"Server.enable_ssl",
|
||||
"Server.certfile",
|
||||
"Server.certkey",
|
||||
"Server.secrets_dir",
|
||||
"Server.images_path",
|
||||
"Server.projects_path",
|
||||
"Server.appliances_path",
|
||||
"Server.symbols_path",
|
||||
"Server.configs_path",
|
||||
"Server.resources_path",
|
||||
"Server.console_start_port_range",
|
||||
"Server.console_end_port_range",
|
||||
"Server.vnc_console_start_port_range",
|
||||
"Server.vnc_console_end_port_range",
|
||||
"Server.udp_start_port_range",
|
||||
"Server.udp_end_port_range",
|
||||
"Server.enable_builtin_templates",
|
||||
"Server.install_builtin_appliances",
|
||||
"Server.skills_repo_url",
|
||||
"Server.skills_repo_branch",
|
||||
"Server.skills_auto_update",
|
||||
"Server.ubridge_path",
|
||||
"Controller.default_admin_username",
|
||||
"Controller.default_admin_password",
|
||||
}
|
||||
)
|
||||
|
||||
# never expose these sections (deprecated) nor the secret managed outside
|
||||
# the configuration file; must match the response model in schemas.controller.settings
|
||||
@@ -89,9 +91,12 @@ def _current_settings_response() -> dict:
|
||||
return settings.model_dump(mode="json", exclude=_DUMP_EXCLUDE)
|
||||
|
||||
|
||||
@router.get("", response_model=schemas.SettingsResponse,
|
||||
dependencies=[Depends(has_privilege("Server.Audit"))],
|
||||
responses={401: {"model": schemas.ErrorMessage}, 403: {"model": schemas.ErrorMessage}})
|
||||
@router.get(
|
||||
"",
|
||||
response_model=schemas.SettingsResponse,
|
||||
dependencies=[Depends(has_privilege("Server.Audit"))],
|
||||
responses={401: {"model": schemas.ErrorMessage}, 403: {"model": schemas.ErrorMessage}},
|
||||
)
|
||||
async def get_server_settings() -> schemas.SettingsResponse:
|
||||
"""
|
||||
Return the server settings.
|
||||
@@ -103,15 +108,18 @@ async def get_server_settings() -> schemas.SettingsResponse:
|
||||
return schemas.SettingsResponse.model_validate(_current_settings_response())
|
||||
|
||||
|
||||
@router.put("", response_model=schemas.SettingsUpdateResponse,
|
||||
dependencies=[Depends(has_privilege("Server.Modify"))],
|
||||
responses={
|
||||
400: {"model": schemas.ErrorMessage},
|
||||
401: {"model": schemas.ErrorMessage},
|
||||
403: {"model": schemas.ErrorMessage},
|
||||
409: {"model": schemas.ErrorMessage},
|
||||
422: {"model": schemas.ErrorMessage},
|
||||
})
|
||||
@router.put(
|
||||
"",
|
||||
response_model=schemas.SettingsUpdateResponse,
|
||||
dependencies=[Depends(has_privilege("Server.Modify"))],
|
||||
responses={
|
||||
400: {"model": schemas.ErrorMessage},
|
||||
401: {"model": schemas.ErrorMessage},
|
||||
403: {"model": schemas.ErrorMessage},
|
||||
409: {"model": schemas.ErrorMessage},
|
||||
422: {"model": schemas.ErrorMessage},
|
||||
},
|
||||
)
|
||||
async def update_server_settings(settings_update: schemas.SettingsUpdate) -> schemas.SettingsUpdateResponse:
|
||||
"""
|
||||
Update the server settings and persist them to the configuration file.
|
||||
@@ -122,9 +130,7 @@ async def update_server_settings(settings_update: schemas.SettingsUpdate) -> sch
|
||||
"""
|
||||
|
||||
changes = {
|
||||
section: options
|
||||
for section, options in settings_update.model_dump(exclude_unset=True).items()
|
||||
if options
|
||||
section: options for section, options in settings_update.model_dump(exclude_unset=True).items() if options
|
||||
}
|
||||
|
||||
# masked or empty secrets mean "unchanged": never write them back
|
||||
@@ -153,8 +159,7 @@ async def update_server_settings(settings_update: schemas.SettingsUpdate) -> sch
|
||||
controller = Controller.instance()
|
||||
if controller is not None:
|
||||
controller.notification.controller_emit(
|
||||
"settings.updated",
|
||||
{"changed": changed, "restart_required": restart_required}
|
||||
"settings.updated", {"changed": changed, "restart_required": restart_required}
|
||||
)
|
||||
|
||||
data = _current_settings_response()
|
||||
|
||||
@@ -53,11 +53,10 @@ def dep_project(project_id: UUID) -> Project:
|
||||
"",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.Snapshot,
|
||||
dependencies=[Depends(has_privilege("Snapshot.Allocate"))]
|
||||
dependencies=[Depends(has_privilege("Snapshot.Allocate"))],
|
||||
)
|
||||
async def create_snapshot(
|
||||
snapshot_data: schemas.SnapshotCreate,
|
||||
project: Project = Depends(dep_project)
|
||||
snapshot_data: schemas.SnapshotCreate, project: Project = Depends(dep_project)
|
||||
) -> schemas.Snapshot:
|
||||
"""
|
||||
Create a new snapshot of a project.
|
||||
@@ -73,7 +72,7 @@ async def create_snapshot(
|
||||
"",
|
||||
response_model=List[schemas.Snapshot],
|
||||
response_model_exclude_unset=True,
|
||||
dependencies=[Depends(has_privilege("Snapshot.Audit"))]
|
||||
dependencies=[Depends(has_privilege("Snapshot.Audit"))],
|
||||
)
|
||||
def get_snapshots(project: Project = Depends(dep_project)) -> List[schemas.Snapshot]:
|
||||
"""
|
||||
@@ -87,14 +86,10 @@ def get_snapshots(project: Project = Depends(dep_project)) -> List[schemas.Snaps
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{snapshot_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(has_privilege("Snapshot.Allocate"))]
|
||||
"/{snapshot_id}", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(has_privilege("Snapshot.Allocate"))]
|
||||
)
|
||||
async def delete_snapshot(
|
||||
snapshot_id: UUID,
|
||||
project: Project = Depends(dep_project),
|
||||
rbac_repo=Depends(get_repository(RbacRepository))
|
||||
snapshot_id: UUID, project: Project = Depends(dep_project), rbac_repo=Depends(get_repository(RbacRepository))
|
||||
) -> None:
|
||||
"""
|
||||
Delete a snapshot.
|
||||
@@ -110,7 +105,7 @@ async def delete_snapshot(
|
||||
"/{snapshot_id}/restore",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.Project,
|
||||
dependencies=[Depends(has_privilege("Snapshot.Restore"))]
|
||||
dependencies=[Depends(has_privilege("Snapshot.Restore"))],
|
||||
)
|
||||
async def restore_snapshot(snapshot_id: UUID, project: Project = Depends(dep_project)) -> schemas.Project:
|
||||
"""
|
||||
|
||||
@@ -40,10 +40,7 @@ log = logging.getLogger(__name__)
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get(
|
||||
"",
|
||||
dependencies=[Depends(has_privilege("Symbol.Audit"))]
|
||||
)
|
||||
@router.get("", dependencies=[Depends(has_privilege("Symbol.Audit"))])
|
||||
def get_symbols() -> List[dict]:
|
||||
"""
|
||||
Return all symbols.
|
||||
@@ -58,7 +55,7 @@ def get_symbols() -> List[dict]:
|
||||
@router.get(
|
||||
"/{symbol_id:path}/raw",
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find symbol"}},
|
||||
dependencies=[Depends(has_privilege("Symbol.Audit"))]
|
||||
dependencies=[Depends(has_privilege("Symbol.Audit"))],
|
||||
)
|
||||
async def get_symbol(symbol_id: str, request: Request) -> Response:
|
||||
"""
|
||||
@@ -74,7 +71,7 @@ async def get_symbol(symbol_id: str, request: Request) -> Response:
|
||||
symbol,
|
||||
headers={
|
||||
"Access-Control-Allow-Origin": request.headers.get("origin", "*"),
|
||||
}
|
||||
},
|
||||
)
|
||||
except (KeyError, OSError) as e:
|
||||
raise ControllerNotFoundError(f"Could not get symbol file: {e}")
|
||||
@@ -83,7 +80,7 @@ async def get_symbol(symbol_id: str, request: Request) -> Response:
|
||||
@router.get(
|
||||
"/{symbol_id:path}/dimensions",
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find symbol"}},
|
||||
dependencies=[Depends(has_privilege("Symbol.Audit"))]
|
||||
dependencies=[Depends(has_privilege("Symbol.Audit"))],
|
||||
)
|
||||
async def get_symbol_dimensions(symbol_id: str) -> dict:
|
||||
"""
|
||||
@@ -116,7 +113,7 @@ def get_default_symbols() -> dict:
|
||||
@router.post(
|
||||
"/{symbol_id:path}/raw",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(has_privilege("Symbol.Allocate"))]
|
||||
dependencies=[Depends(has_privilege("Symbol.Allocate"))],
|
||||
)
|
||||
async def upload_symbol(symbol_id: str, request: Request) -> None:
|
||||
"""
|
||||
@@ -141,7 +138,7 @@ async def upload_symbol(symbol_id: str, request: Request) -> None:
|
||||
@router.delete(
|
||||
"/{symbol_id:path}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(has_privilege("Symbol.Allocate"))]
|
||||
dependencies=[Depends(has_privilege("Symbol.Allocate"))],
|
||||
)
|
||||
async def delete_symbol(symbol_id: str) -> None:
|
||||
"""
|
||||
|
||||
@@ -52,11 +52,11 @@ router = APIRouter(responses=responses)
|
||||
"",
|
||||
response_model=schemas.Template,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
dependencies=[Depends(has_privilege("Template.Allocate"))]
|
||||
dependencies=[Depends(has_privilege("Template.Allocate"))],
|
||||
)
|
||||
async def create_template(
|
||||
template_create: schemas.TemplateCreate,
|
||||
templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository))
|
||||
templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository)),
|
||||
) -> schemas.Template:
|
||||
"""
|
||||
Create a new template.
|
||||
@@ -73,7 +73,7 @@ async def create_template(
|
||||
response_model=schemas.Template,
|
||||
response_model_exclude_unset=True,
|
||||
dependencies=[Depends(get_current_active_user)],
|
||||
#dependencies=[Depends(has_privilege("Template.Audit"))] # FIXME: this is a temporary workaround due to a bug in the web-ui
|
||||
# dependencies=[Depends(has_privilege("Template.Audit"))] # FIXME: this is a temporary workaround due to a bug in the web-ui
|
||||
)
|
||||
async def get_template(
|
||||
template_id: UUID,
|
||||
@@ -102,7 +102,7 @@ async def get_template(
|
||||
"/{template_id}",
|
||||
response_model=schemas.Template,
|
||||
response_model_exclude_unset=True,
|
||||
dependencies=[Depends(has_privilege("Template.Modify"))]
|
||||
dependencies=[Depends(has_privilege("Template.Modify"))],
|
||||
)
|
||||
async def update_template(
|
||||
template_id: UUID,
|
||||
@@ -119,16 +119,14 @@ async def update_template(
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{template_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(has_privilege("Template.Allocate"))]
|
||||
"/{template_id}", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(has_privilege("Template.Allocate"))]
|
||||
)
|
||||
async def delete_template(
|
||||
template_id: UUID,
|
||||
prune_images: Optional[bool] = False,
|
||||
templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository)),
|
||||
images_repo: RbacRepository = Depends(get_repository(ImagesRepository)),
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
|
||||
template_id: UUID,
|
||||
prune_images: Optional[bool] = False,
|
||||
templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository)),
|
||||
images_repo: RbacRepository = Depends(get_repository(ImagesRepository)),
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
|
||||
) -> None:
|
||||
"""
|
||||
Delete a template.
|
||||
@@ -152,7 +150,8 @@ async def delete_template(
|
||||
# the template being deleted is still in the database at this
|
||||
# point, exclude it from the other-templates check
|
||||
other_templates = [
|
||||
template for template in await images_repo.get_image_templates(image.image_id)
|
||||
template
|
||||
for template in await images_repo.get_image_templates(image.image_id)
|
||||
if str(template.template_id) != str(template_id)
|
||||
]
|
||||
if other_templates:
|
||||
@@ -163,7 +162,9 @@ async def delete_template(
|
||||
referenced_filenames = controller.collect_referenced_image_filenames()
|
||||
if image.filename in referenced_filenames:
|
||||
project_names = controller.find_projects_using_image(image.filename)
|
||||
raise ControllerError(f"Image '{image.path}' is used by one or more projects: {', '.join(project_names)}")
|
||||
raise ControllerError(
|
||||
f"Image '{image.path}' is used by one or more projects: {', '.join(project_names)}"
|
||||
)
|
||||
images_to_prune.append(image)
|
||||
|
||||
await TemplatesService(templates_repo).delete_template(template_id)
|
||||
@@ -186,12 +187,12 @@ async def delete_template(
|
||||
response_model=List[schemas.Template],
|
||||
response_model_exclude_unset=True,
|
||||
dependencies=[Depends(get_current_active_user)],
|
||||
#dependencies=[Depends(has_privilege("Template.Audit"))] # FIXME: this is a temporary workaround due to a bug in the web-ui
|
||||
# dependencies=[Depends(has_privilege("Template.Audit"))] # FIXME: this is a temporary workaround due to a bug in the web-ui
|
||||
)
|
||||
async def get_templates(
|
||||
templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository)),
|
||||
current_user: schemas.User = Depends(get_current_active_user),
|
||||
tags: Optional[List[str]] = Query(None, description="Filter by tags (e.g. tags=vendor:cisco&tags=model:7200)")
|
||||
templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository)),
|
||||
current_user: schemas.User = Depends(get_current_active_user),
|
||||
tags: Optional[List[str]] = Query(None, description="Filter by tags (e.g. tags=vendor:cisco&tags=model:7200)"),
|
||||
) -> List[schemas.Template]:
|
||||
"""
|
||||
Return all templates.
|
||||
@@ -239,10 +240,10 @@ async def get_templates(
|
||||
"/{template_id}/duplicate",
|
||||
response_model=schemas.Template,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
dependencies=[Depends(has_privilege("Template.Allocate"))]
|
||||
dependencies=[Depends(has_privilege("Template.Allocate"))],
|
||||
)
|
||||
async def duplicate_template(
|
||||
template_id: UUID, templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository))
|
||||
template_id: UUID, templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository))
|
||||
) -> schemas.Template:
|
||||
"""
|
||||
Duplicate a template.
|
||||
@@ -253,10 +254,8 @@ async def duplicate_template(
|
||||
template = await TemplatesService(templates_repo).duplicate_template(template_id)
|
||||
return template
|
||||
|
||||
@router.get(
|
||||
"/{template_id}/base-config/{filename}",
|
||||
dependencies=[Depends(has_privilege("Template.Audit"))]
|
||||
)
|
||||
|
||||
@router.get("/{template_id}/base-config/{filename}", dependencies=[Depends(has_privilege("Template.Audit"))])
|
||||
async def get_base_config(
|
||||
template_id: UUID,
|
||||
filename: str,
|
||||
@@ -267,17 +266,10 @@ async def get_base_config(
|
||||
await service.get_template(template_id)
|
||||
content = service.get_file(str(template_id), filename)
|
||||
|
||||
return {
|
||||
"template_id": str(template_id),
|
||||
"filename": os.path.basename(filename),
|
||||
"content": content
|
||||
}
|
||||
return {"template_id": str(template_id), "filename": os.path.basename(filename), "content": content}
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{template_id}/base-config/{filename}",
|
||||
dependencies=[Depends(has_privilege("Template.Modify"))]
|
||||
)
|
||||
@router.put("/{template_id}/base-config/{filename}", dependencies=[Depends(has_privilege("Template.Modify"))])
|
||||
async def update_base_config(
|
||||
template_id: UUID,
|
||||
filename: str,
|
||||
@@ -291,17 +283,10 @@ async def update_base_config(
|
||||
await service.get_template(template_id)
|
||||
service.update_file(str(template_id), filename, body["content"])
|
||||
|
||||
return {
|
||||
"template_id": str(template_id),
|
||||
"filename": os.path.basename(filename),
|
||||
"content": body["content"]
|
||||
}
|
||||
return {"template_id": str(template_id), "filename": os.path.basename(filename), "content": body["content"]}
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{template_id}/base-configs",
|
||||
dependencies=[Depends(has_privilege("Template.Audit"))]
|
||||
)
|
||||
@router.get("/{template_id}/base-configs", dependencies=[Depends(has_privilege("Template.Audit"))])
|
||||
async def list_base_configs(
|
||||
template_id: UUID,
|
||||
templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository)),
|
||||
|
||||
@@ -144,8 +144,8 @@ async def refresh_access_token(
|
||||
|
||||
@router.post("/logout", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def logout(
|
||||
current_user: schemas.User = Depends(get_current_active_user),
|
||||
users_repo: UsersRepository = Depends(get_repository(UsersRepository)),
|
||||
current_user: schemas.User = Depends(get_current_active_user),
|
||||
users_repo: UsersRepository = Depends(get_repository(UsersRepository)),
|
||||
) -> None:
|
||||
"""
|
||||
Logout the current user by revoking all existing tokens.
|
||||
@@ -165,9 +165,9 @@ async def get_logged_in_user(current_user: schemas.User = Depends(get_current_ac
|
||||
|
||||
@router.put("/me", response_model=schemas.User)
|
||||
async def update_logged_in_user(
|
||||
user_update: schemas.LoggedInUserUpdate,
|
||||
current_user: schemas.User = Depends(get_current_active_user),
|
||||
users_repo: UsersRepository = Depends(get_repository(UsersRepository))
|
||||
user_update: schemas.LoggedInUserUpdate,
|
||||
current_user: schemas.User = Depends(get_current_active_user),
|
||||
users_repo: UsersRepository = Depends(get_repository(UsersRepository)),
|
||||
) -> schemas.User:
|
||||
"""
|
||||
Update the current active user.
|
||||
@@ -179,14 +179,8 @@ async def update_logged_in_user(
|
||||
return await users_repo.update_user(current_user.user_id, user_update)
|
||||
|
||||
|
||||
@router.get(
|
||||
"",
|
||||
response_model=List[schemas.User],
|
||||
dependencies=[Depends(has_privilege("User.Audit"))]
|
||||
)
|
||||
async def get_users(
|
||||
users_repo: UsersRepository = Depends(get_repository(UsersRepository))
|
||||
) -> List[schemas.User]:
|
||||
@router.get("", response_model=List[schemas.User], dependencies=[Depends(has_privilege("User.Audit"))])
|
||||
async def get_users(users_repo: UsersRepository = Depends(get_repository(UsersRepository))) -> List[schemas.User]:
|
||||
"""
|
||||
Get all users.
|
||||
|
||||
@@ -200,11 +194,10 @@ async def get_users(
|
||||
"",
|
||||
response_model=schemas.User,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
dependencies=[Depends(has_privilege("User.Allocate"))]
|
||||
dependencies=[Depends(has_privilege("User.Allocate"))],
|
||||
)
|
||||
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.
|
||||
@@ -221,14 +214,10 @@ async def create_user(
|
||||
return await users_repo.create_user(user_create)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{user_id}",
|
||||
response_model=schemas.User,
|
||||
dependencies=[Depends(has_privilege("User.Audit"))]
|
||||
)
|
||||
@router.get("/{user_id}", response_model=schemas.User, dependencies=[Depends(has_privilege("User.Audit"))])
|
||||
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 a user.
|
||||
@@ -242,15 +231,11 @@ async def get_user(
|
||||
return user
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{user_id}",
|
||||
response_model=schemas.User,
|
||||
dependencies=[Depends(has_privilege("User.Modify"))]
|
||||
)
|
||||
@router.put("/{user_id}", response_model=schemas.User, dependencies=[Depends(has_privilege("User.Modify"))])
|
||||
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 a user.
|
||||
@@ -271,14 +256,12 @@ async def update_user(
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{user_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(has_privilege("User.Allocate"))]
|
||||
"/{user_id}", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(has_privilege("User.Allocate"))]
|
||||
)
|
||||
async def delete_user(
|
||||
user_id: UUID,
|
||||
users_repo: UsersRepository = Depends(get_repository(UsersRepository)),
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
|
||||
user_id: UUID,
|
||||
users_repo: UsersRepository = Depends(get_repository(UsersRepository)),
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
|
||||
) -> None:
|
||||
"""
|
||||
Delete a user.
|
||||
@@ -300,13 +283,10 @@ async def delete_user(
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{user_id}/groups",
|
||||
response_model=List[schemas.UserGroup],
|
||||
dependencies=[Depends(has_privilege("Group.Audit"))]
|
||||
"/{user_id}/groups", response_model=List[schemas.UserGroup], dependencies=[Depends(has_privilege("Group.Audit"))]
|
||||
)
|
||||
async def get_user_memberships(
|
||||
user_id: UUID,
|
||||
users_repo: UsersRepository = Depends(get_repository(UsersRepository))
|
||||
user_id: UUID, users_repo: UsersRepository = Depends(get_repository(UsersRepository))
|
||||
) -> List[schemas.UserGroup]:
|
||||
"""
|
||||
Get user memberships.
|
||||
|
||||
Reference in new issue
Block a user