mirror of
https://github.com/GNS3/gns3-server.git
synced 2024-12-23 14:42:28 +00:00
Merge pull request #2300 from GNS3/fix/1468
Fix compute authentication for websocket endpoints
This commit is contained in:
commit
f3ad97c398
@ -3,4 +3,5 @@ flake8==6.1.0
|
|||||||
pytest-timeout==2.2.0
|
pytest-timeout==2.2.0
|
||||||
pytest-asyncio==0.21.1
|
pytest-asyncio==0.21.1
|
||||||
requests==2.31.0
|
requests==2.31.0
|
||||||
httpx==0.25.0
|
httpx==0.24.1 # version 0.24.1 is required by httpx_ws
|
||||||
|
httpx_ws==0.4.2
|
||||||
|
@ -199,14 +199,12 @@ compute_api.include_router(
|
|||||||
|
|
||||||
compute_api.include_router(
|
compute_api.include_router(
|
||||||
docker_nodes.router,
|
docker_nodes.router,
|
||||||
dependencies=[Depends(compute_authentication)],
|
|
||||||
prefix="/projects/{project_id}/docker/nodes",
|
prefix="/projects/{project_id}/docker/nodes",
|
||||||
tags=["Docker nodes"]
|
tags=["Docker nodes"]
|
||||||
)
|
)
|
||||||
|
|
||||||
compute_api.include_router(
|
compute_api.include_router(
|
||||||
dynamips_nodes.router,
|
dynamips_nodes.router,
|
||||||
dependencies=[Depends(compute_authentication)],
|
|
||||||
prefix="/projects/{project_id}/dynamips/nodes",
|
prefix="/projects/{project_id}/dynamips/nodes",
|
||||||
tags=["Dynamips nodes"]
|
tags=["Dynamips nodes"]
|
||||||
)
|
)
|
||||||
@ -234,7 +232,6 @@ compute_api.include_router(
|
|||||||
|
|
||||||
compute_api.include_router(
|
compute_api.include_router(
|
||||||
iou_nodes.router,
|
iou_nodes.router,
|
||||||
dependencies=[Depends(compute_authentication)],
|
|
||||||
prefix="/projects/{project_id}/iou/nodes",
|
prefix="/projects/{project_id}/iou/nodes",
|
||||||
tags=["IOU nodes"])
|
tags=["IOU nodes"])
|
||||||
|
|
||||||
@ -247,28 +244,24 @@ compute_api.include_router(
|
|||||||
|
|
||||||
compute_api.include_router(
|
compute_api.include_router(
|
||||||
qemu_nodes.router,
|
qemu_nodes.router,
|
||||||
dependencies=[Depends(compute_authentication)],
|
|
||||||
prefix="/projects/{project_id}/qemu/nodes",
|
prefix="/projects/{project_id}/qemu/nodes",
|
||||||
tags=["Qemu nodes"]
|
tags=["Qemu nodes"]
|
||||||
)
|
)
|
||||||
|
|
||||||
compute_api.include_router(
|
compute_api.include_router(
|
||||||
virtualbox_nodes.router,
|
virtualbox_nodes.router,
|
||||||
dependencies=[Depends(compute_authentication)],
|
|
||||||
prefix="/projects/{project_id}/virtualbox/nodes",
|
prefix="/projects/{project_id}/virtualbox/nodes",
|
||||||
tags=["VirtualBox nodes"]
|
tags=["VirtualBox nodes"]
|
||||||
)
|
)
|
||||||
|
|
||||||
compute_api.include_router(
|
compute_api.include_router(
|
||||||
vmware_nodes.router,
|
vmware_nodes.router,
|
||||||
dependencies=[Depends(compute_authentication)],
|
|
||||||
prefix="/projects/{project_id}/vmware/nodes",
|
prefix="/projects/{project_id}/vmware/nodes",
|
||||||
tags=["VMware nodes"]
|
tags=["VMware nodes"]
|
||||||
)
|
)
|
||||||
|
|
||||||
compute_api.include_router(
|
compute_api.include_router(
|
||||||
vpcs_nodes.router,
|
vpcs_nodes.router,
|
||||||
dependencies=[Depends(compute_authentication)],
|
|
||||||
prefix="/projects/{project_id}/vpcs/nodes",
|
prefix="/projects/{project_id}/vpcs/nodes",
|
||||||
tags=["VPCS nodes"]
|
tags=["VPCS nodes"]
|
||||||
)
|
)
|
||||||
|
@ -15,12 +15,17 @@
|
|||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import secrets
|
import secrets
|
||||||
|
import base64
|
||||||
|
import binascii
|
||||||
|
import logging
|
||||||
|
|
||||||
from fastapi import Depends, HTTPException, status
|
from fastapi import Depends, HTTPException, WebSocket, status
|
||||||
from fastapi.security import HTTPBasic, HTTPBasicCredentials
|
from fastapi.security import HTTPBasic, HTTPBasicCredentials
|
||||||
|
from fastapi.security.utils import get_authorization_scheme_param
|
||||||
from gns3server.config import Config
|
from gns3server.config import Config
|
||||||
from typing import Optional
|
from typing import Optional, Union
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
security = HTTPBasic()
|
security = HTTPBasic()
|
||||||
|
|
||||||
|
|
||||||
@ -35,3 +40,44 @@ def compute_authentication(credentials: Optional[HTTPBasicCredentials] = Depends
|
|||||||
detail="Invalid compute username or password",
|
detail="Invalid compute username or password",
|
||||||
headers={"WWW-Authenticate": "Basic"},
|
headers={"WWW-Authenticate": "Basic"},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def ws_compute_authentication(websocket: WebSocket) -> Union[None, WebSocket]:
|
||||||
|
"""
|
||||||
|
"""
|
||||||
|
|
||||||
|
await websocket.accept()
|
||||||
|
|
||||||
|
# handle basic HTTP authentication
|
||||||
|
invalid_user_credentials_exc = HTTPException(
|
||||||
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
detail="Could not validate credentials",
|
||||||
|
headers={"WWW-Authenticate": "Basic"},
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
authorization = websocket.headers.get("Authorization")
|
||||||
|
scheme, param = get_authorization_scheme_param(authorization)
|
||||||
|
if not authorization or scheme.lower() != "basic":
|
||||||
|
raise invalid_user_credentials_exc
|
||||||
|
try:
|
||||||
|
data = base64.b64decode(param).decode("ascii")
|
||||||
|
except (ValueError, UnicodeDecodeError, binascii.Error):
|
||||||
|
raise invalid_user_credentials_exc
|
||||||
|
|
||||||
|
username, separator, password = data.partition(":")
|
||||||
|
if not separator:
|
||||||
|
raise invalid_user_credentials_exc
|
||||||
|
|
||||||
|
server_settings = Config.instance().settings.Server
|
||||||
|
username = secrets.compare_digest(username, server_settings.compute_username)
|
||||||
|
password = secrets.compare_digest(password, server_settings.compute_password.get_secret_value())
|
||||||
|
if not (username and password):
|
||||||
|
raise invalid_user_credentials_exc
|
||||||
|
|
||||||
|
except HTTPException as e:
|
||||||
|
err_msg = f"Could not authenticate while connecting to compute WebSocket: {e.detail}"
|
||||||
|
websocket_error = {"action": "log.error", "event": {"message": err_msg}}
|
||||||
|
await websocket.send_json(websocket_error)
|
||||||
|
log.error(err_msg)
|
||||||
|
return await websocket.close(code=1008)
|
||||||
|
return websocket
|
||||||
|
@ -20,15 +20,18 @@ API routes for Docker nodes.
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from fastapi import APIRouter, WebSocket, Depends, Body, Response, status
|
from fastapi import APIRouter, WebSocket, Depends, Body, status
|
||||||
from fastapi.encoders import jsonable_encoder
|
from fastapi.encoders import jsonable_encoder
|
||||||
from fastapi.responses import StreamingResponse
|
from fastapi.responses import StreamingResponse
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
from gns3server import schemas
|
from gns3server import schemas
|
||||||
from gns3server.compute.docker import Docker
|
from gns3server.compute.docker import Docker
|
||||||
from gns3server.compute.docker.docker_vm import DockerVM
|
from gns3server.compute.docker.docker_vm import DockerVM
|
||||||
|
|
||||||
|
from .dependencies.authentication import compute_authentication, ws_compute_authentication
|
||||||
|
|
||||||
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or Docker node"}}
|
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or Docker node"}}
|
||||||
|
|
||||||
router = APIRouter(responses=responses)
|
router = APIRouter(responses=responses)
|
||||||
@ -49,6 +52,7 @@ def dep_node(project_id: UUID, node_id: UUID) -> DockerVM:
|
|||||||
response_model=schemas.Docker,
|
response_model=schemas.Docker,
|
||||||
status_code=status.HTTP_201_CREATED,
|
status_code=status.HTTP_201_CREATED,
|
||||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Docker node"}},
|
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Docker node"}},
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
)
|
)
|
||||||
async def create_docker_node(project_id: UUID, node_data: schemas.DockerCreate) -> schemas.Docker:
|
async def create_docker_node(project_id: UUID, node_data: schemas.DockerCreate) -> schemas.Docker:
|
||||||
"""
|
"""
|
||||||
@ -85,7 +89,11 @@ async def create_docker_node(project_id: UUID, node_data: schemas.DockerCreate)
|
|||||||
return container.asdict()
|
return container.asdict()
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{node_id}", response_model=schemas.Docker)
|
@router.get(
|
||||||
|
"/{node_id}",
|
||||||
|
response_model=schemas.Docker,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
def get_docker_node(node: DockerVM = Depends(dep_node)) -> schemas.Docker:
|
def get_docker_node(node: DockerVM = Depends(dep_node)) -> schemas.Docker:
|
||||||
"""
|
"""
|
||||||
Return a Docker node.
|
Return a Docker node.
|
||||||
@ -94,7 +102,11 @@ def get_docker_node(node: DockerVM = Depends(dep_node)) -> schemas.Docker:
|
|||||||
return node.asdict()
|
return node.asdict()
|
||||||
|
|
||||||
|
|
||||||
@router.put("/{node_id}", response_model=schemas.Docker)
|
@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:
|
async def update_docker_node(node_data: schemas.DockerUpdate, node: DockerVM = Depends(dep_node)) -> schemas.Docker:
|
||||||
"""
|
"""
|
||||||
Update a Docker node.
|
Update a Docker node.
|
||||||
@ -131,7 +143,11 @@ async def update_docker_node(node_data: schemas.DockerUpdate, node: DockerVM = D
|
|||||||
return node.asdict()
|
return node.asdict()
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post(
|
||||||
|
"/{node_id}/start",
|
||||||
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def start_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
async def start_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
||||||
"""
|
"""
|
||||||
Start a Docker node.
|
Start a Docker node.
|
||||||
@ -140,7 +156,11 @@ async def start_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
|||||||
await node.start()
|
await node.start()
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post(
|
||||||
|
"/{node_id}/stop",
|
||||||
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def stop_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
async def stop_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
||||||
"""
|
"""
|
||||||
Stop a Docker node.
|
Stop a Docker node.
|
||||||
@ -149,7 +169,11 @@ async def stop_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
|||||||
await node.stop()
|
await node.stop()
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post(
|
||||||
|
"/{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:
|
async def suspend_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
||||||
"""
|
"""
|
||||||
Suspend a Docker node.
|
Suspend a Docker node.
|
||||||
@ -158,7 +182,11 @@ async def suspend_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
|||||||
await node.pause()
|
await node.pause()
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post(
|
||||||
|
"/{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:
|
async def reload_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
||||||
"""
|
"""
|
||||||
Reload a Docker node.
|
Reload a Docker node.
|
||||||
@ -167,7 +195,11 @@ async def reload_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
|||||||
await node.restart()
|
await node.restart()
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/pause", status_code=status.HTTP_204_NO_CONTENT)
|
@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:
|
async def pause_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
||||||
"""
|
"""
|
||||||
Pause a Docker node.
|
Pause a Docker node.
|
||||||
@ -176,7 +208,11 @@ async def pause_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
|||||||
await node.pause()
|
await node.pause()
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/unpause", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post(
|
||||||
|
"/{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:
|
async def unpause_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
||||||
"""
|
"""
|
||||||
Unpause a Docker node.
|
Unpause a Docker node.
|
||||||
@ -185,7 +221,11 @@ async def unpause_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
|||||||
await node.unpause()
|
await node.unpause()
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
@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:
|
async def delete_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
||||||
"""
|
"""
|
||||||
Delete a Docker node.
|
Delete a Docker node.
|
||||||
@ -194,7 +234,12 @@ async def delete_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
|||||||
await node.delete()
|
await node.delete()
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/duplicate", response_model=schemas.Docker, status_code=status.HTTP_201_CREATED)
|
@router.post(
|
||||||
|
"/{node_id}/duplicate",
|
||||||
|
response_model=schemas.Docker,
|
||||||
|
status_code=status.HTTP_201_CREATED,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def duplicate_docker_node(
|
async def duplicate_docker_node(
|
||||||
destination_node_id: UUID = Body(..., embed=True),
|
destination_node_id: UUID = Body(..., embed=True),
|
||||||
node: DockerVM = Depends(dep_node)
|
node: DockerVM = Depends(dep_node)
|
||||||
@ -211,6 +256,7 @@ async def duplicate_docker_node(
|
|||||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||||
status_code=status.HTTP_201_CREATED,
|
status_code=status.HTTP_201_CREATED,
|
||||||
response_model=schemas.UDPNIO,
|
response_model=schemas.UDPNIO,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
)
|
)
|
||||||
async def create_docker_node_nio(
|
async def create_docker_node_nio(
|
||||||
adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: DockerVM = Depends(dep_node)
|
adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: DockerVM = Depends(dep_node)
|
||||||
@ -229,6 +275,7 @@ async def create_docker_node_nio(
|
|||||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||||
status_code=status.HTTP_201_CREATED,
|
status_code=status.HTTP_201_CREATED,
|
||||||
response_model=schemas.UDPNIO,
|
response_model=schemas.UDPNIO,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
)
|
)
|
||||||
async def update_docker_node_nio(
|
async def update_docker_node_nio(
|
||||||
adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: DockerVM = Depends(dep_node)
|
adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: DockerVM = Depends(dep_node)
|
||||||
@ -245,7 +292,11 @@ async def update_docker_node_nio(
|
|||||||
return nio.asdict()
|
return nio.asdict()
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
|
@router.delete(
|
||||||
|
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||||
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def delete_docker_node_nio(
|
async def delete_docker_node_nio(
|
||||||
adapter_number: int,
|
adapter_number: int,
|
||||||
port_number: int,
|
port_number: int,
|
||||||
@ -259,7 +310,10 @@ async def delete_docker_node_nio(
|
|||||||
await node.adapter_remove_nio_binding(adapter_number)
|
await node.adapter_remove_nio_binding(adapter_number)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
@router.post(
|
||||||
|
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start",
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def start_docker_node_capture(
|
async def start_docker_node_capture(
|
||||||
adapter_number: int,
|
adapter_number: int,
|
||||||
port_number: int,
|
port_number: int,
|
||||||
@ -278,7 +332,8 @@ async def start_docker_node_capture(
|
|||||||
|
|
||||||
@router.post(
|
@router.post(
|
||||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop",
|
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop",
|
||||||
status_code=status.HTTP_204_NO_CONTENT
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
)
|
)
|
||||||
async def stop_docker_node_capture(
|
async def stop_docker_node_capture(
|
||||||
adapter_number: int,
|
adapter_number: int,
|
||||||
@ -293,7 +348,10 @@ async def stop_docker_node_capture(
|
|||||||
await node.stop_capture(adapter_number)
|
await node.stop_capture(adapter_number)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
|
@router.get(
|
||||||
|
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream",
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def stream_pcap_file(
|
async def stream_pcap_file(
|
||||||
adapter_number: int,
|
adapter_number: int,
|
||||||
port_number: int,
|
port_number: int,
|
||||||
@ -310,15 +368,23 @@ async def stream_pcap_file(
|
|||||||
|
|
||||||
|
|
||||||
@router.websocket("/{node_id}/console/ws")
|
@router.websocket("/{node_id}/console/ws")
|
||||||
async def console_ws(websocket: WebSocket, node: DockerVM = Depends(dep_node)) -> None:
|
async def console_ws(
|
||||||
|
websocket: Union[None, WebSocket] = Depends(ws_compute_authentication),
|
||||||
|
node: DockerVM = Depends(dep_node)
|
||||||
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Console WebSocket.
|
Console WebSocket.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.start_websocket_console(websocket)
|
if websocket:
|
||||||
|
await node.start_websocket_console(websocket)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post(
|
||||||
|
"/{node_id}/console/reset",
|
||||||
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def reset_console(node: DockerVM = Depends(dep_node)) -> None:
|
async def reset_console(node: DockerVM = Depends(dep_node)) -> None:
|
||||||
|
|
||||||
await node.reset_console()
|
await node.reset_console()
|
||||||
|
@ -20,16 +20,18 @@ API routes for Dynamips nodes.
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from fastapi import APIRouter, WebSocket, Depends, Response, status
|
from fastapi import APIRouter, WebSocket, Depends, status
|
||||||
from fastapi.encoders import jsonable_encoder
|
from fastapi.encoders import jsonable_encoder
|
||||||
from fastapi.responses import StreamingResponse
|
from fastapi.responses import StreamingResponse
|
||||||
from typing import List
|
from typing import List, Union
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from gns3server.compute.dynamips import Dynamips
|
from gns3server.compute.dynamips import Dynamips
|
||||||
from gns3server.compute.dynamips.nodes.router import Router
|
from gns3server.compute.dynamips.nodes.router import Router
|
||||||
from gns3server import schemas
|
from gns3server import schemas
|
||||||
|
|
||||||
|
from .dependencies.authentication import compute_authentication, ws_compute_authentication
|
||||||
|
|
||||||
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or Dynamips node"}}
|
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or Dynamips node"}}
|
||||||
|
|
||||||
router = APIRouter(responses=responses)
|
router = APIRouter(responses=responses)
|
||||||
@ -53,6 +55,7 @@ def dep_node(project_id: UUID, node_id: UUID) -> Router:
|
|||||||
response_model=schemas.Dynamips,
|
response_model=schemas.Dynamips,
|
||||||
status_code=status.HTTP_201_CREATED,
|
status_code=status.HTTP_201_CREATED,
|
||||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Dynamips node"}},
|
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Dynamips node"}},
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
)
|
)
|
||||||
async def create_router(project_id: UUID, node_data: schemas.DynamipsCreate) -> schemas.Dynamips:
|
async def create_router(project_id: UUID, node_data: schemas.DynamipsCreate) -> schemas.Dynamips:
|
||||||
"""
|
"""
|
||||||
@ -84,7 +87,11 @@ async def create_router(project_id: UUID, node_data: schemas.DynamipsCreate) ->
|
|||||||
return vm.asdict()
|
return vm.asdict()
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{node_id}", response_model=schemas.Dynamips)
|
@router.get(
|
||||||
|
"/{node_id}",
|
||||||
|
response_model=schemas.Dynamips,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
def get_router(node: Router = Depends(dep_node)) -> schemas.Dynamips:
|
def get_router(node: Router = Depends(dep_node)) -> schemas.Dynamips:
|
||||||
"""
|
"""
|
||||||
Return Dynamips router.
|
Return Dynamips router.
|
||||||
@ -93,7 +100,11 @@ def get_router(node: Router = Depends(dep_node)) -> schemas.Dynamips:
|
|||||||
return node.asdict()
|
return node.asdict()
|
||||||
|
|
||||||
|
|
||||||
@router.put("/{node_id}", response_model=schemas.Dynamips)
|
@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:
|
async def update_router(node_data: schemas.DynamipsUpdate, node: Router = Depends(dep_node)) -> schemas.Dynamips:
|
||||||
"""
|
"""
|
||||||
Update a Dynamips router.
|
Update a Dynamips router.
|
||||||
@ -104,7 +115,11 @@ async def update_router(node_data: schemas.DynamipsUpdate, node: Router = Depend
|
|||||||
return node.asdict()
|
return node.asdict()
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
@router.delete(
|
||||||
|
"/{node_id}",
|
||||||
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def delete_router(node: Router = Depends(dep_node)) -> None:
|
async def delete_router(node: Router = Depends(dep_node)) -> None:
|
||||||
"""
|
"""
|
||||||
Delete a Dynamips router.
|
Delete a Dynamips router.
|
||||||
@ -113,7 +128,11 @@ async def delete_router(node: Router = Depends(dep_node)) -> None:
|
|||||||
await Dynamips.instance().delete_node(node.id)
|
await Dynamips.instance().delete_node(node.id)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
@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:
|
async def start_router(node: Router = Depends(dep_node)) -> None:
|
||||||
"""
|
"""
|
||||||
Start a Dynamips router.
|
Start a Dynamips router.
|
||||||
@ -126,7 +145,11 @@ async def start_router(node: Router = Depends(dep_node)) -> None:
|
|||||||
await node.start()
|
await node.start()
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post(
|
||||||
|
"/{node_id}/stop",
|
||||||
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def stop_router(node: Router = Depends(dep_node)) -> None:
|
async def stop_router(node: Router = Depends(dep_node)) -> None:
|
||||||
"""
|
"""
|
||||||
Stop a Dynamips router.
|
Stop a Dynamips router.
|
||||||
@ -135,13 +158,21 @@ async def stop_router(node: Router = Depends(dep_node)) -> None:
|
|||||||
await node.stop()
|
await node.stop()
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post(
|
||||||
|
"/{node_id}/suspend",
|
||||||
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def suspend_router(node: Router = Depends(dep_node)) -> None:
|
async def suspend_router(node: Router = Depends(dep_node)) -> None:
|
||||||
|
|
||||||
await node.suspend()
|
await node.suspend()
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/resume", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post(
|
||||||
|
"/{node_id}/resume",
|
||||||
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def resume_router(node: Router = Depends(dep_node)) -> None:
|
async def resume_router(node: Router = Depends(dep_node)) -> None:
|
||||||
"""
|
"""
|
||||||
Resume a suspended Dynamips router.
|
Resume a suspended Dynamips router.
|
||||||
@ -150,7 +181,11 @@ async def resume_router(node: Router = Depends(dep_node)) -> None:
|
|||||||
await node.resume()
|
await node.resume()
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post(
|
||||||
|
"/{node_id}/reload",
|
||||||
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def reload_router(node: Router = Depends(dep_node)) -> None:
|
async def reload_router(node: Router = Depends(dep_node)) -> None:
|
||||||
"""
|
"""
|
||||||
Reload a suspended Dynamips router.
|
Reload a suspended Dynamips router.
|
||||||
@ -163,6 +198,7 @@ async def reload_router(node: Router = Depends(dep_node)) -> None:
|
|||||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||||
status_code=status.HTTP_201_CREATED,
|
status_code=status.HTTP_201_CREATED,
|
||||||
response_model=schemas.UDPNIO,
|
response_model=schemas.UDPNIO,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
)
|
)
|
||||||
async def create_nio(
|
async def create_nio(
|
||||||
adapter_number: int,
|
adapter_number: int,
|
||||||
@ -183,6 +219,7 @@ async def create_nio(
|
|||||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||||
status_code=status.HTTP_201_CREATED,
|
status_code=status.HTTP_201_CREATED,
|
||||||
response_model=schemas.UDPNIO,
|
response_model=schemas.UDPNIO,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
)
|
)
|
||||||
async def update_nio(
|
async def update_nio(
|
||||||
adapter_number: int,
|
adapter_number: int,
|
||||||
@ -201,7 +238,11 @@ async def update_nio(
|
|||||||
return nio.asdict()
|
return nio.asdict()
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
|
@router.delete(
|
||||||
|
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||||
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def delete_nio(adapter_number: int, port_number: int, node: Router = Depends(dep_node)) -> None:
|
async def delete_nio(adapter_number: int, port_number: int, node: Router = Depends(dep_node)) -> None:
|
||||||
"""
|
"""
|
||||||
Delete a NIO (Network Input/Output) from the node.
|
Delete a NIO (Network Input/Output) from the node.
|
||||||
@ -211,7 +252,10 @@ async def delete_nio(adapter_number: int, port_number: int, node: Router = Depen
|
|||||||
await nio.delete()
|
await nio.delete()
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
@router.post(
|
||||||
|
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start",
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def start_capture(
|
async def start_capture(
|
||||||
adapter_number: int,
|
adapter_number: int,
|
||||||
port_number: int,
|
port_number: int,
|
||||||
@ -228,7 +272,9 @@ async def start_capture(
|
|||||||
|
|
||||||
|
|
||||||
@router.post(
|
@router.post(
|
||||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT
|
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop",
|
||||||
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
)
|
)
|
||||||
async def stop_capture(adapter_number: int, port_number: int, node: Router = Depends(dep_node)) -> None:
|
async def stop_capture(adapter_number: int, port_number: int, node: Router = Depends(dep_node)) -> None:
|
||||||
"""
|
"""
|
||||||
@ -238,7 +284,10 @@ async def stop_capture(adapter_number: int, port_number: int, node: Router = Dep
|
|||||||
await node.stop_capture(adapter_number, port_number)
|
await node.stop_capture(adapter_number, port_number)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
|
@router.get(
|
||||||
|
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream",
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def stream_pcap_file(
|
async def stream_pcap_file(
|
||||||
adapter_number: int,
|
adapter_number: int,
|
||||||
port_number: int,
|
port_number: int,
|
||||||
@ -253,7 +302,10 @@ async def stream_pcap_file(
|
|||||||
return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap")
|
return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap")
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{node_id}/idlepc_proposals")
|
@router.get(
|
||||||
|
"/{node_id}/idlepc_proposals",
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def get_idlepcs(node: Router = Depends(dep_node)) -> List[str]:
|
async def get_idlepcs(node: Router = Depends(dep_node)) -> List[str]:
|
||||||
"""
|
"""
|
||||||
Retrieve Dynamips idle-pc proposals
|
Retrieve Dynamips idle-pc proposals
|
||||||
@ -263,7 +315,10 @@ async def get_idlepcs(node: Router = Depends(dep_node)) -> List[str]:
|
|||||||
return await node.get_idle_pc_prop()
|
return await node.get_idle_pc_prop()
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{node_id}/auto_idlepc")
|
@router.get(
|
||||||
|
"/{node_id}/auto_idlepc",
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def get_auto_idlepc(node: Router = Depends(dep_node)) -> dict:
|
async def get_auto_idlepc(node: Router = Depends(dep_node)) -> dict:
|
||||||
"""
|
"""
|
||||||
Get an automatically guessed best idle-pc value.
|
Get an automatically guessed best idle-pc value.
|
||||||
@ -273,7 +328,12 @@ async def get_auto_idlepc(node: Router = Depends(dep_node)) -> dict:
|
|||||||
return {"idlepc": idlepc}
|
return {"idlepc": idlepc}
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/duplicate", response_model=schemas.Dynamips, status_code=status.HTTP_201_CREATED)
|
@router.post(
|
||||||
|
"/{node_id}/duplicate",
|
||||||
|
response_model=schemas.Dynamips,
|
||||||
|
status_code=status.HTTP_201_CREATED,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def duplicate_router(destination_node_id: UUID, node: Router = Depends(dep_node)) -> schemas.Dynamips:
|
async def duplicate_router(destination_node_id: UUID, node: Router = Depends(dep_node)) -> schemas.Dynamips:
|
||||||
"""
|
"""
|
||||||
Duplicate a router.
|
Duplicate a router.
|
||||||
@ -284,15 +344,24 @@ async def duplicate_router(destination_node_id: UUID, node: Router = Depends(dep
|
|||||||
|
|
||||||
|
|
||||||
@router.websocket("/{node_id}/console/ws")
|
@router.websocket("/{node_id}/console/ws")
|
||||||
async def console_ws(websocket: WebSocket, node: Router = Depends(dep_node)) -> None:
|
async def console_ws(
|
||||||
|
websocket: Union[None, WebSocket] = Depends(ws_compute_authentication),
|
||||||
|
node: Router = Depends(dep_node)
|
||||||
|
|
||||||
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Console WebSocket.
|
Console WebSocket.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.start_websocket_console(websocket)
|
if websocket:
|
||||||
|
await node.start_websocket_console(websocket)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post(
|
||||||
|
"/{node_id}/console/reset",
|
||||||
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def reset_console(node: Router = Depends(dep_node)) -> None:
|
async def reset_console(node: Router = Depends(dep_node)) -> None:
|
||||||
|
|
||||||
await node.reset_console()
|
await node.reset_console()
|
||||||
|
@ -30,6 +30,8 @@ from gns3server import schemas
|
|||||||
from gns3server.compute.iou import IOU
|
from gns3server.compute.iou import IOU
|
||||||
from gns3server.compute.iou.iou_vm import IOUVM
|
from gns3server.compute.iou.iou_vm import IOUVM
|
||||||
|
|
||||||
|
from .dependencies.authentication import compute_authentication, ws_compute_authentication
|
||||||
|
|
||||||
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or IOU node"}}
|
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or IOU node"}}
|
||||||
|
|
||||||
router = APIRouter(responses=responses)
|
router = APIRouter(responses=responses)
|
||||||
@ -50,6 +52,7 @@ def dep_node(project_id: UUID, node_id: UUID) -> IOUVM:
|
|||||||
response_model=schemas.IOU,
|
response_model=schemas.IOU,
|
||||||
status_code=status.HTTP_201_CREATED,
|
status_code=status.HTTP_201_CREATED,
|
||||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create IOU node"}},
|
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create IOU node"}},
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
)
|
)
|
||||||
async def create_iou_node(project_id: UUID, node_data: schemas.IOUCreate) -> schemas.IOU:
|
async def create_iou_node(project_id: UUID, node_data: schemas.IOUCreate) -> schemas.IOU:
|
||||||
"""
|
"""
|
||||||
@ -82,7 +85,11 @@ async def create_iou_node(project_id: UUID, node_data: schemas.IOUCreate) -> sch
|
|||||||
return vm.asdict()
|
return vm.asdict()
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{node_id}", response_model=schemas.IOU)
|
@router.get(
|
||||||
|
"/{node_id}",
|
||||||
|
response_model=schemas.IOU,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
def get_iou_node(node: IOUVM = Depends(dep_node)) -> schemas.IOU:
|
def get_iou_node(node: IOUVM = Depends(dep_node)) -> schemas.IOU:
|
||||||
"""
|
"""
|
||||||
Return an IOU node.
|
Return an IOU node.
|
||||||
@ -91,7 +98,11 @@ def get_iou_node(node: IOUVM = Depends(dep_node)) -> schemas.IOU:
|
|||||||
return node.asdict()
|
return node.asdict()
|
||||||
|
|
||||||
|
|
||||||
@router.put("/{node_id}", response_model=schemas.IOU)
|
@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:
|
async def update_iou_node(node_data: schemas.IOUUpdate, node: IOUVM = Depends(dep_node)) -> schemas.IOU:
|
||||||
"""
|
"""
|
||||||
Update an IOU node.
|
Update an IOU node.
|
||||||
@ -112,7 +123,11 @@ async def update_iou_node(node_data: schemas.IOUUpdate, node: IOUVM = Depends(de
|
|||||||
return node.asdict()
|
return node.asdict()
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
@router.delete(
|
||||||
|
"/{node_id}",
|
||||||
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def delete_iou_node(node: IOUVM = Depends(dep_node)) -> None:
|
async def delete_iou_node(node: IOUVM = Depends(dep_node)) -> None:
|
||||||
"""
|
"""
|
||||||
Delete an IOU node.
|
Delete an IOU node.
|
||||||
@ -121,7 +136,12 @@ async def delete_iou_node(node: IOUVM = Depends(dep_node)) -> None:
|
|||||||
await IOU.instance().delete_node(node.id)
|
await IOU.instance().delete_node(node.id)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/duplicate", response_model=schemas.IOU, status_code=status.HTTP_201_CREATED)
|
@router.post(
|
||||||
|
"/{node_id}/duplicate",
|
||||||
|
response_model=schemas.IOU,
|
||||||
|
status_code=status.HTTP_201_CREATED,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def duplicate_iou_node(
|
async def duplicate_iou_node(
|
||||||
destination_node_id: UUID = Body(..., embed=True),
|
destination_node_id: UUID = Body(..., embed=True),
|
||||||
node: IOUVM = Depends(dep_node)
|
node: IOUVM = Depends(dep_node)
|
||||||
@ -134,7 +154,11 @@ async def duplicate_iou_node(
|
|||||||
return new_node.asdict()
|
return new_node.asdict()
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post(
|
||||||
|
"/{node_id}/start",
|
||||||
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def start_iou_node(start_data: schemas.IOUStart, node: IOUVM = Depends(dep_node)) -> None:
|
async def start_iou_node(start_data: schemas.IOUStart, node: IOUVM = Depends(dep_node)) -> None:
|
||||||
"""
|
"""
|
||||||
Start an IOU node.
|
Start an IOU node.
|
||||||
@ -148,7 +172,11 @@ async def start_iou_node(start_data: schemas.IOUStart, node: IOUVM = Depends(dep
|
|||||||
await node.start()
|
await node.start()
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post(
|
||||||
|
"/{node_id}/stop",
|
||||||
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def stop_iou_node(node: IOUVM = Depends(dep_node)) -> None:
|
async def stop_iou_node(node: IOUVM = Depends(dep_node)) -> None:
|
||||||
"""
|
"""
|
||||||
Stop an IOU node.
|
Stop an IOU node.
|
||||||
@ -157,7 +185,11 @@ async def stop_iou_node(node: IOUVM = Depends(dep_node)) -> None:
|
|||||||
await node.stop()
|
await node.stop()
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post(
|
||||||
|
"/{node_id}/stop",
|
||||||
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
def suspend_iou_node(node: IOUVM = Depends(dep_node)) -> None:
|
def suspend_iou_node(node: IOUVM = Depends(dep_node)) -> None:
|
||||||
"""
|
"""
|
||||||
Suspend an IOU node.
|
Suspend an IOU node.
|
||||||
@ -167,7 +199,11 @@ def suspend_iou_node(node: IOUVM = Depends(dep_node)) -> None:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post(
|
||||||
|
"/{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:
|
async def reload_iou_node(node: IOUVM = Depends(dep_node)) -> None:
|
||||||
"""
|
"""
|
||||||
Reload an IOU node.
|
Reload an IOU node.
|
||||||
@ -180,6 +216,7 @@ async def reload_iou_node(node: IOUVM = Depends(dep_node)) -> None:
|
|||||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||||
status_code=status.HTTP_201_CREATED,
|
status_code=status.HTTP_201_CREATED,
|
||||||
response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
|
response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
)
|
)
|
||||||
async def create_iou_node_nio(
|
async def create_iou_node_nio(
|
||||||
adapter_number: int,
|
adapter_number: int,
|
||||||
@ -200,6 +237,7 @@ async def create_iou_node_nio(
|
|||||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||||
status_code=status.HTTP_201_CREATED,
|
status_code=status.HTTP_201_CREATED,
|
||||||
response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
|
response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
)
|
)
|
||||||
async def update_iou_node_nio(
|
async def update_iou_node_nio(
|
||||||
adapter_number: int,
|
adapter_number: int,
|
||||||
@ -218,7 +256,11 @@ async def update_iou_node_nio(
|
|||||||
return nio.asdict()
|
return nio.asdict()
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
|
@router.delete(
|
||||||
|
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||||
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def delete_iou_node_nio(adapter_number: int, port_number: int, node: IOUVM = Depends(dep_node)) -> None:
|
async def delete_iou_node_nio(adapter_number: int, port_number: int, node: IOUVM = Depends(dep_node)) -> None:
|
||||||
"""
|
"""
|
||||||
Delete a NIO (Network Input/Output) from the node.
|
Delete a NIO (Network Input/Output) from the node.
|
||||||
@ -227,7 +269,10 @@ async def delete_iou_node_nio(adapter_number: int, port_number: int, node: IOUVM
|
|||||||
await node.adapter_remove_nio_binding(adapter_number, port_number)
|
await node.adapter_remove_nio_binding(adapter_number, port_number)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
@router.post(
|
||||||
|
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start",
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def start_iou_node_capture(
|
async def start_iou_node_capture(
|
||||||
adapter_number: int,
|
adapter_number: int,
|
||||||
port_number: int,
|
port_number: int,
|
||||||
@ -244,7 +289,9 @@ async def start_iou_node_capture(
|
|||||||
|
|
||||||
|
|
||||||
@router.post(
|
@router.post(
|
||||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT
|
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop",
|
||||||
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
)
|
)
|
||||||
async def stop_iou_node_capture(adapter_number: int, port_number: int, node: IOUVM = Depends(dep_node)) -> None:
|
async def stop_iou_node_capture(adapter_number: int, port_number: int, node: IOUVM = Depends(dep_node)) -> None:
|
||||||
"""
|
"""
|
||||||
@ -254,7 +301,10 @@ async def stop_iou_node_capture(adapter_number: int, port_number: int, node: IOU
|
|||||||
await node.stop_capture(adapter_number, port_number)
|
await node.stop_capture(adapter_number, port_number)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
|
@router.get(
|
||||||
|
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream",
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def stream_pcap_file(
|
async def stream_pcap_file(
|
||||||
adapter_number: int,
|
adapter_number: int,
|
||||||
port_number: int,
|
port_number: int,
|
||||||
@ -269,16 +319,26 @@ async def stream_pcap_file(
|
|||||||
return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap")
|
return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap")
|
||||||
|
|
||||||
|
|
||||||
@router.websocket("/{node_id}/console/ws")
|
@router.websocket(
|
||||||
async def console_ws(websocket: WebSocket, node: IOUVM = Depends(dep_node)) -> None:
|
"/{node_id}/console/ws",
|
||||||
|
)
|
||||||
|
async def console_ws(
|
||||||
|
websocket: Union[None, WebSocket] = Depends(ws_compute_authentication),
|
||||||
|
node: IOUVM = Depends(dep_node)
|
||||||
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Console WebSocket.
|
Console WebSocket.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.start_websocket_console(websocket)
|
if websocket:
|
||||||
|
await node.start_websocket_console(websocket)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post(
|
||||||
|
"/{node_id}/console/reset",
|
||||||
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def reset_console(node: IOUVM = Depends(dep_node)) -> None:
|
async def reset_console(node: IOUVM = Depends(dep_node)) -> None:
|
||||||
|
|
||||||
await node.reset_console()
|
await node.reset_console()
|
||||||
|
@ -18,14 +18,13 @@
|
|||||||
API routes for compute notifications.
|
API routes for compute notifications.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import base64
|
|
||||||
import binascii
|
|
||||||
|
|
||||||
from fastapi import APIRouter, WebSocket, WebSocketDisconnect, status, HTTPException
|
from fastapi import APIRouter, Depends, WebSocket, WebSocketDisconnect
|
||||||
from fastapi.security.utils import get_authorization_scheme_param
|
from typing import Union
|
||||||
from websockets.exceptions import ConnectionClosed, WebSocketException
|
from websockets.exceptions import ConnectionClosed, WebSocketException
|
||||||
|
|
||||||
from gns3server.compute.notification_manager import NotificationManager
|
from gns3server.compute.notification_manager import NotificationManager
|
||||||
|
from .dependencies.authentication import ws_compute_authentication
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
@ -35,53 +34,27 @@ router = APIRouter()
|
|||||||
|
|
||||||
|
|
||||||
@router.websocket("/notifications/ws")
|
@router.websocket("/notifications/ws")
|
||||||
async def project_ws_notifications(websocket: WebSocket) -> None:
|
async def project_ws_notifications(websocket: Union[None, WebSocket] = Depends(ws_compute_authentication)) -> None:
|
||||||
"""
|
"""
|
||||||
Receive project notifications about the project from WebSocket.
|
Receive project notifications about the project from WebSocket.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await websocket.accept()
|
if websocket:
|
||||||
|
log.info(f"New client {websocket.client.host}:{websocket.client.port} has connected to compute WebSocket")
|
||||||
# handle basic HTTP authentication
|
|
||||||
invalid_user_credentials_exc = HTTPException(
|
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
||||||
detail="Invalid authentication credentials",
|
|
||||||
headers={"WWW-Authenticate": "Basic"},
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
authorization = websocket.headers.get("Authorization")
|
|
||||||
scheme, param = get_authorization_scheme_param(authorization)
|
|
||||||
if not authorization or scheme.lower() != "basic":
|
|
||||||
raise invalid_user_credentials_exc
|
|
||||||
try:
|
try:
|
||||||
data = base64.b64decode(param).decode("ascii")
|
with NotificationManager.instance().queue() as queue:
|
||||||
except (ValueError, UnicodeDecodeError, binascii.Error):
|
while True:
|
||||||
raise invalid_user_credentials_exc
|
notification = await queue.get_json(5)
|
||||||
username, separator, password = data.partition(":")
|
await websocket.send_text(notification)
|
||||||
if not separator:
|
except (ConnectionClosed, WebSocketDisconnect):
|
||||||
raise invalid_user_credentials_exc
|
log.info(f"Client {websocket.client.host}:{websocket.client.port} has disconnected from compute WebSocket")
|
||||||
except invalid_user_credentials_exc as e:
|
except WebSocketException as e:
|
||||||
websocket_error = {"action": "log.error", "event": {"message": f"Could not authenticate while connecting to "
|
log.warning(f"Error while sending to controller event to WebSocket client: {e}")
|
||||||
f"compute WebSocket: {e.detail}"}}
|
finally:
|
||||||
await websocket.send_json(websocket_error)
|
try:
|
||||||
return await websocket.close(code=1008)
|
await websocket.close()
|
||||||
|
except OSError:
|
||||||
log.info(f"New client {websocket.client.host}:{websocket.client.port} has connected to compute WebSocket")
|
pass # ignore OSError: [Errno 107] Transport endpoint is not connected
|
||||||
try:
|
|
||||||
with NotificationManager.instance().queue() as queue:
|
|
||||||
while True:
|
|
||||||
notification = await queue.get_json(5)
|
|
||||||
await websocket.send_text(notification)
|
|
||||||
except (ConnectionClosed, WebSocketDisconnect):
|
|
||||||
log.info(f"Client {websocket.client.host}:{websocket.client.port} has disconnected from compute WebSocket")
|
|
||||||
except WebSocketException as e:
|
|
||||||
log.warning(f"Error while sending to controller event to WebSocket client: {e}")
|
|
||||||
finally:
|
|
||||||
try:
|
|
||||||
await websocket.close()
|
|
||||||
except OSError:
|
|
||||||
pass # ignore OSError: [Errno 107] Transport endpoint is not connected
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
@ -20,15 +20,17 @@ API routes for Qemu nodes.
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from fastapi import APIRouter, WebSocket, Depends, Body, Path, Response, status
|
from fastapi import APIRouter, WebSocket, Depends, Body, Path, status
|
||||||
from fastapi.encoders import jsonable_encoder
|
from fastapi.encoders import jsonable_encoder
|
||||||
from fastapi.responses import StreamingResponse
|
from fastapi.responses import StreamingResponse
|
||||||
|
from typing import Union
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from gns3server import schemas
|
from gns3server import schemas
|
||||||
from gns3server.compute.qemu import Qemu
|
from gns3server.compute.qemu import Qemu
|
||||||
from gns3server.compute.qemu.qemu_vm import QemuVM
|
from gns3server.compute.qemu.qemu_vm import QemuVM
|
||||||
|
|
||||||
|
from .dependencies.authentication import compute_authentication, ws_compute_authentication
|
||||||
|
|
||||||
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or Qemu node"}}
|
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or Qemu node"}}
|
||||||
|
|
||||||
@ -50,6 +52,7 @@ def dep_node(project_id: UUID, node_id: UUID) -> QemuVM:
|
|||||||
response_model=schemas.Qemu,
|
response_model=schemas.Qemu,
|
||||||
status_code=status.HTTP_201_CREATED,
|
status_code=status.HTTP_201_CREATED,
|
||||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Qemu node"}},
|
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Qemu node"}},
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
)
|
)
|
||||||
async def create_qemu_node(project_id: UUID, node_data: schemas.QemuCreate) -> schemas.Qemu:
|
async def create_qemu_node(project_id: UUID, node_data: schemas.QemuCreate) -> schemas.Qemu:
|
||||||
"""
|
"""
|
||||||
@ -78,7 +81,11 @@ async def create_qemu_node(project_id: UUID, node_data: schemas.QemuCreate) -> s
|
|||||||
return vm.asdict()
|
return vm.asdict()
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{node_id}", response_model=schemas.Qemu)
|
@router.get(
|
||||||
|
"/{node_id}",
|
||||||
|
response_model=schemas.Qemu,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
def get_qemu_node(node: QemuVM = Depends(dep_node)) -> schemas.Qemu:
|
def get_qemu_node(node: QemuVM = Depends(dep_node)) -> schemas.Qemu:
|
||||||
"""
|
"""
|
||||||
Return a Qemu node.
|
Return a Qemu node.
|
||||||
@ -87,7 +94,11 @@ def get_qemu_node(node: QemuVM = Depends(dep_node)) -> schemas.Qemu:
|
|||||||
return node.asdict()
|
return node.asdict()
|
||||||
|
|
||||||
|
|
||||||
@router.put("/{node_id}", response_model=schemas.Qemu)
|
@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:
|
async def update_qemu_node(node_data: schemas.QemuUpdate, node: QemuVM = Depends(dep_node)) -> schemas.Qemu:
|
||||||
"""
|
"""
|
||||||
Update a Qemu node.
|
Update a Qemu node.
|
||||||
@ -103,7 +114,11 @@ async def update_qemu_node(node_data: schemas.QemuUpdate, node: QemuVM = Depends
|
|||||||
return node.asdict()
|
return node.asdict()
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
@router.delete(
|
||||||
|
"/{node_id}",
|
||||||
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def delete_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
|
async def delete_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
|
||||||
"""
|
"""
|
||||||
Delete a Qemu node.
|
Delete a Qemu node.
|
||||||
@ -112,7 +127,12 @@ async def delete_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
|
|||||||
await Qemu.instance().delete_node(node.id)
|
await Qemu.instance().delete_node(node.id)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/duplicate", response_model=schemas.Qemu, status_code=status.HTTP_201_CREATED)
|
@router.post(
|
||||||
|
"/{node_id}/duplicate",
|
||||||
|
response_model=schemas.Qemu,
|
||||||
|
status_code=status.HTTP_201_CREATED,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def duplicate_qemu_node(
|
async def duplicate_qemu_node(
|
||||||
destination_node_id: UUID = Body(..., embed=True),
|
destination_node_id: UUID = Body(..., embed=True),
|
||||||
node: QemuVM = Depends(dep_node)
|
node: QemuVM = Depends(dep_node)
|
||||||
@ -127,7 +147,8 @@ async def duplicate_qemu_node(
|
|||||||
|
|
||||||
@router.post(
|
@router.post(
|
||||||
"/{node_id}/disk_image/{disk_name}",
|
"/{node_id}/disk_image/{disk_name}",
|
||||||
status_code=status.HTTP_204_NO_CONTENT
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
)
|
)
|
||||||
async def create_qemu_disk_image(
|
async def create_qemu_disk_image(
|
||||||
disk_name: str,
|
disk_name: str,
|
||||||
@ -144,7 +165,8 @@ async def create_qemu_disk_image(
|
|||||||
|
|
||||||
@router.put(
|
@router.put(
|
||||||
"/{node_id}/disk_image/{disk_name}",
|
"/{node_id}/disk_image/{disk_name}",
|
||||||
status_code=status.HTTP_204_NO_CONTENT
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
)
|
)
|
||||||
async def update_qemu_disk_image(
|
async def update_qemu_disk_image(
|
||||||
disk_name: str,
|
disk_name: str,
|
||||||
@ -161,7 +183,8 @@ async def update_qemu_disk_image(
|
|||||||
|
|
||||||
@router.delete(
|
@router.delete(
|
||||||
"/{node_id}/disk_image/{disk_name}",
|
"/{node_id}/disk_image/{disk_name}",
|
||||||
status_code=status.HTTP_204_NO_CONTENT
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
)
|
)
|
||||||
async def delete_qemu_disk_image(
|
async def delete_qemu_disk_image(
|
||||||
disk_name: str,
|
disk_name: str,
|
||||||
@ -174,7 +197,11 @@ async def delete_qemu_disk_image(
|
|||||||
node.delete_disk_image(disk_name)
|
node.delete_disk_image(disk_name)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
@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:
|
async def start_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
|
||||||
"""
|
"""
|
||||||
Start a Qemu node.
|
Start a Qemu node.
|
||||||
@ -183,7 +210,11 @@ async def start_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
|
|||||||
await node.start()
|
await node.start()
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post(
|
||||||
|
"/{node_id}/stop",
|
||||||
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def stop_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
|
async def stop_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
|
||||||
"""
|
"""
|
||||||
Stop a Qemu node.
|
Stop a Qemu node.
|
||||||
@ -192,7 +223,11 @@ async def stop_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
|
|||||||
await node.stop()
|
await node.stop()
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post(
|
||||||
|
"/{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:
|
async def reload_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
|
||||||
"""
|
"""
|
||||||
Reload a Qemu node.
|
Reload a Qemu node.
|
||||||
@ -201,7 +236,11 @@ async def reload_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
|
|||||||
await node.reload()
|
await node.reload()
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post(
|
||||||
|
"/{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:
|
async def suspend_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
|
||||||
"""
|
"""
|
||||||
Suspend a Qemu node.
|
Suspend a Qemu node.
|
||||||
@ -210,7 +249,11 @@ async def suspend_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
|
|||||||
await node.suspend()
|
await node.suspend()
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/resume", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post(
|
||||||
|
"/{node_id}/resume",
|
||||||
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def resume_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
|
async def resume_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
|
||||||
"""
|
"""
|
||||||
Resume a Qemu node.
|
Resume a Qemu node.
|
||||||
@ -223,6 +266,7 @@ async def resume_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
|
|||||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||||
status_code=status.HTTP_201_CREATED,
|
status_code=status.HTTP_201_CREATED,
|
||||||
response_model=schemas.UDPNIO,
|
response_model=schemas.UDPNIO,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
)
|
)
|
||||||
async def create_qemu_node_nio(
|
async def create_qemu_node_nio(
|
||||||
*,
|
*,
|
||||||
@ -245,6 +289,7 @@ async def create_qemu_node_nio(
|
|||||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||||
status_code=status.HTTP_201_CREATED,
|
status_code=status.HTTP_201_CREATED,
|
||||||
response_model=schemas.UDPNIO,
|
response_model=schemas.UDPNIO,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
)
|
)
|
||||||
async def update_qemu_node_nio(
|
async def update_qemu_node_nio(
|
||||||
*,
|
*,
|
||||||
@ -267,7 +312,11 @@ async def update_qemu_node_nio(
|
|||||||
return nio.asdict()
|
return nio.asdict()
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
|
@router.delete(
|
||||||
|
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||||
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def delete_qemu_node_nio(
|
async def delete_qemu_node_nio(
|
||||||
adapter_number: int,
|
adapter_number: int,
|
||||||
port_number: int = Path(..., ge=0, le=0),
|
port_number: int = Path(..., ge=0, le=0),
|
||||||
@ -281,7 +330,10 @@ async def delete_qemu_node_nio(
|
|||||||
await node.adapter_remove_nio_binding(adapter_number)
|
await node.adapter_remove_nio_binding(adapter_number)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
@router.post(
|
||||||
|
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start",
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def start_qemu_node_capture(
|
async def start_qemu_node_capture(
|
||||||
*,
|
*,
|
||||||
adapter_number: int,
|
adapter_number: int,
|
||||||
@ -300,7 +352,9 @@ async def start_qemu_node_capture(
|
|||||||
|
|
||||||
|
|
||||||
@router.post(
|
@router.post(
|
||||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT
|
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop",
|
||||||
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
)
|
)
|
||||||
async def stop_qemu_node_capture(
|
async def stop_qemu_node_capture(
|
||||||
adapter_number: int,
|
adapter_number: int,
|
||||||
@ -315,7 +369,10 @@ async def stop_qemu_node_capture(
|
|||||||
await node.stop_capture(adapter_number)
|
await node.stop_capture(adapter_number)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
|
@router.get(
|
||||||
|
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream",
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def stream_pcap_file(
|
async def stream_pcap_file(
|
||||||
adapter_number: int,
|
adapter_number: int,
|
||||||
port_number: int = Path(..., ge=0, le=0),
|
port_number: int = Path(..., ge=0, le=0),
|
||||||
@ -330,16 +387,26 @@ async def stream_pcap_file(
|
|||||||
return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap")
|
return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap")
|
||||||
|
|
||||||
|
|
||||||
@router.websocket("/{node_id}/console/ws")
|
@router.websocket(
|
||||||
async def console_ws(websocket: WebSocket, node: QemuVM = Depends(dep_node)) -> None:
|
"/{node_id}/console/ws"
|
||||||
|
)
|
||||||
|
async def console_ws(
|
||||||
|
websocket: Union[None, WebSocket] = Depends(ws_compute_authentication),
|
||||||
|
node: QemuVM = Depends(dep_node)
|
||||||
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Console WebSocket.
|
Console WebSocket.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.start_websocket_console(websocket)
|
if websocket:
|
||||||
|
await node.start_websocket_console(websocket)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post(
|
||||||
|
"/{node_id}/console/reset",
|
||||||
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def reset_console(node: QemuVM = Depends(dep_node)) -> None:
|
async def reset_console(node: QemuVM = Depends(dep_node)) -> None:
|
||||||
|
|
||||||
await node.reset_console()
|
await node.reset_console()
|
||||||
|
@ -20,16 +20,19 @@ API routes for VirtualBox nodes.
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from fastapi import APIRouter, WebSocket, Depends, Path, Response, status
|
from fastapi import APIRouter, WebSocket, Depends, Path, status
|
||||||
from fastapi.encoders import jsonable_encoder
|
from fastapi.encoders import jsonable_encoder
|
||||||
from fastapi.responses import StreamingResponse
|
from fastapi.responses import StreamingResponse
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
from gns3server import schemas
|
from gns3server import schemas
|
||||||
from gns3server.compute.virtualbox import VirtualBox
|
from gns3server.compute.virtualbox import VirtualBox
|
||||||
from gns3server.compute.virtualbox.virtualbox_error import VirtualBoxError
|
from gns3server.compute.virtualbox.virtualbox_error import VirtualBoxError
|
||||||
from gns3server.compute.virtualbox.virtualbox_vm import VirtualBoxVM
|
from gns3server.compute.virtualbox.virtualbox_vm import VirtualBoxVM
|
||||||
|
|
||||||
|
from .dependencies.authentication import compute_authentication, ws_compute_authentication
|
||||||
|
|
||||||
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or VirtualBox node"}}
|
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or VirtualBox node"}}
|
||||||
|
|
||||||
router = APIRouter(responses=responses, deprecated=True)
|
router = APIRouter(responses=responses, deprecated=True)
|
||||||
@ -50,6 +53,7 @@ def dep_node(project_id: UUID, node_id: UUID) -> VirtualBoxVM:
|
|||||||
response_model=schemas.VirtualBox,
|
response_model=schemas.VirtualBox,
|
||||||
status_code=status.HTTP_201_CREATED,
|
status_code=status.HTTP_201_CREATED,
|
||||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create VirtualBox node"}},
|
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create VirtualBox node"}},
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
)
|
)
|
||||||
async def create_virtualbox_node(project_id: UUID, node_data: schemas.VirtualBoxCreate) -> schemas.VirtualBox:
|
async def create_virtualbox_node(project_id: UUID, node_data: schemas.VirtualBoxCreate) -> schemas.VirtualBox:
|
||||||
"""
|
"""
|
||||||
@ -82,7 +86,11 @@ async def create_virtualbox_node(project_id: UUID, node_data: schemas.VirtualBox
|
|||||||
return vm.asdict()
|
return vm.asdict()
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{node_id}", response_model=schemas.VirtualBox)
|
@router.get(
|
||||||
|
"/{node_id}",
|
||||||
|
response_model=schemas.VirtualBox,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
def get_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> schemas.VirtualBox:
|
def get_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> schemas.VirtualBox:
|
||||||
"""
|
"""
|
||||||
Return a VirtualBox node.
|
Return a VirtualBox node.
|
||||||
@ -91,7 +99,11 @@ def get_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> schemas.Virtu
|
|||||||
return node.asdict()
|
return node.asdict()
|
||||||
|
|
||||||
|
|
||||||
@router.put("/{node_id}", response_model=schemas.VirtualBox)
|
@router.put(
|
||||||
|
"/{node_id}",
|
||||||
|
response_model=schemas.VirtualBox,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def update_virtualbox_node(
|
async def update_virtualbox_node(
|
||||||
node_data: schemas.VirtualBoxUpdate,
|
node_data: schemas.VirtualBoxUpdate,
|
||||||
node: VirtualBoxVM = Depends(dep_node)
|
node: VirtualBoxVM = Depends(dep_node)
|
||||||
@ -136,7 +148,11 @@ async def update_virtualbox_node(
|
|||||||
return node.asdict()
|
return node.asdict()
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
@router.delete(
|
||||||
|
"/{node_id}",
|
||||||
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def delete_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
|
async def delete_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
|
||||||
"""
|
"""
|
||||||
Delete a VirtualBox node.
|
Delete a VirtualBox node.
|
||||||
@ -145,7 +161,11 @@ async def delete_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None
|
|||||||
await VirtualBox.instance().delete_node(node.id)
|
await VirtualBox.instance().delete_node(node.id)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
@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:
|
async def start_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
|
||||||
"""
|
"""
|
||||||
Start a VirtualBox node.
|
Start a VirtualBox node.
|
||||||
@ -154,7 +174,11 @@ async def start_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
|
|||||||
await node.start()
|
await node.start()
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post(
|
||||||
|
"/{node_id}/stop",
|
||||||
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def stop_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
|
async def stop_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
|
||||||
"""
|
"""
|
||||||
Stop a VirtualBox node.
|
Stop a VirtualBox node.
|
||||||
@ -163,7 +187,11 @@ async def stop_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
|
|||||||
await node.stop()
|
await node.stop()
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post(
|
||||||
|
"/{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:
|
async def suspend_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
|
||||||
"""
|
"""
|
||||||
Suspend a VirtualBox node.
|
Suspend a VirtualBox node.
|
||||||
@ -172,7 +200,11 @@ async def suspend_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> Non
|
|||||||
await node.suspend()
|
await node.suspend()
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/resume", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post(
|
||||||
|
"/{node_id}/resume",
|
||||||
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def resume_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
|
async def resume_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
|
||||||
"""
|
"""
|
||||||
Resume a VirtualBox node.
|
Resume a VirtualBox node.
|
||||||
@ -181,7 +213,11 @@ async def resume_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None
|
|||||||
await node.resume()
|
await node.resume()
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post(
|
||||||
|
"/{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:
|
async def reload_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
|
||||||
"""
|
"""
|
||||||
Reload a VirtualBox node.
|
Reload a VirtualBox node.
|
||||||
@ -194,6 +230,7 @@ async def reload_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None
|
|||||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||||
status_code=status.HTTP_201_CREATED,
|
status_code=status.HTTP_201_CREATED,
|
||||||
response_model=schemas.UDPNIO,
|
response_model=schemas.UDPNIO,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
)
|
)
|
||||||
async def create_virtualbox_node_nio(
|
async def create_virtualbox_node_nio(
|
||||||
*,
|
*,
|
||||||
@ -216,6 +253,7 @@ async def create_virtualbox_node_nio(
|
|||||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||||
status_code=status.HTTP_201_CREATED,
|
status_code=status.HTTP_201_CREATED,
|
||||||
response_model=schemas.UDPNIO,
|
response_model=schemas.UDPNIO,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
)
|
)
|
||||||
async def update_virtualbox_node_nio(
|
async def update_virtualbox_node_nio(
|
||||||
*,
|
*,
|
||||||
@ -238,7 +276,11 @@ async def update_virtualbox_node_nio(
|
|||||||
return nio.asdict()
|
return nio.asdict()
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
|
@router.delete(
|
||||||
|
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||||
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def delete_virtualbox_node_nio(
|
async def delete_virtualbox_node_nio(
|
||||||
adapter_number: int,
|
adapter_number: int,
|
||||||
port_number: int = Path(..., ge=0, le=0),
|
port_number: int = Path(..., ge=0, le=0),
|
||||||
@ -252,7 +294,10 @@ async def delete_virtualbox_node_nio(
|
|||||||
await node.adapter_remove_nio_binding(adapter_number)
|
await node.adapter_remove_nio_binding(adapter_number)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
@router.post(
|
||||||
|
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start",
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def start_virtualbox_node_capture(
|
async def start_virtualbox_node_capture(
|
||||||
*,
|
*,
|
||||||
adapter_number: int,
|
adapter_number: int,
|
||||||
@ -271,7 +316,9 @@ async def start_virtualbox_node_capture(
|
|||||||
|
|
||||||
|
|
||||||
@router.post(
|
@router.post(
|
||||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT
|
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop",
|
||||||
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
)
|
)
|
||||||
async def stop_virtualbox_node_capture(
|
async def stop_virtualbox_node_capture(
|
||||||
adapter_number: int,
|
adapter_number: int,
|
||||||
@ -286,7 +333,10 @@ async def stop_virtualbox_node_capture(
|
|||||||
await node.stop_capture(adapter_number)
|
await node.stop_capture(adapter_number)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
|
@router.get(
|
||||||
|
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream",
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def stream_pcap_file(
|
async def stream_pcap_file(
|
||||||
adapter_number: int,
|
adapter_number: int,
|
||||||
port_number: int = Path(..., ge=0, le=0),
|
port_number: int = Path(..., ge=0, le=0),
|
||||||
@ -302,8 +352,13 @@ async def stream_pcap_file(
|
|||||||
return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap")
|
return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap")
|
||||||
|
|
||||||
|
|
||||||
@router.websocket("/{node_id}/console/ws")
|
@router.websocket(
|
||||||
async def console_ws(websocket: WebSocket, node: VirtualBoxVM = Depends(dep_node)) -> None:
|
"/{node_id}/console/ws"
|
||||||
|
)
|
||||||
|
async def console_ws(
|
||||||
|
websocket: Union[None, WebSocket] = Depends(ws_compute_authentication),
|
||||||
|
node: VirtualBoxVM = Depends(dep_node)
|
||||||
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Console WebSocket.
|
Console WebSocket.
|
||||||
"""
|
"""
|
||||||
@ -311,7 +366,11 @@ async def console_ws(websocket: WebSocket, node: VirtualBoxVM = Depends(dep_node
|
|||||||
await node.start_websocket_console(websocket)
|
await node.start_websocket_console(websocket)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post(
|
||||||
|
"/{node_id}/console/reset",
|
||||||
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def reset_console(node: VirtualBoxVM = Depends(dep_node)) -> None:
|
async def reset_console(node: VirtualBoxVM = Depends(dep_node)) -> None:
|
||||||
|
|
||||||
await node.reset_console()
|
await node.reset_console()
|
||||||
|
@ -20,16 +20,18 @@ API routes for VMware nodes.
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from fastapi import APIRouter, WebSocket, Depends, Path, Response, status
|
from fastapi import APIRouter, WebSocket, Depends, Path, status
|
||||||
from fastapi.encoders import jsonable_encoder
|
from fastapi.encoders import jsonable_encoder
|
||||||
from fastapi.responses import StreamingResponse
|
from fastapi.responses import StreamingResponse
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
from gns3server import schemas
|
from gns3server import schemas
|
||||||
from gns3server.compute.vmware import VMware
|
from gns3server.compute.vmware import VMware
|
||||||
from gns3server.compute.project_manager import ProjectManager
|
|
||||||
from gns3server.compute.vmware.vmware_vm import VMwareVM
|
from gns3server.compute.vmware.vmware_vm import VMwareVM
|
||||||
|
|
||||||
|
from .dependencies.authentication import compute_authentication, ws_compute_authentication
|
||||||
|
|
||||||
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or VMware node"}}
|
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or VMware node"}}
|
||||||
|
|
||||||
router = APIRouter(responses=responses, deprecated=True)
|
router = APIRouter(responses=responses, deprecated=True)
|
||||||
@ -50,6 +52,7 @@ def dep_node(project_id: UUID, node_id: UUID) -> VMwareVM:
|
|||||||
response_model=schemas.VMware,
|
response_model=schemas.VMware,
|
||||||
status_code=status.HTTP_201_CREATED,
|
status_code=status.HTTP_201_CREATED,
|
||||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create VMware node"}},
|
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create VMware node"}},
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
)
|
)
|
||||||
async def create_vmware_node(project_id: UUID, node_data: schemas.VMwareCreate) -> schemas.VMware:
|
async def create_vmware_node(project_id: UUID, node_data: schemas.VMwareCreate) -> schemas.VMware:
|
||||||
"""
|
"""
|
||||||
@ -76,7 +79,11 @@ async def create_vmware_node(project_id: UUID, node_data: schemas.VMwareCreate)
|
|||||||
return vm.asdict()
|
return vm.asdict()
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{node_id}", response_model=schemas.VMware)
|
@router.get(
|
||||||
|
"/{node_id}",
|
||||||
|
response_model=schemas.VMware,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
def get_vmware_node(node: VMwareVM = Depends(dep_node)) -> schemas.VMware:
|
def get_vmware_node(node: VMwareVM = Depends(dep_node)) -> schemas.VMware:
|
||||||
"""
|
"""
|
||||||
Return a VMware node.
|
Return a VMware node.
|
||||||
@ -85,7 +92,11 @@ def get_vmware_node(node: VMwareVM = Depends(dep_node)) -> schemas.VMware:
|
|||||||
return node.asdict()
|
return node.asdict()
|
||||||
|
|
||||||
|
|
||||||
@router.put("/{node_id}", response_model=schemas.VMware)
|
@router.put(
|
||||||
|
"/{node_id}",
|
||||||
|
response_model=schemas.VMware,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
def update_vmware_node(node_data: schemas.VMwareUpdate, node: VMwareVM = Depends(dep_node)) -> schemas.VMware:
|
def update_vmware_node(node_data: schemas.VMwareUpdate, node: VMwareVM = Depends(dep_node)) -> schemas.VMware:
|
||||||
"""
|
"""
|
||||||
Update a VMware node.
|
Update a VMware node.
|
||||||
@ -102,7 +113,11 @@ def update_vmware_node(node_data: schemas.VMwareUpdate, node: VMwareVM = Depends
|
|||||||
return node.asdict()
|
return node.asdict()
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
@router.delete(
|
||||||
|
"/{node_id}",
|
||||||
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def delete_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
|
async def delete_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
|
||||||
"""
|
"""
|
||||||
Delete a VMware node.
|
Delete a VMware node.
|
||||||
@ -111,7 +126,11 @@ async def delete_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
|
|||||||
await VMware.instance().delete_node(node.id)
|
await VMware.instance().delete_node(node.id)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
@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:
|
async def start_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
|
||||||
"""
|
"""
|
||||||
Start a VMware node.
|
Start a VMware node.
|
||||||
@ -120,7 +139,11 @@ async def start_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
|
|||||||
await node.start()
|
await node.start()
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post(
|
||||||
|
"/{node_id}/stop",
|
||||||
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def stop_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
|
async def stop_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
|
||||||
"""
|
"""
|
||||||
Stop a VMware node.
|
Stop a VMware node.
|
||||||
@ -129,7 +152,11 @@ async def stop_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
|
|||||||
await node.stop()
|
await node.stop()
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post(
|
||||||
|
"/{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:
|
async def suspend_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
|
||||||
"""
|
"""
|
||||||
Suspend a VMware node.
|
Suspend a VMware node.
|
||||||
@ -138,7 +165,11 @@ async def suspend_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
|
|||||||
await node.suspend()
|
await node.suspend()
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/resume", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post(
|
||||||
|
"/{node_id}/resume",
|
||||||
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def resume_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
|
async def resume_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
|
||||||
"""
|
"""
|
||||||
Resume a VMware node.
|
Resume a VMware node.
|
||||||
@ -147,7 +178,11 @@ async def resume_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
|
|||||||
await node.resume()
|
await node.resume()
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post(
|
||||||
|
"/{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:
|
async def reload_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
|
||||||
"""
|
"""
|
||||||
Reload a VMware node.
|
Reload a VMware node.
|
||||||
@ -160,6 +195,7 @@ async def reload_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
|
|||||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||||
status_code=status.HTTP_201_CREATED,
|
status_code=status.HTTP_201_CREATED,
|
||||||
response_model=schemas.UDPNIO,
|
response_model=schemas.UDPNIO,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
)
|
)
|
||||||
async def create_vmware_node_nio(
|
async def create_vmware_node_nio(
|
||||||
*,
|
*,
|
||||||
@ -182,6 +218,7 @@ async def create_vmware_node_nio(
|
|||||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||||
status_code=status.HTTP_201_CREATED,
|
status_code=status.HTTP_201_CREATED,
|
||||||
response_model=schemas.UDPNIO,
|
response_model=schemas.UDPNIO,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
)
|
)
|
||||||
async def update_vmware_node_nio(
|
async def update_vmware_node_nio(
|
||||||
*,
|
*,
|
||||||
@ -202,7 +239,11 @@ async def update_vmware_node_nio(
|
|||||||
return nio.asdict()
|
return nio.asdict()
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
|
@router.delete(
|
||||||
|
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||||
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def delete_vmware_node_nio(
|
async def delete_vmware_node_nio(
|
||||||
adapter_number: int,
|
adapter_number: int,
|
||||||
port_number: int = Path(..., ge=0, le=0),
|
port_number: int = Path(..., ge=0, le=0),
|
||||||
@ -216,7 +257,10 @@ async def delete_vmware_node_nio(
|
|||||||
await node.adapter_remove_nio_binding(adapter_number)
|
await node.adapter_remove_nio_binding(adapter_number)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
@router.post(
|
||||||
|
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start",
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def start_vmware_node_capture(
|
async def start_vmware_node_capture(
|
||||||
*,
|
*,
|
||||||
adapter_number: int,
|
adapter_number: int,
|
||||||
@ -235,7 +279,9 @@ async def start_vmware_node_capture(
|
|||||||
|
|
||||||
|
|
||||||
@router.post(
|
@router.post(
|
||||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT
|
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop",
|
||||||
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
)
|
)
|
||||||
async def stop_vmware_node_capture(
|
async def stop_vmware_node_capture(
|
||||||
adapter_number: int,
|
adapter_number: int,
|
||||||
@ -250,7 +296,10 @@ async def stop_vmware_node_capture(
|
|||||||
await node.stop_capture(adapter_number)
|
await node.stop_capture(adapter_number)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
|
@router.get(
|
||||||
|
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream",
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def stream_pcap_file(
|
async def stream_pcap_file(
|
||||||
adapter_number: int,
|
adapter_number: int,
|
||||||
port_number: int = Path(..., ge=0, le=0),
|
port_number: int = Path(..., ge=0, le=0),
|
||||||
@ -266,7 +315,11 @@ async def stream_pcap_file(
|
|||||||
return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap")
|
return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap")
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/interfaces/vmnet", status_code=status.HTTP_201_CREATED)
|
@router.post(
|
||||||
|
"/{node_id}/interfaces/vmnet",
|
||||||
|
status_code=status.HTTP_201_CREATED,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
def allocate_vmnet(node: VMwareVM = Depends(dep_node)) -> dict:
|
def allocate_vmnet(node: VMwareVM = Depends(dep_node)) -> dict:
|
||||||
"""
|
"""
|
||||||
Allocate a VMware VMnet interface on the server.
|
Allocate a VMware VMnet interface on the server.
|
||||||
@ -280,16 +333,23 @@ def allocate_vmnet(node: VMwareVM = Depends(dep_node)) -> dict:
|
|||||||
|
|
||||||
|
|
||||||
@router.websocket("/{node_id}/console/ws")
|
@router.websocket("/{node_id}/console/ws")
|
||||||
async def console_ws(websocket: WebSocket, node: VMwareVM = Depends(dep_node)) -> None:
|
async def console_ws(
|
||||||
|
websocket: Union[None, WebSocket] = Depends(ws_compute_authentication),
|
||||||
|
node: VMwareVM = Depends(dep_node)
|
||||||
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Console WebSocket.
|
Console WebSocket.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.start_websocket_console(websocket)
|
if websocket:
|
||||||
|
await node.start_websocket_console(websocket)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post(
|
||||||
|
"/{node_id}/console/reset",
|
||||||
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def reset_console(node: VMwareVM = Depends(dep_node)) -> None:
|
async def reset_console(node: VMwareVM = Depends(dep_node)) -> None:
|
||||||
|
|
||||||
await node.reset_console()
|
await node.reset_console()
|
||||||
|
|
||||||
|
@ -20,15 +20,18 @@ API routes for VPCS nodes.
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from fastapi import APIRouter, WebSocket, Depends, Body, Path, Response, status
|
from fastapi import APIRouter, WebSocket, Depends, Body, Path, status
|
||||||
from fastapi.encoders import jsonable_encoder
|
from fastapi.encoders import jsonable_encoder
|
||||||
from fastapi.responses import StreamingResponse
|
from fastapi.responses import StreamingResponse
|
||||||
|
from typing import Union
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from gns3server import schemas
|
from gns3server import schemas
|
||||||
from gns3server.compute.vpcs import VPCS
|
from gns3server.compute.vpcs import VPCS
|
||||||
from gns3server.compute.vpcs.vpcs_vm import VPCSVM
|
from gns3server.compute.vpcs.vpcs_vm import VPCSVM
|
||||||
|
|
||||||
|
from .dependencies.authentication import compute_authentication, ws_compute_authentication
|
||||||
|
|
||||||
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or VMware node"}}
|
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or VMware node"}}
|
||||||
|
|
||||||
router = APIRouter(responses=responses)
|
router = APIRouter(responses=responses)
|
||||||
@ -49,6 +52,7 @@ def dep_node(project_id: UUID, node_id: UUID) -> VPCSVM:
|
|||||||
response_model=schemas.VPCS,
|
response_model=schemas.VPCS,
|
||||||
status_code=status.HTTP_201_CREATED,
|
status_code=status.HTTP_201_CREATED,
|
||||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create VMware node"}},
|
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create VMware node"}},
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
)
|
)
|
||||||
async def create_vpcs_node(project_id: UUID, node_data: schemas.VPCSCreate) -> schemas.VPCS:
|
async def create_vpcs_node(project_id: UUID, node_data: schemas.VPCSCreate) -> schemas.VPCS:
|
||||||
"""
|
"""
|
||||||
@ -69,7 +73,11 @@ async def create_vpcs_node(project_id: UUID, node_data: schemas.VPCSCreate) -> s
|
|||||||
return vm.asdict()
|
return vm.asdict()
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{node_id}", response_model=schemas.VPCS)
|
@router.get(
|
||||||
|
"/{node_id}",
|
||||||
|
response_model=schemas.VPCS,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
def get_vpcs_node(node: VPCSVM = Depends(dep_node)) -> schemas.VPCS:
|
def get_vpcs_node(node: VPCSVM = Depends(dep_node)) -> schemas.VPCS:
|
||||||
"""
|
"""
|
||||||
Return a VPCS node.
|
Return a VPCS node.
|
||||||
@ -78,7 +86,11 @@ def get_vpcs_node(node: VPCSVM = Depends(dep_node)) -> schemas.VPCS:
|
|||||||
return node.asdict()
|
return node.asdict()
|
||||||
|
|
||||||
|
|
||||||
@router.put("/{node_id}", response_model=schemas.VPCS)
|
@router.put(
|
||||||
|
"/{node_id}",
|
||||||
|
response_model=schemas.VPCS,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
def update_vpcs_node(node_data: schemas.VPCSUpdate, node: VPCSVM = Depends(dep_node)) -> schemas.VPCS:
|
def update_vpcs_node(node_data: schemas.VPCSUpdate, node: VPCSVM = Depends(dep_node)) -> schemas.VPCS:
|
||||||
"""
|
"""
|
||||||
Update a VPCS node.
|
Update a VPCS node.
|
||||||
@ -92,7 +104,11 @@ def update_vpcs_node(node_data: schemas.VPCSUpdate, node: VPCSVM = Depends(dep_n
|
|||||||
return node.asdict()
|
return node.asdict()
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
@router.delete(
|
||||||
|
"/{node_id}",
|
||||||
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def delete_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
|
async def delete_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
|
||||||
"""
|
"""
|
||||||
Delete a VPCS node.
|
Delete a VPCS node.
|
||||||
@ -101,7 +117,12 @@ async def delete_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
|
|||||||
await VPCS.instance().delete_node(node.id)
|
await VPCS.instance().delete_node(node.id)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/duplicate", response_model=schemas.VPCS, status_code=status.HTTP_201_CREATED)
|
@router.post(
|
||||||
|
"/{node_id}/duplicate",
|
||||||
|
response_model=schemas.VPCS,
|
||||||
|
status_code=status.HTTP_201_CREATED,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def duplicate_vpcs_node(
|
async def duplicate_vpcs_node(
|
||||||
destination_node_id: UUID = Body(..., embed=True),
|
destination_node_id: UUID = Body(..., embed=True),
|
||||||
node: VPCSVM = Depends(dep_node)) -> None:
|
node: VPCSVM = Depends(dep_node)) -> None:
|
||||||
@ -113,7 +134,11 @@ async def duplicate_vpcs_node(
|
|||||||
return new_node.asdict()
|
return new_node.asdict()
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post(
|
||||||
|
"/{node_id}/start",
|
||||||
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def start_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
|
async def start_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
|
||||||
"""
|
"""
|
||||||
Start a VPCS node.
|
Start a VPCS node.
|
||||||
@ -122,7 +147,11 @@ async def start_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
|
|||||||
await node.start()
|
await node.start()
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post(
|
||||||
|
"/{node_id}/stop",
|
||||||
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def stop_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
|
async def stop_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
|
||||||
"""
|
"""
|
||||||
Stop a VPCS node.
|
Stop a VPCS node.
|
||||||
@ -131,7 +160,11 @@ async def stop_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
|
|||||||
await node.stop()
|
await node.stop()
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post(
|
||||||
|
"/{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:
|
async def suspend_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
|
||||||
"""
|
"""
|
||||||
Suspend a VPCS node.
|
Suspend a VPCS node.
|
||||||
@ -141,7 +174,11 @@ async def suspend_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT)
|
@router.post(
|
||||||
|
"/{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:
|
async def reload_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
|
||||||
"""
|
"""
|
||||||
Reload a VPCS node.
|
Reload a VPCS node.
|
||||||
@ -154,6 +191,7 @@ async def reload_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
|
|||||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||||
status_code=status.HTTP_201_CREATED,
|
status_code=status.HTTP_201_CREATED,
|
||||||
response_model=schemas.UDPNIO,
|
response_model=schemas.UDPNIO,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
)
|
)
|
||||||
async def create_vpcs_node_nio(
|
async def create_vpcs_node_nio(
|
||||||
*,
|
*,
|
||||||
@ -176,6 +214,7 @@ async def create_vpcs_node_nio(
|
|||||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||||
status_code=status.HTTP_201_CREATED,
|
status_code=status.HTTP_201_CREATED,
|
||||||
response_model=schemas.UDPNIO,
|
response_model=schemas.UDPNIO,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
)
|
)
|
||||||
async def update_vpcs_node_nio(
|
async def update_vpcs_node_nio(
|
||||||
*,
|
*,
|
||||||
@ -196,7 +235,11 @@ async def update_vpcs_node_nio(
|
|||||||
return nio.asdict()
|
return nio.asdict()
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
|
@router.delete(
|
||||||
|
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||||
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def delete_vpcs_node_nio(
|
async def delete_vpcs_node_nio(
|
||||||
*,
|
*,
|
||||||
adapter_number: int = Path(..., ge=0, le=0),
|
adapter_number: int = Path(..., ge=0, le=0),
|
||||||
@ -211,7 +254,10 @@ async def delete_vpcs_node_nio(
|
|||||||
await node.port_remove_nio_binding(port_number)
|
await node.port_remove_nio_binding(port_number)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
@router.post(
|
||||||
|
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start",
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
|
)
|
||||||
async def start_vpcs_node_capture(
|
async def start_vpcs_node_capture(
|
||||||
*,
|
*,
|
||||||
adapter_number: int = Path(..., ge=0, le=0),
|
adapter_number: int = Path(..., ge=0, le=0),
|
||||||
@ -230,7 +276,9 @@ async def start_vpcs_node_capture(
|
|||||||
|
|
||||||
|
|
||||||
@router.post(
|
@router.post(
|
||||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT
|
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop",
|
||||||
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
)
|
)
|
||||||
async def stop_vpcs_node_capture(
|
async def stop_vpcs_node_capture(
|
||||||
*,
|
*,
|
||||||
@ -246,13 +294,10 @@ async def stop_vpcs_node_capture(
|
|||||||
await node.stop_capture(port_number)
|
await node.stop_capture(port_number)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
|
@router.get(
|
||||||
async def reset_console(node: VPCSVM = Depends(dep_node)) -> None:
|
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream",
|
||||||
|
dependencies=[Depends(compute_authentication)]
|
||||||
await node.reset_console()
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
|
|
||||||
async def stream_pcap_file(
|
async def stream_pcap_file(
|
||||||
*,
|
*,
|
||||||
adapter_number: int = Path(..., ge=0, le=0),
|
adapter_number: int = Path(..., ge=0, le=0),
|
||||||
@ -269,10 +314,24 @@ async def stream_pcap_file(
|
|||||||
return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap")
|
return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap")
|
||||||
|
|
||||||
|
|
||||||
@router.websocket("/{node_id}/console/ws")
|
@router.websocket(
|
||||||
async def console_ws(websocket: WebSocket, node: VPCSVM = Depends(dep_node)) -> None:
|
"/{node_id}/console/ws"
|
||||||
|
)
|
||||||
|
async def console_ws(
|
||||||
|
websocket: Union[None, WebSocket] = Depends(ws_compute_authentication),
|
||||||
|
node: VPCSVM = Depends(dep_node)) -> None:
|
||||||
"""
|
"""
|
||||||
Console WebSocket.
|
Console WebSocket.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
await node.start_websocket_console(websocket)
|
await node.start_websocket_console(websocket)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post(
|
||||||
|
"/{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()
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import re
|
import logging
|
||||||
|
|
||||||
from fastapi import Request, Query, Depends, HTTPException, WebSocket, status
|
from fastapi import Request, Query, Depends, HTTPException, WebSocket, status
|
||||||
from fastapi.security import OAuth2PasswordBearer
|
from fastapi.security import OAuth2PasswordBearer
|
||||||
@ -26,6 +26,7 @@ from gns3server.db.repositories.rbac import RbacRepository
|
|||||||
from gns3server.services import auth_service
|
from gns3server.services import auth_service
|
||||||
from .database import get_repository
|
from .database import get_repository
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/v3/access/users/login", auto_error=False)
|
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/v3/access/users/login", auto_error=False)
|
||||||
|
|
||||||
|
|
||||||
@ -108,7 +109,9 @@ async def get_current_active_user_from_websocket(
|
|||||||
return user
|
return user
|
||||||
|
|
||||||
except HTTPException as e:
|
except HTTPException as e:
|
||||||
websocket_error = {"action": "log.error", "event": {"message": f"Could not authenticate while connecting to "
|
err_msg = f"Could not authenticate while connecting to controller WebSocket: {e.detail}"
|
||||||
f"WebSocket: {e.detail}"}}
|
websocket_error = {"action": "log.error", "event": {"message": err_msg}}
|
||||||
await websocket.send_json(websocket_error)
|
await websocket.send_json(websocket_error)
|
||||||
await websocket.close(code=1008)
|
log.error(err_msg)
|
||||||
|
return await websocket.close(code=1008)
|
||||||
|
|
||||||
|
@ -29,6 +29,7 @@ from typing import List, Callable
|
|||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from gns3server.controller import Controller
|
from gns3server.controller import Controller
|
||||||
|
from gns3server.config import Config
|
||||||
from gns3server.controller.node import Node
|
from gns3server.controller.node import Node
|
||||||
from gns3server.controller.project import Project
|
from gns3server.controller.project import Project
|
||||||
from gns3server.utils import force_unix_path
|
from gns3server.utils import force_unix_path
|
||||||
@ -510,16 +511,22 @@ async def post_file(file_path: str, request: Request, node: Node = Depends(dep_n
|
|||||||
# FIXME: response with correct status code (from compute)
|
# FIXME: response with correct status code (from compute)
|
||||||
|
|
||||||
|
|
||||||
@router.websocket("/{node_id}/console/ws", dependencies=[Depends(has_privilege_on_websocket("Node.Console"))])
|
@router.websocket("/{node_id}/console/ws")
|
||||||
async def ws_console(websocket: WebSocket, node: Node = Depends(dep_node)) -> None:
|
async def ws_console(
|
||||||
|
websocket: WebSocket,
|
||||||
|
current_user: schemas.User = Depends(has_privilege_on_websocket("Node.Console")),
|
||||||
|
node: Node = Depends(dep_node)
|
||||||
|
) -> None:
|
||||||
"""
|
"""
|
||||||
WebSocket console.
|
WebSocket console.
|
||||||
|
|
||||||
Required privilege: Node.Console
|
Required privilege: Node.Console
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if current_user is None:
|
||||||
|
return
|
||||||
|
|
||||||
compute = node.compute
|
compute = node.compute
|
||||||
await websocket.accept()
|
|
||||||
log.info(
|
log.info(
|
||||||
f"New client {websocket.client.host}:{websocket.client.port} has connected to controller console WebSocket"
|
f"New client {websocket.client.host}:{websocket.client.port} has connected to controller console WebSocket"
|
||||||
)
|
)
|
||||||
@ -557,9 +564,20 @@ async def ws_console(websocket: WebSocket, node: Node = Depends(dep_node)) -> No
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
# receive WebSocket data from compute console WebSocket and forward to client.
|
# receive WebSocket data from compute console WebSocket and forward to client.
|
||||||
async with HTTPClient.get_client().ws_connect(ws_console_compute_url) as ws_console_compute:
|
log.info(f"Forwarding console WebSocket to '{ws_console_compute_url}'")
|
||||||
asyncio.ensure_future(ws_receive(ws_console_compute))
|
server_config = Config.instance().settings.Server
|
||||||
async for msg in ws_console_compute:
|
user = server_config.compute_username
|
||||||
|
password = server_config.compute_password
|
||||||
|
if not user:
|
||||||
|
raise ControllerForbiddenError("Compute username is not set")
|
||||||
|
user = user.strip()
|
||||||
|
if user and password:
|
||||||
|
auth = aiohttp.BasicAuth(user, password.get_secret_value(), "utf-8")
|
||||||
|
else:
|
||||||
|
auth = aiohttp.BasicAuth(user, "")
|
||||||
|
async with HTTPClient.get_client().ws_connect(ws_console_compute_url, auth=auth) as ws:
|
||||||
|
asyncio.ensure_future(ws_receive(ws))
|
||||||
|
async for msg in ws:
|
||||||
if msg.type == aiohttp.WSMsgType.TEXT:
|
if msg.type == aiohttp.WSMsgType.TEXT:
|
||||||
await websocket.send_text(msg.data)
|
await websocket.send_text(msg.data)
|
||||||
elif msg.type == aiohttp.WSMsgType.BINARY:
|
elif msg.type == aiohttp.WSMsgType.BINARY:
|
||||||
|
@ -485,6 +485,11 @@ class BaseNode:
|
|||||||
:param ws: Websocket object
|
:param ws: Websocket object
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
log.info(
|
||||||
|
f"New client {websocket.client.host}:{websocket.client.port} has connected to compute"
|
||||||
|
f" console WebSocket"
|
||||||
|
)
|
||||||
|
|
||||||
if self.status != "started":
|
if self.status != "started":
|
||||||
raise NodeError(f"Node {self.name} is not started")
|
raise NodeError(f"Node {self.name} is not started")
|
||||||
|
|
||||||
@ -492,20 +497,13 @@ class BaseNode:
|
|||||||
raise NodeError(f"Node {self.name} console type is not telnet")
|
raise NodeError(f"Node {self.name} console type is not telnet")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
(telnet_reader, telnet_writer) = await asyncio.open_connection(
|
host = self._manager.port_manager.console_host
|
||||||
self._manager.port_manager.console_host, self.console
|
port = self.console
|
||||||
)
|
(telnet_reader, telnet_writer) = await asyncio.open_connection(host, port)
|
||||||
|
log.info(f"Connected to local Telnet server {host}:{port}")
|
||||||
except ConnectionError as e:
|
except ConnectionError as e:
|
||||||
raise NodeError(f"Cannot connect to node {self.name} telnet server: {e}")
|
raise NodeError(f"Cannot connect to node {self.name} telnet server: {e}")
|
||||||
|
|
||||||
log.info("Connected to Telnet server")
|
|
||||||
|
|
||||||
await websocket.accept()
|
|
||||||
log.info(
|
|
||||||
f"New client {websocket.client.host}:{websocket.client.port} has connected to compute"
|
|
||||||
f" console WebSocket"
|
|
||||||
)
|
|
||||||
|
|
||||||
async def ws_forward(telnet_writer):
|
async def ws_forward(telnet_writer):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -20,6 +20,10 @@ from fastapi import FastAPI, status
|
|||||||
from fastapi.routing import APIRoute, APIWebSocketRoute
|
from fastapi.routing import APIRoute, APIWebSocketRoute
|
||||||
from starlette.routing import Mount
|
from starlette.routing import Mount
|
||||||
from httpx import AsyncClient
|
from httpx import AsyncClient
|
||||||
|
from httpx_ws import aconnect_ws
|
||||||
|
from httpx_ws.transport import ASGIWebSocketTransport
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
pytestmark = pytest.mark.asyncio
|
pytestmark = pytest.mark.asyncio
|
||||||
|
|
||||||
@ -37,6 +41,7 @@ ALLOWED_CONTROLLER_ENDPOINTS = [
|
|||||||
("/v3/symbols/default_symbols", "GET")
|
("/v3/symbols/default_symbols", "GET")
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
# Controller endpoints have a OAuth2 bearer token authentication
|
# Controller endpoints have a OAuth2 bearer token authentication
|
||||||
async def test_controller_endpoints_require_authentication(app: FastAPI, unauthorized_client: AsyncClient) -> None:
|
async def test_controller_endpoints_require_authentication(app: FastAPI, unauthorized_client: AsyncClient) -> None:
|
||||||
|
|
||||||
@ -47,7 +52,14 @@ async def test_controller_endpoints_require_authentication(app: FastAPI, unautho
|
|||||||
response = await getattr(unauthorized_client, method.lower())(route.path)
|
response = await getattr(unauthorized_client, method.lower())(route.path)
|
||||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||||
elif isinstance(route, APIWebSocketRoute):
|
elif isinstance(route, APIWebSocketRoute):
|
||||||
pass # TODO: test websocket route authentication
|
params = {"token": "wrong_token"}
|
||||||
|
async with AsyncClient(base_url="http://test-api", transport=ASGIWebSocketTransport(app)) as client:
|
||||||
|
async with aconnect_ws(route.path, client, params=params) as ws:
|
||||||
|
json_notification = await ws.receive_json()
|
||||||
|
assert json_notification['event'] == {
|
||||||
|
'message': 'Could not authenticate while connecting to controller WebSocket: Could not validate credentials'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
# Compute endpoints have a basic HTTP authentication
|
# Compute endpoints have a basic HTTP authentication
|
||||||
async def test_compute_endpoints_require_authentication(app: FastAPI, unauthorized_client: AsyncClient) -> None:
|
async def test_compute_endpoints_require_authentication(app: FastAPI, unauthorized_client: AsyncClient) -> None:
|
||||||
@ -55,9 +67,14 @@ async def test_compute_endpoints_require_authentication(app: FastAPI, unauthoriz
|
|||||||
for route in app.routes:
|
for route in app.routes:
|
||||||
if isinstance(route, Mount):
|
if isinstance(route, Mount):
|
||||||
for compute_route in route.routes:
|
for compute_route in route.routes:
|
||||||
if isinstance(compute_route, APIRoute): # APIWebSocketRoute
|
if isinstance(compute_route, APIRoute):
|
||||||
for method in list(compute_route.methods):
|
for method in list(compute_route.methods):
|
||||||
response = await getattr(unauthorized_client, method.lower())(route.path + compute_route.path)
|
response = await getattr(unauthorized_client, method.lower())(route.path + compute_route.path)
|
||||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||||
elif isinstance(compute_route, APIWebSocketRoute):
|
elif isinstance(compute_route, APIWebSocketRoute):
|
||||||
pass # TODO: test websocket route authentication
|
async with AsyncClient(base_url="http://test-api", transport=ASGIWebSocketTransport(app)) as client:
|
||||||
|
async with aconnect_ws(route.path + compute_route.path, client, auth=("wrong_user", "password123")) as ws:
|
||||||
|
json_notification = await ws.receive_json()
|
||||||
|
assert json_notification['event'] == {
|
||||||
|
'message': 'Could not authenticate while connecting to compute WebSocket: Could not validate credentials'
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user