mirror of
https://github.com/GNS3/gns3-server
synced 2024-12-01 04:38:12 +00:00
Merge remote-tracking branch 'origin/3.0' into gh-pages
This commit is contained in:
commit
4f5671066e
2
.github/workflows/testing.yml
vendored
2
.github/workflows/testing.yml
vendored
@ -17,7 +17,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
|
||||
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
11
CHANGELOG
11
CHANGELOG
@ -1,5 +1,16 @@
|
||||
# Change Log
|
||||
|
||||
## 3.0.0a5 27/10/2023
|
||||
|
||||
* Bundle web-ui v3.0.0a5
|
||||
* Fix L2IOU "failed code signing checks" when IOU base file name is >= 63 characters
|
||||
* Python 3.12 support
|
||||
* Add igb Qemu adapter
|
||||
* Change "ip cef" to "no ip cef" in IOU default configs. Fixes #2298
|
||||
* Drop support for Python 3.7 and upgrade dependencies
|
||||
* Fix compute authentication for websocket endpoints
|
||||
* Add Qemu IGB network device
|
||||
|
||||
## 3.0.0a4 18/10/2023
|
||||
|
||||
* Bundle web-ui v3.0.0a4
|
||||
|
@ -1,6 +1,7 @@
|
||||
pytest==7.4.0
|
||||
flake8==5.0.4 # v5.0.4 is the last to support Python 3.7
|
||||
pytest-timeout==2.1.0
|
||||
pytest==7.4.2
|
||||
flake8==6.1.0
|
||||
pytest-timeout==2.2.0
|
||||
pytest-asyncio==0.21.1
|
||||
requests==2.31.0
|
||||
httpx==0.24.1
|
||||
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(
|
||||
docker_nodes.router,
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
prefix="/projects/{project_id}/docker/nodes",
|
||||
tags=["Docker nodes"]
|
||||
)
|
||||
|
||||
compute_api.include_router(
|
||||
dynamips_nodes.router,
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
prefix="/projects/{project_id}/dynamips/nodes",
|
||||
tags=["Dynamips nodes"]
|
||||
)
|
||||
@ -234,7 +232,6 @@ compute_api.include_router(
|
||||
|
||||
compute_api.include_router(
|
||||
iou_nodes.router,
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
prefix="/projects/{project_id}/iou/nodes",
|
||||
tags=["IOU nodes"])
|
||||
|
||||
@ -247,28 +244,24 @@ compute_api.include_router(
|
||||
|
||||
compute_api.include_router(
|
||||
qemu_nodes.router,
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
prefix="/projects/{project_id}/qemu/nodes",
|
||||
tags=["Qemu nodes"]
|
||||
)
|
||||
|
||||
compute_api.include_router(
|
||||
virtualbox_nodes.router,
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
prefix="/projects/{project_id}/virtualbox/nodes",
|
||||
tags=["VirtualBox nodes"]
|
||||
)
|
||||
|
||||
compute_api.include_router(
|
||||
vmware_nodes.router,
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
prefix="/projects/{project_id}/vmware/nodes",
|
||||
tags=["VMware nodes"]
|
||||
)
|
||||
|
||||
compute_api.include_router(
|
||||
vpcs_nodes.router,
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
prefix="/projects/{project_id}/vpcs/nodes",
|
||||
tags=["VPCS nodes"]
|
||||
)
|
||||
|
@ -15,12 +15,17 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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.utils import get_authorization_scheme_param
|
||||
from gns3server.config import Config
|
||||
from typing import Optional
|
||||
from typing import Optional, Union
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
security = HTTPBasic()
|
||||
|
||||
|
||||
@ -35,3 +40,44 @@ def compute_authentication(credentials: Optional[HTTPBasicCredentials] = Depends
|
||||
detail="Invalid compute username or password",
|
||||
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
|
||||
|
||||
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.responses import StreamingResponse
|
||||
from uuid import UUID
|
||||
from typing import Union
|
||||
|
||||
from gns3server import schemas
|
||||
from gns3server.compute.docker import Docker
|
||||
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"}}
|
||||
|
||||
router = APIRouter(responses=responses)
|
||||
@ -49,6 +52,7 @@ def dep_node(project_id: UUID, node_id: UUID) -> DockerVM:
|
||||
response_model=schemas.Docker,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Docker node"}},
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
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()
|
||||
|
||||
|
||||
@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:
|
||||
"""
|
||||
Return a Docker node.
|
||||
@ -94,7 +102,11 @@ def get_docker_node(node: DockerVM = Depends(dep_node)) -> schemas.Docker:
|
||||
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:
|
||||
"""
|
||||
Update a Docker node.
|
||||
@ -131,7 +143,11 @@ async def update_docker_node(node_data: schemas.DockerUpdate, node: DockerVM = D
|
||||
return node.asdict()
|
||||
|
||||
|
||||
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post(
|
||||
"/{node_id}/start",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def start_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Start a Docker node.
|
||||
@ -140,7 +156,11 @@ async def start_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
||||
await node.start()
|
||||
|
||||
|
||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post(
|
||||
"/{node_id}/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def stop_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Stop a Docker node.
|
||||
@ -149,7 +169,11 @@ async def stop_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
||||
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:
|
||||
"""
|
||||
Suspend a Docker node.
|
||||
@ -158,7 +182,11 @@ async def suspend_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
||||
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:
|
||||
"""
|
||||
Reload a Docker node.
|
||||
@ -167,7 +195,11 @@ async def reload_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
||||
await node.restart()
|
||||
|
||||
|
||||
@router.post("/{node_id}/pause", status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post(
|
||||
"/{node_id}/pause",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def pause_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Pause a Docker node.
|
||||
@ -176,7 +208,11 @@ async def pause_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
||||
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:
|
||||
"""
|
||||
Unpause a Docker node.
|
||||
@ -185,7 +221,11 @@ async def unpause_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
||||
await node.unpause()
|
||||
|
||||
|
||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.delete(
|
||||
"/{node_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def delete_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Delete a Docker node.
|
||||
@ -194,7 +234,12 @@ async def delete_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
||||
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(
|
||||
destination_node_id: UUID = Body(..., embed=True),
|
||||
node: DockerVM = Depends(dep_node)
|
||||
@ -211,6 +256,7 @@ async def duplicate_docker_node(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def create_docker_node_nio(
|
||||
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",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def update_docker_node_nio(
|
||||
adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: DockerVM = Depends(dep_node)
|
||||
@ -245,7 +292,11 @@ async def update_docker_node_nio(
|
||||
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(
|
||||
adapter_number: int,
|
||||
port_number: int,
|
||||
@ -259,7 +310,10 @@ async def delete_docker_node_nio(
|
||||
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(
|
||||
adapter_number: int,
|
||||
port_number: int,
|
||||
@ -278,7 +332,8 @@ async def start_docker_node_capture(
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def stop_docker_node_capture(
|
||||
adapter_number: int,
|
||||
@ -293,7 +348,10 @@ async def stop_docker_node_capture(
|
||||
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(
|
||||
adapter_number: int,
|
||||
port_number: int,
|
||||
@ -310,15 +368,23 @@ async def stream_pcap_file(
|
||||
|
||||
|
||||
@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.
|
||||
"""
|
||||
|
||||
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:
|
||||
|
||||
await node.reset_console()
|
||||
|
@ -20,16 +20,18 @@ API routes for Dynamips nodes.
|
||||
|
||||
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.responses import StreamingResponse
|
||||
from typing import List
|
||||
from typing import List, Union
|
||||
from uuid import UUID
|
||||
|
||||
from gns3server.compute.dynamips import Dynamips
|
||||
from gns3server.compute.dynamips.nodes.router import Router
|
||||
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"}}
|
||||
|
||||
router = APIRouter(responses=responses)
|
||||
@ -53,6 +55,7 @@ def dep_node(project_id: UUID, node_id: UUID) -> Router:
|
||||
response_model=schemas.Dynamips,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Dynamips node"}},
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
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()
|
||||
|
||||
|
||||
@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:
|
||||
"""
|
||||
Return Dynamips router.
|
||||
@ -93,7 +100,11 @@ def get_router(node: Router = Depends(dep_node)) -> schemas.Dynamips:
|
||||
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:
|
||||
"""
|
||||
Update a Dynamips router.
|
||||
@ -104,7 +115,11 @@ async def update_router(node_data: schemas.DynamipsUpdate, node: Router = Depend
|
||||
return node.asdict()
|
||||
|
||||
|
||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.delete(
|
||||
"/{node_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def delete_router(node: Router = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Delete a Dynamips router.
|
||||
@ -113,7 +128,11 @@ async def delete_router(node: Router = Depends(dep_node)) -> None:
|
||||
await Dynamips.instance().delete_node(node.id)
|
||||
|
||||
|
||||
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post(
|
||||
"/{node_id}/start",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def start_router(node: Router = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Start a Dynamips router.
|
||||
@ -126,7 +145,11 @@ async def start_router(node: Router = Depends(dep_node)) -> None:
|
||||
await node.start()
|
||||
|
||||
|
||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post(
|
||||
"/{node_id}/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def stop_router(node: Router = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Stop a Dynamips router.
|
||||
@ -135,13 +158,21 @@ async def stop_router(node: Router = Depends(dep_node)) -> None:
|
||||
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:
|
||||
|
||||
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:
|
||||
"""
|
||||
Resume a suspended Dynamips router.
|
||||
@ -150,7 +181,11 @@ async def resume_router(node: Router = Depends(dep_node)) -> None:
|
||||
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:
|
||||
"""
|
||||
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",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def create_nio(
|
||||
adapter_number: int,
|
||||
@ -183,6 +219,7 @@ async def create_nio(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def update_nio(
|
||||
adapter_number: int,
|
||||
@ -201,7 +238,11 @@ async def update_nio(
|
||||
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:
|
||||
"""
|
||||
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()
|
||||
|
||||
|
||||
@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(
|
||||
adapter_number: int,
|
||||
port_number: int,
|
||||
@ -228,7 +272,9 @@ async def start_capture(
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
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)
|
||||
|
||||
|
||||
@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(
|
||||
adapter_number: int,
|
||||
port_number: int,
|
||||
@ -253,7 +302,10 @@ async def stream_pcap_file(
|
||||
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]:
|
||||
"""
|
||||
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()
|
||||
|
||||
|
||||
@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:
|
||||
"""
|
||||
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}
|
||||
|
||||
|
||||
@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:
|
||||
"""
|
||||
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")
|
||||
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.
|
||||
"""
|
||||
|
||||
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:
|
||||
|
||||
await node.reset_console()
|
||||
|
@ -30,6 +30,8 @@ from gns3server import schemas
|
||||
from gns3server.compute.iou import IOU
|
||||
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"}}
|
||||
|
||||
router = APIRouter(responses=responses)
|
||||
@ -50,6 +52,7 @@ def dep_node(project_id: UUID, node_id: UUID) -> IOUVM:
|
||||
response_model=schemas.IOU,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create IOU node"}},
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
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()
|
||||
|
||||
|
||||
@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:
|
||||
"""
|
||||
Return an IOU node.
|
||||
@ -91,7 +98,11 @@ def get_iou_node(node: IOUVM = Depends(dep_node)) -> schemas.IOU:
|
||||
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:
|
||||
"""
|
||||
Update an IOU node.
|
||||
@ -112,7 +123,11 @@ async def update_iou_node(node_data: schemas.IOUUpdate, node: IOUVM = Depends(de
|
||||
return node.asdict()
|
||||
|
||||
|
||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.delete(
|
||||
"/{node_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def delete_iou_node(node: IOUVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Delete an IOU node.
|
||||
@ -121,7 +136,12 @@ async def delete_iou_node(node: IOUVM = Depends(dep_node)) -> None:
|
||||
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(
|
||||
destination_node_id: UUID = Body(..., embed=True),
|
||||
node: IOUVM = Depends(dep_node)
|
||||
@ -134,7 +154,11 @@ async def duplicate_iou_node(
|
||||
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:
|
||||
"""
|
||||
Start an IOU node.
|
||||
@ -148,7 +172,11 @@ async def start_iou_node(start_data: schemas.IOUStart, node: IOUVM = Depends(dep
|
||||
await node.start()
|
||||
|
||||
|
||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post(
|
||||
"/{node_id}/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def stop_iou_node(node: IOUVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Stop an IOU node.
|
||||
@ -157,7 +185,11 @@ async def stop_iou_node(node: IOUVM = Depends(dep_node)) -> None:
|
||||
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:
|
||||
"""
|
||||
Suspend an IOU node.
|
||||
@ -167,7 +199,11 @@ def suspend_iou_node(node: IOUVM = Depends(dep_node)) -> None:
|
||||
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:
|
||||
"""
|
||||
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",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def create_iou_node_nio(
|
||||
adapter_number: int,
|
||||
@ -200,6 +237,7 @@ async def create_iou_node_nio(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def update_iou_node_nio(
|
||||
adapter_number: int,
|
||||
@ -218,7 +256,11 @@ async def update_iou_node_nio(
|
||||
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:
|
||||
"""
|
||||
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)
|
||||
|
||||
|
||||
@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(
|
||||
adapter_number: int,
|
||||
port_number: int,
|
||||
@ -244,7 +289,9 @@ async def start_iou_node_capture(
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT
|
||||
"/{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:
|
||||
"""
|
||||
@ -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)
|
||||
|
||||
|
||||
@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(
|
||||
adapter_number: int,
|
||||
port_number: int,
|
||||
@ -269,16 +319,26 @@ async def stream_pcap_file(
|
||||
return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap")
|
||||
|
||||
|
||||
@router.websocket("/{node_id}/console/ws")
|
||||
async def console_ws(websocket: WebSocket, node: IOUVM = Depends(dep_node)) -> None:
|
||||
@router.websocket(
|
||||
"/{node_id}/console/ws",
|
||||
)
|
||||
async def console_ws(
|
||||
websocket: Union[None, WebSocket] = Depends(ws_compute_authentication),
|
||||
node: IOUVM = Depends(dep_node)
|
||||
) -> None:
|
||||
"""
|
||||
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:
|
||||
|
||||
await node.reset_console()
|
||||
|
@ -18,14 +18,13 @@
|
||||
API routes for compute notifications.
|
||||
"""
|
||||
|
||||
import base64
|
||||
import binascii
|
||||
|
||||
from fastapi import APIRouter, WebSocket, WebSocketDisconnect, status, HTTPException
|
||||
from fastapi.security.utils import get_authorization_scheme_param
|
||||
from fastapi import APIRouter, Depends, WebSocket, WebSocketDisconnect
|
||||
from typing import Union
|
||||
from websockets.exceptions import ConnectionClosed, WebSocketException
|
||||
|
||||
from gns3server.compute.notification_manager import NotificationManager
|
||||
from .dependencies.authentication import ws_compute_authentication
|
||||
|
||||
import logging
|
||||
|
||||
@ -35,38 +34,12 @@ router = APIRouter()
|
||||
|
||||
|
||||
@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.
|
||||
"""
|
||||
|
||||
await websocket.accept()
|
||||
|
||||
# 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:
|
||||
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
|
||||
except invalid_user_credentials_exc as e:
|
||||
websocket_error = {"action": "log.error", "event": {"message": f"Could not authenticate while connecting to "
|
||||
f"compute WebSocket: {e.detail}"}}
|
||||
await websocket.send_json(websocket_error)
|
||||
return await websocket.close(code=1008)
|
||||
|
||||
if websocket:
|
||||
log.info(f"New client {websocket.client.host}:{websocket.client.port} has connected to compute WebSocket")
|
||||
try:
|
||||
with NotificationManager.instance().queue() as queue:
|
||||
|
@ -20,15 +20,17 @@ API routes for Qemu nodes.
|
||||
|
||||
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.responses import StreamingResponse
|
||||
from typing import Union
|
||||
from uuid import UUID
|
||||
|
||||
from gns3server import schemas
|
||||
from gns3server.compute.qemu import Qemu
|
||||
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"}}
|
||||
|
||||
@ -50,6 +52,7 @@ def dep_node(project_id: UUID, node_id: UUID) -> QemuVM:
|
||||
response_model=schemas.Qemu,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Qemu node"}},
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
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()
|
||||
|
||||
|
||||
@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:
|
||||
"""
|
||||
Return a Qemu node.
|
||||
@ -87,7 +94,11 @@ def get_qemu_node(node: QemuVM = Depends(dep_node)) -> schemas.Qemu:
|
||||
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:
|
||||
"""
|
||||
Update a Qemu node.
|
||||
@ -103,7 +114,11 @@ async def update_qemu_node(node_data: schemas.QemuUpdate, node: QemuVM = Depends
|
||||
return node.asdict()
|
||||
|
||||
|
||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.delete(
|
||||
"/{node_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def delete_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Delete a Qemu node.
|
||||
@ -112,7 +127,12 @@ async def delete_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
|
||||
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(
|
||||
destination_node_id: UUID = Body(..., embed=True),
|
||||
node: QemuVM = Depends(dep_node)
|
||||
@ -127,7 +147,8 @@ async def duplicate_qemu_node(
|
||||
|
||||
@router.post(
|
||||
"/{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(
|
||||
disk_name: str,
|
||||
@ -144,7 +165,8 @@ async def create_qemu_disk_image(
|
||||
|
||||
@router.put(
|
||||
"/{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(
|
||||
disk_name: str,
|
||||
@ -161,7 +183,8 @@ async def update_qemu_disk_image(
|
||||
|
||||
@router.delete(
|
||||
"/{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(
|
||||
disk_name: str,
|
||||
@ -174,7 +197,11 @@ async def delete_qemu_disk_image(
|
||||
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:
|
||||
"""
|
||||
Start a Qemu node.
|
||||
@ -183,7 +210,11 @@ async def start_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
|
||||
await node.start()
|
||||
|
||||
|
||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post(
|
||||
"/{node_id}/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def stop_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Stop a Qemu node.
|
||||
@ -192,7 +223,11 @@ async def stop_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
|
||||
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:
|
||||
"""
|
||||
Reload a Qemu node.
|
||||
@ -201,7 +236,11 @@ async def reload_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
|
||||
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:
|
||||
"""
|
||||
Suspend a Qemu node.
|
||||
@ -210,7 +249,11 @@ async def suspend_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
|
||||
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:
|
||||
"""
|
||||
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",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
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",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def update_qemu_node_nio(
|
||||
*,
|
||||
@ -267,7 +312,11 @@ async def update_qemu_node_nio(
|
||||
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(
|
||||
adapter_number: int,
|
||||
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)
|
||||
|
||||
|
||||
@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(
|
||||
*,
|
||||
adapter_number: int,
|
||||
@ -300,7 +352,9 @@ async def start_qemu_node_capture(
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT
|
||||
"/{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(
|
||||
adapter_number: int,
|
||||
@ -315,7 +369,10 @@ async def stop_qemu_node_capture(
|
||||
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(
|
||||
adapter_number: int,
|
||||
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")
|
||||
|
||||
|
||||
@router.websocket("/{node_id}/console/ws")
|
||||
async def console_ws(websocket: WebSocket, node: QemuVM = Depends(dep_node)) -> None:
|
||||
@router.websocket(
|
||||
"/{node_id}/console/ws"
|
||||
)
|
||||
async def console_ws(
|
||||
websocket: Union[None, WebSocket] = Depends(ws_compute_authentication),
|
||||
node: QemuVM = Depends(dep_node)
|
||||
) -> None:
|
||||
"""
|
||||
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:
|
||||
|
||||
await node.reset_console()
|
||||
|
@ -20,16 +20,19 @@ API routes for VirtualBox nodes.
|
||||
|
||||
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.responses import StreamingResponse
|
||||
from uuid import UUID
|
||||
from typing import Union
|
||||
|
||||
from gns3server import schemas
|
||||
from gns3server.compute.virtualbox import VirtualBox
|
||||
from gns3server.compute.virtualbox.virtualbox_error import VirtualBoxError
|
||||
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"}}
|
||||
|
||||
router = APIRouter(responses=responses, deprecated=True)
|
||||
@ -50,6 +53,7 @@ def dep_node(project_id: UUID, node_id: UUID) -> VirtualBoxVM:
|
||||
response_model=schemas.VirtualBox,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create VirtualBox node"}},
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
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()
|
||||
|
||||
|
||||
@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:
|
||||
"""
|
||||
Return a VirtualBox node.
|
||||
@ -91,7 +99,11 @@ def get_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> schemas.Virtu
|
||||
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(
|
||||
node_data: schemas.VirtualBoxUpdate,
|
||||
node: VirtualBoxVM = Depends(dep_node)
|
||||
@ -136,7 +148,11 @@ async def update_virtualbox_node(
|
||||
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:
|
||||
"""
|
||||
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)
|
||||
|
||||
|
||||
@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:
|
||||
"""
|
||||
Start a VirtualBox node.
|
||||
@ -154,7 +174,11 @@ async def start_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
|
||||
await node.start()
|
||||
|
||||
|
||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post(
|
||||
"/{node_id}/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def stop_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Stop a VirtualBox node.
|
||||
@ -163,7 +187,11 @@ async def stop_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
|
||||
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:
|
||||
"""
|
||||
Suspend a VirtualBox node.
|
||||
@ -172,7 +200,11 @@ async def suspend_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> Non
|
||||
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:
|
||||
"""
|
||||
Resume a VirtualBox node.
|
||||
@ -181,7 +213,11 @@ async def resume_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None
|
||||
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:
|
||||
"""
|
||||
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",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
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",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def update_virtualbox_node_nio(
|
||||
*,
|
||||
@ -238,7 +276,11 @@ async def update_virtualbox_node_nio(
|
||||
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(
|
||||
adapter_number: int,
|
||||
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)
|
||||
|
||||
|
||||
@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(
|
||||
*,
|
||||
adapter_number: int,
|
||||
@ -271,7 +316,9 @@ async def start_virtualbox_node_capture(
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT
|
||||
"/{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(
|
||||
adapter_number: int,
|
||||
@ -286,7 +333,10 @@ async def stop_virtualbox_node_capture(
|
||||
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(
|
||||
adapter_number: int,
|
||||
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")
|
||||
|
||||
|
||||
@router.websocket("/{node_id}/console/ws")
|
||||
async def console_ws(websocket: WebSocket, node: VirtualBoxVM = Depends(dep_node)) -> None:
|
||||
@router.websocket(
|
||||
"/{node_id}/console/ws"
|
||||
)
|
||||
async def console_ws(
|
||||
websocket: Union[None, WebSocket] = Depends(ws_compute_authentication),
|
||||
node: VirtualBoxVM = Depends(dep_node)
|
||||
) -> None:
|
||||
"""
|
||||
Console WebSocket.
|
||||
"""
|
||||
@ -311,7 +366,11 @@ async def console_ws(websocket: WebSocket, node: VirtualBoxVM = Depends(dep_node
|
||||
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:
|
||||
|
||||
await node.reset_console()
|
||||
|
@ -20,16 +20,18 @@ API routes for VMware nodes.
|
||||
|
||||
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.responses import StreamingResponse
|
||||
from uuid import UUID
|
||||
from typing import Union
|
||||
|
||||
from gns3server import schemas
|
||||
from gns3server.compute.vmware import VMware
|
||||
from gns3server.compute.project_manager import ProjectManager
|
||||
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"}}
|
||||
|
||||
router = APIRouter(responses=responses, deprecated=True)
|
||||
@ -50,6 +52,7 @@ def dep_node(project_id: UUID, node_id: UUID) -> VMwareVM:
|
||||
response_model=schemas.VMware,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create VMware node"}},
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
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()
|
||||
|
||||
|
||||
@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:
|
||||
"""
|
||||
Return a VMware node.
|
||||
@ -85,7 +92,11 @@ def get_vmware_node(node: VMwareVM = Depends(dep_node)) -> schemas.VMware:
|
||||
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:
|
||||
"""
|
||||
Update a VMware node.
|
||||
@ -102,7 +113,11 @@ def update_vmware_node(node_data: schemas.VMwareUpdate, node: VMwareVM = Depends
|
||||
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:
|
||||
"""
|
||||
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)
|
||||
|
||||
|
||||
@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:
|
||||
"""
|
||||
Start a VMware node.
|
||||
@ -120,7 +139,11 @@ async def start_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
|
||||
await node.start()
|
||||
|
||||
|
||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post(
|
||||
"/{node_id}/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def stop_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Stop a VMware node.
|
||||
@ -129,7 +152,11 @@ async def stop_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
|
||||
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:
|
||||
"""
|
||||
Suspend a VMware node.
|
||||
@ -138,7 +165,11 @@ async def suspend_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
|
||||
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:
|
||||
"""
|
||||
Resume a VMware node.
|
||||
@ -147,7 +178,11 @@ async def resume_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
|
||||
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:
|
||||
"""
|
||||
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",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
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",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def update_vmware_node_nio(
|
||||
*,
|
||||
@ -202,7 +239,11 @@ async def update_vmware_node_nio(
|
||||
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(
|
||||
adapter_number: int,
|
||||
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)
|
||||
|
||||
|
||||
@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(
|
||||
*,
|
||||
adapter_number: int,
|
||||
@ -235,7 +279,9 @@ async def start_vmware_node_capture(
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT
|
||||
"/{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(
|
||||
adapter_number: int,
|
||||
@ -250,7 +296,10 @@ async def stop_vmware_node_capture(
|
||||
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(
|
||||
adapter_number: int,
|
||||
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")
|
||||
|
||||
|
||||
@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:
|
||||
"""
|
||||
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")
|
||||
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.
|
||||
"""
|
||||
|
||||
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:
|
||||
|
||||
await node.reset_console()
|
||||
|
||||
|
@ -20,15 +20,18 @@ API routes for VPCS nodes.
|
||||
|
||||
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.responses import StreamingResponse
|
||||
from typing import Union
|
||||
from uuid import UUID
|
||||
|
||||
from gns3server import schemas
|
||||
from gns3server.compute.vpcs import VPCS
|
||||
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"}}
|
||||
|
||||
router = APIRouter(responses=responses)
|
||||
@ -49,6 +52,7 @@ def dep_node(project_id: UUID, node_id: UUID) -> VPCSVM:
|
||||
response_model=schemas.VPCS,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create VMware node"}},
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
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()
|
||||
|
||||
|
||||
@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:
|
||||
"""
|
||||
Return a VPCS node.
|
||||
@ -78,7 +86,11 @@ def get_vpcs_node(node: VPCSVM = Depends(dep_node)) -> schemas.VPCS:
|
||||
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:
|
||||
"""
|
||||
Update a VPCS node.
|
||||
@ -92,7 +104,11 @@ def update_vpcs_node(node_data: schemas.VPCSUpdate, node: VPCSVM = Depends(dep_n
|
||||
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:
|
||||
"""
|
||||
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)
|
||||
|
||||
|
||||
@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(
|
||||
destination_node_id: UUID = Body(..., embed=True),
|
||||
node: VPCSVM = Depends(dep_node)) -> None:
|
||||
@ -113,7 +134,11 @@ async def duplicate_vpcs_node(
|
||||
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:
|
||||
"""
|
||||
Start a VPCS node.
|
||||
@ -122,7 +147,11 @@ async def start_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
|
||||
await node.start()
|
||||
|
||||
|
||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post(
|
||||
"/{node_id}/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def stop_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Stop a VPCS node.
|
||||
@ -131,7 +160,11 @@ async def stop_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
|
||||
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:
|
||||
"""
|
||||
Suspend a VPCS node.
|
||||
@ -141,7 +174,11 @@ async def suspend_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
|
||||
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:
|
||||
"""
|
||||
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",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
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",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def update_vpcs_node_nio(
|
||||
*,
|
||||
@ -196,7 +235,11 @@ async def update_vpcs_node_nio(
|
||||
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(
|
||||
*,
|
||||
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)
|
||||
|
||||
|
||||
@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(
|
||||
*,
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
@ -230,7 +276,9 @@ async def start_vpcs_node_capture(
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT
|
||||
"/{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(
|
||||
*,
|
||||
@ -246,13 +294,10 @@ async def stop_vpcs_node_capture(
|
||||
await node.stop_capture(port_number)
|
||||
|
||||
|
||||
@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def reset_console(node: VPCSVM = Depends(dep_node)) -> None:
|
||||
|
||||
await node.reset_console()
|
||||
|
||||
|
||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
|
||||
@router.get(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream",
|
||||
dependencies=[Depends(compute_authentication)]
|
||||
)
|
||||
async def stream_pcap_file(
|
||||
*,
|
||||
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")
|
||||
|
||||
|
||||
@router.websocket("/{node_id}/console/ws")
|
||||
async def console_ws(websocket: WebSocket, node: VPCSVM = Depends(dep_node)) -> None:
|
||||
@router.websocket(
|
||||
"/{node_id}/console/ws"
|
||||
)
|
||||
async def console_ws(
|
||||
websocket: Union[None, WebSocket] = Depends(ws_compute_authentication),
|
||||
node: VPCSVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
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
|
||||
# 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.security import OAuth2PasswordBearer
|
||||
@ -26,6 +26,7 @@ from gns3server.db.repositories.rbac import RbacRepository
|
||||
from gns3server.services import auth_service
|
||||
from .database import get_repository
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
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
|
||||
|
||||
except HTTPException as e:
|
||||
websocket_error = {"action": "log.error", "event": {"message": f"Could not authenticate while connecting to "
|
||||
f"WebSocket: {e.detail}"}}
|
||||
err_msg = f"Could not authenticate while connecting to controller WebSocket: {e.detail}"
|
||||
websocket_error = {"action": "log.error", "event": {"message": err_msg}}
|
||||
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 gns3server.controller import Controller
|
||||
from gns3server.config import Config
|
||||
from gns3server.controller.node import Node
|
||||
from gns3server.controller.project import Project
|
||||
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)
|
||||
|
||||
|
||||
@router.websocket("/{node_id}/console/ws", dependencies=[Depends(has_privilege_on_websocket("Node.Console"))])
|
||||
async def ws_console(websocket: WebSocket, node: Node = Depends(dep_node)) -> None:
|
||||
@router.websocket("/{node_id}/console/ws")
|
||||
async def ws_console(
|
||||
websocket: WebSocket,
|
||||
current_user: schemas.User = Depends(has_privilege_on_websocket("Node.Console")),
|
||||
node: Node = Depends(dep_node)
|
||||
) -> None:
|
||||
"""
|
||||
WebSocket console.
|
||||
|
||||
Required privilege: Node.Console
|
||||
"""
|
||||
|
||||
if current_user is None:
|
||||
return
|
||||
|
||||
compute = node.compute
|
||||
await websocket.accept()
|
||||
log.info(
|
||||
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:
|
||||
# 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:
|
||||
asyncio.ensure_future(ws_receive(ws_console_compute))
|
||||
async for msg in ws_console_compute:
|
||||
log.info(f"Forwarding console WebSocket to '{ws_console_compute_url}'")
|
||||
server_config = Config.instance().settings.Server
|
||||
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:
|
||||
await websocket.send_text(msg.data)
|
||||
elif msg.type == aiohttp.WSMsgType.BINARY:
|
||||
|
@ -11,15 +11,16 @@
|
||||
"product_url": "https://www.fortinet.com/products-services/products/management-reporting/fortianalyzer.html",
|
||||
"registry_version": 4,
|
||||
"status": "stable",
|
||||
"maintainer": "GNS3 Team",
|
||||
"maintainer_email": "developers@gns3.net",
|
||||
"usage": "Default username is admin, no password is set.\n\n- Versions 7.0 and higher require:\n--RAM: 8192 MB\n--CPU:4",
|
||||
"maintainer": "Ean Towne",
|
||||
"maintainer_email": "ean.fortinet@gmail.com",
|
||||
"usage": "Default username is admin, no password is set.\n\n- Versions lower than 7.0.x can reduce CPU/RAM",
|
||||
"symbol": "fortinet.svg",
|
||||
"port_name_format": "Port{port1}",
|
||||
"qemu": {
|
||||
"adapter_type": "e1000",
|
||||
"adapters": 4,
|
||||
"ram": 4096,
|
||||
"ram": 16384,
|
||||
"cpus": 4,
|
||||
"hda_disk_interface": "virtio",
|
||||
"hdb_disk_interface": "virtio",
|
||||
"arch": "x86_64",
|
||||
@ -28,6 +29,20 @@
|
||||
"kvm": "allow"
|
||||
},
|
||||
"images": [
|
||||
{
|
||||
"filename": "FAZ_VM64_KVM-v7.4.1-build2308-FORTINET.out.kvm.qcow2",
|
||||
"version": "7.4.1",
|
||||
"md5sum": "f30caac36854c2a0cc1e35c4ab5f310d",
|
||||
"filesize": 435310592,
|
||||
"download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
|
||||
},
|
||||
{
|
||||
"filename": "FAZ_VM64_KVM-v7.2.4-build1460-FORTINET.out.kvm.qcow2",
|
||||
"version": "7.2.4",
|
||||
"md5sum": "d53bd5c61cc3f5e387557dfcfe9bc530",
|
||||
"filesize": 363327488,
|
||||
"download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
|
||||
},
|
||||
{
|
||||
"filename": "FAZ_VM64_KVM-v7.2.2-build1334-FORTINET.out.kvm.qcow2",
|
||||
"version": "7.2.2",
|
||||
@ -42,6 +57,13 @@
|
||||
"filesize": 340631552,
|
||||
"download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
|
||||
},
|
||||
{
|
||||
"filename": "FAZ_VM64_KVM-v7.0.9-build0489-FORTINET.out.kvm.qcow2",
|
||||
"version": "7.0.9",
|
||||
"md5sum": "3f69c9bc4fa7776476edf0ce9728ebd7",
|
||||
"filesize": 347889664,
|
||||
"download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
|
||||
},
|
||||
{
|
||||
"filename": "FAZ_VM64_KVM-v7.0.6-build0372-FORTINET.out.kvm.qcow2",
|
||||
"version": "7.0.6",
|
||||
@ -56,6 +78,13 @@
|
||||
"filesize": 334184448,
|
||||
"download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
|
||||
},
|
||||
{
|
||||
"filename": "FAZ_VM64_KVM-v6.4.12-build2610-FORTINET.out.kvm.qcow2",
|
||||
"version": "6.4.12",
|
||||
"md5sum": "b9e164c2d4e778348a6a7107d375abf3",
|
||||
"filesize": 300691456,
|
||||
"download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
|
||||
},
|
||||
{
|
||||
"filename": "FAZ_VM64_KVM-v6-build2288-FORTINET.out.kvm.qcow2",
|
||||
"version": "6.4.5",
|
||||
@ -206,6 +235,20 @@
|
||||
}
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
"name": "7.4.1",
|
||||
"images": {
|
||||
"hda_disk_image": "FAZ_VM64_KVM-v7.4.1-build2308-FORTINET.out.kvm.qcow2",
|
||||
"hdb_disk_image": "empty30G.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "7.2.4",
|
||||
"images": {
|
||||
"hda_disk_image": "FAZ_VM64_KVM-v7.2.4-build1460-FORTINET.out.kvm.qcow2",
|
||||
"hdb_disk_image": "empty30G.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "7.2.2",
|
||||
"images": {
|
||||
@ -220,6 +263,13 @@
|
||||
"hdb_disk_image": "empty30G.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "7.0.9",
|
||||
"images": {
|
||||
"hda_disk_image": "FAZ_VM64_KVM-v7.0.9-build0489-FORTINET.out.kvm.qcow2",
|
||||
"hdb_disk_image": "empty30G.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "7.0.6",
|
||||
"images": {
|
||||
@ -234,6 +284,13 @@
|
||||
"hdb_disk_image": "empty30G.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "6.4.12",
|
||||
"images": {
|
||||
"hda_disk_image": "FAZ_VM64_KVM-v6.4.12-build2610-FORTINET.out.kvm.qcow2",
|
||||
"hdb_disk_image": "empty30G.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "6.4.5",
|
||||
"images": {
|
||||
|
@ -11,15 +11,15 @@
|
||||
"product_url": "http://www.fortinet.com/products/fortigate/virtual-appliances.html",
|
||||
"registry_version": 4,
|
||||
"status": "stable",
|
||||
"maintainer": "GNS3 Team",
|
||||
"maintainer_email": "developers@gns3.net",
|
||||
"maintainer": "Ean Towne",
|
||||
"maintainer_email": "ean.fortinet@gmail.com",
|
||||
"usage": "Default username is admin, no password is set.\n\n- FortiGate version 7.0.0 and above require 2GB RAM.\n\n- FortiGate versions higher than 7.2.0 trial license is VERY restrictive, not recommended for use.",
|
||||
"symbol": "fortinet.svg",
|
||||
"port_name_format": "Port{port1}",
|
||||
"qemu": {
|
||||
"adapter_type": "e1000",
|
||||
"adapters": 10,
|
||||
"ram": 1024,
|
||||
"ram": 2048,
|
||||
"hda_disk_interface": "virtio",
|
||||
"hdb_disk_interface": "virtio",
|
||||
"arch": "x86_64",
|
||||
@ -28,6 +28,20 @@
|
||||
"kvm": "allow"
|
||||
},
|
||||
"images": [
|
||||
{
|
||||
"filename": "FGT_VM64_KVM-v7.4.1.F-build2463-FORTINET.out.kvm.qcow2",
|
||||
"version": "7.4.1",
|
||||
"md5sum": "362a2f3d4ca842aaabd87191d4446584",
|
||||
"filesize": 116064256,
|
||||
"download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
|
||||
},
|
||||
{
|
||||
"filename": "FGT_VM64_KVM-v7.2.6.F-build1575-FORTINET.out.kvm.qcow2",
|
||||
"version": "7.2.6",
|
||||
"md5sum": "b5ef3c844abb4947f98b88ae0048660b",
|
||||
"filesize": 103022592,
|
||||
"download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
|
||||
},
|
||||
{
|
||||
"filename": "FGT_VM64_KVM-v7.2.4.F-build1396-FORTINET.out.kvm.qcow2",
|
||||
"version": "7.2.4",
|
||||
@ -49,6 +63,13 @@
|
||||
"filesize": 86704128,
|
||||
"download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
|
||||
},
|
||||
{
|
||||
"filename": "FGT_VM64_KVM-v7.0.12.M-build0523-FORTINET.out.kvm.qcow2",
|
||||
"version": "7.0.12",
|
||||
"md5sum": "7cd2452dde489c80f48c40a7f8a48c8e",
|
||||
"filesize": 88997888,
|
||||
"download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
|
||||
},
|
||||
{
|
||||
"filename": "FGT_VM64_KVM-v7.0.10.M-build0450-FORTINET.out.kvm.qcow2",
|
||||
"version": "7.0.10",
|
||||
@ -63,6 +84,13 @@
|
||||
"filesize": 77135872,
|
||||
"download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
|
||||
},
|
||||
{
|
||||
"filename": "FGT_VM64_KVM-v6.4.14.M-build2093-FORTINET.out.kvm.qcow2",
|
||||
"version": "6.4.14",
|
||||
"md5sum": "5758340f9d3e1a03139176ab46e63e8d",
|
||||
"filesize": 81461248,
|
||||
"download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
|
||||
},
|
||||
{
|
||||
"filename": "FGT_VM64_KVM-v6.4.12.M-build2060-FORTINET.out.kvm.qcow2",
|
||||
"version": "6.4.12",
|
||||
@ -304,6 +332,20 @@
|
||||
}
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
"name": "7.4.1",
|
||||
"images": {
|
||||
"hda_disk_image": "FGT_VM64_KVM-v7.4.1.F-build2463-FORTINET.out.kvm.qcow2",
|
||||
"hdb_disk_image": "empty30G.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "7.2.6",
|
||||
"images": {
|
||||
"hda_disk_image": "FGT_VM64_KVM-v7.2.6.F-build1575-FORTINET.out.kvm.qcow2",
|
||||
"hdb_disk_image": "empty30G.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "7.2.4",
|
||||
"images": {
|
||||
@ -325,6 +367,13 @@
|
||||
"hdb_disk_image": "empty30G.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "7.0.12",
|
||||
"images": {
|
||||
"hda_disk_image": "FGT_VM64_KVM-v7.0.12.M-build0523-FORTINET.out.kvm.qcow2",
|
||||
"hdb_disk_image": "empty30G.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "7.0.10",
|
||||
"images": {
|
||||
@ -339,6 +388,13 @@
|
||||
"hdb_disk_image": "empty30G.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "6.4.14",
|
||||
"images": {
|
||||
"hda_disk_image": "FGT_VM64_KVM-v6.4.14.M-build2093-FORTINET.out.kvm.qcow2",
|
||||
"hdb_disk_image": "empty30G.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "6.4.12",
|
||||
"images": {
|
||||
|
@ -11,15 +11,16 @@
|
||||
"product_url": "http://www.fortinet.com/products/fortimanager/virtual-security-management.html",
|
||||
"registry_version": 4,
|
||||
"status": "stable",
|
||||
"maintainer": "GNS3 Team",
|
||||
"maintainer_email": "developers@gns3.net",
|
||||
"usage": "Default username is admin, no password is set.\n\n- Versions 7.0 and higher require:\n--RAM: 8192 MB\n--CPU:4",
|
||||
"maintainer": "Ean Towne",
|
||||
"maintainer_email": "ean.fortinet@gmail.com",
|
||||
"usage": "Default username is admin, no password is set.\n\n- Versions lower than 7.0.x require less CPU/RAM",
|
||||
"symbol": "fortinet.svg",
|
||||
"port_name_format": "Port{port1}",
|
||||
"qemu": {
|
||||
"adapter_type": "virtio-net-pci",
|
||||
"adapters": 4,
|
||||
"ram": 2048,
|
||||
"ram": 8192,
|
||||
"cpus": 4,
|
||||
"hda_disk_interface": "virtio",
|
||||
"hdb_disk_interface": "virtio",
|
||||
"arch": "x86_64",
|
||||
@ -28,6 +29,20 @@
|
||||
"kvm": "allow"
|
||||
},
|
||||
"images": [
|
||||
{
|
||||
"filename": "FMG_VM64_KVM-v7.4.1-build2308-FORTINET.out.kvm.qcow2",
|
||||
"version": "7.4.1",
|
||||
"md5sum": "e542cc8f2d8f46e9c32b783bf31bef39",
|
||||
"filesize": 309387264,
|
||||
"download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
|
||||
},
|
||||
{
|
||||
"filename": "FMG_VM64_KVM-v7.2.4-build1460-FORTINET.out.kvm.qcow2",
|
||||
"version": "7.2.4",
|
||||
"md5sum": "98fa9830d9ecb5911a703d03b80026b6",
|
||||
"filesize": 261992448,
|
||||
"download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
|
||||
},
|
||||
{
|
||||
"filename": "FMG_VM64_KVM-v7.2.2-build1334-FORTINET.out.kvm.qcow2",
|
||||
"version": "7.2.2",
|
||||
@ -42,6 +57,13 @@
|
||||
"filesize": 242814976,
|
||||
"download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
|
||||
},
|
||||
{
|
||||
"filename": "FMG_VM64_KVM-v7.0.9-build0489-FORTINET.out.kvm.qcow2",
|
||||
"version": "7.0.9",
|
||||
"md5sum": "dbeb6a79b6e421000573dbbbdb50b8b5",
|
||||
"filesize": 247955456,
|
||||
"download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
|
||||
},
|
||||
{
|
||||
"filename": "FMG_VM64_KVM-v7.0.6-build0372-FORTINET.out.kvm.qcow2",
|
||||
"version": "7.0.6",
|
||||
@ -56,6 +78,13 @@
|
||||
"filesize": 237535232,
|
||||
"download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
|
||||
},
|
||||
{
|
||||
"filename": "FMG_VM64_KVM-v6.4.12-build2610-FORTINET.out.kvm.qcow2",
|
||||
"version": "6.4.12",
|
||||
"md5sum": "36c0dc531d921e5f1e1e09b030f7c813",
|
||||
"filesize": 219455488,
|
||||
"download_url": "https://support.fortinet.com/Download/FirmwareImages.aspx"
|
||||
},
|
||||
{
|
||||
"filename": "FMG_VM64_KVM-v6-build2288-FORTINET.out.kvm.qcow2",
|
||||
"version": "6.4.5",
|
||||
@ -206,6 +235,20 @@
|
||||
}
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
"name": "7.4.1",
|
||||
"images": {
|
||||
"hda_disk_image": "FMG_VM64_KVM-v7.4.1-build2308-FORTINET.out.kvm.qcow2",
|
||||
"hdb_disk_image": "empty30G.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "7.2.4",
|
||||
"images": {
|
||||
"hda_disk_image": "FMG_VM64_KVM-v7.2.4-build1460-FORTINET.out.kvm.qcow2",
|
||||
"hdb_disk_image": "empty30G.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "7.2.2",
|
||||
"images": {
|
||||
@ -220,6 +263,13 @@
|
||||
"hdb_disk_image": "empty30G.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "7.0.9",
|
||||
"images": {
|
||||
"hda_disk_image": "FMG_VM64_KVM-v7.0.9-build0489-FORTINET.out.kvm.qcow2",
|
||||
"hdb_disk_image": "empty30G.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "7.0.6",
|
||||
"images": {
|
||||
@ -234,6 +284,13 @@
|
||||
"hdb_disk_image": "empty30G.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "6.4.12",
|
||||
"images": {
|
||||
"hda_disk_image": "FMG_VM64_KVM-v6.4.12-build2610-FORTINET.out.kvm.qcow2",
|
||||
"hdb_disk_image": "empty30G.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "6.4.5",
|
||||
"images": {
|
||||
|
@ -485,6 +485,11 @@ class BaseNode:
|
||||
: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":
|
||||
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")
|
||||
|
||||
try:
|
||||
(telnet_reader, telnet_writer) = await asyncio.open_connection(
|
||||
self._manager.port_manager.console_host, self.console
|
||||
)
|
||||
host = self._manager.port_manager.console_host
|
||||
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:
|
||||
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):
|
||||
|
||||
try:
|
||||
|
@ -587,7 +587,12 @@ class IOUVM(BaseNode):
|
||||
# create a symbolic link to the image to avoid IOU error "failed code signing checks"
|
||||
# on newer images, see https://github.com/GNS3/gns3-server/issues/1484
|
||||
try:
|
||||
symlink = os.path.join(self.working_dir, os.path.basename(self.path))
|
||||
iou_image_path = os.path.basename(self.path)
|
||||
if len(iou_image_path) > 63:
|
||||
# IOU file basename length must be <= 63 chars
|
||||
iou_file_name, iou_file_ext = os.path.splitext(iou_image_path)
|
||||
iou_image_path = iou_file_name[:63 - len(iou_file_ext)] + iou_file_ext
|
||||
symlink = os.path.join(self.working_dir, iou_image_path)
|
||||
if os.path.islink(symlink):
|
||||
os.unlink(symlink)
|
||||
os.symlink(self.path, symlink)
|
||||
|
@ -13,7 +13,8 @@ logging console discriminator EXCESS
|
||||
!
|
||||
no ip icmp rate-limit unreachable
|
||||
!
|
||||
ip cef
|
||||
! due to some bugs with IOU, try to change the following line to 'ip cef' if your routing does not work
|
||||
no ip cef
|
||||
no ip domain-lookup
|
||||
!
|
||||
!
|
||||
|
@ -12,7 +12,8 @@ no ip icmp rate-limit unreachable
|
||||
!
|
||||
!
|
||||
!
|
||||
ip cef
|
||||
! due to some bugs with IOU, try to change the following line to 'ip cef' if your routing does not work
|
||||
no ip cef
|
||||
no ip domain-lookup
|
||||
!
|
||||
!
|
||||
|
@ -58,7 +58,7 @@ class CrashReport:
|
||||
Report crash to a third party service
|
||||
"""
|
||||
|
||||
DSN = "https://c6696321127aaa1b5bfd332536eb3676@o19455.ingest.sentry.io/38482"
|
||||
DSN = "https://803d7abaf0e865096421affb70ee9368@o19455.ingest.sentry.io/38482"
|
||||
_instance = None
|
||||
|
||||
def __init__(self):
|
||||
|
@ -124,6 +124,7 @@ class QemuAdapterType(str, Enum):
|
||||
i82559er = "i82559er"
|
||||
i82562 = "i82562"
|
||||
i82801 = "i82801"
|
||||
igb = "igb"
|
||||
ne2k_pci = "ne2k_pci"
|
||||
pcnet = "pcnet"
|
||||
rocker = "rocker"
|
||||
|
@ -170,6 +170,7 @@ class AdapterType(str, Enum):
|
||||
i82559er = 'i82559er'
|
||||
i82562 = 'i82562'
|
||||
i82801 = 'i82801'
|
||||
igb = 'igb'
|
||||
ne2k_pci = 'ne2k_pci'
|
||||
pcnet = 'pcnet'
|
||||
rocker = 'rocker'
|
||||
|
@ -267,9 +267,9 @@ class Server:
|
||||
else:
|
||||
log.info(f"Compute authentication is enabled with username '{config.Server.compute_username}'")
|
||||
|
||||
# we only support Python 3 version >= 3.7
|
||||
if sys.version_info < (3, 7, 0):
|
||||
raise SystemExit("Python 3.7 or higher is required")
|
||||
# we only support Python 3 version >= 3.8
|
||||
if sys.version_info < (3, 8, 0):
|
||||
raise SystemExit("Python 3.8 or higher is required")
|
||||
|
||||
log.info(
|
||||
"Running with Python {major}.{minor}.{micro} and has PID {pid}".format(
|
||||
|
@ -46,6 +46,6 @@
|
||||
|
||||
gtag('config', 'G-5D6FZL9923');
|
||||
</script>
|
||||
<script src="runtime.53e0b4d68251ad21.js" type="module"></script><script src="polyfills.6b3755eb116e6874.js" type="module"></script><script src="main.123149e4bf7e0712.js" type="module"></script>
|
||||
<script src="runtime.53e0b4d68251ad21.js" type="module"></script><script src="polyfills.6b3755eb116e6874.js" type="module"></script><script src="main.25c9e252a720e771.js" type="module"></script>
|
||||
|
||||
</body></html>
|
File diff suppressed because one or more lines are too long
1
gns3server/static/web-ui/main.25c9e252a720e771.js
Normal file
1
gns3server/static/web-ui/main.25c9e252a720e771.js
Normal file
File diff suppressed because one or more lines are too long
@ -22,8 +22,8 @@
|
||||
# or negative for a release candidate or beta (after the base version
|
||||
# number has been incremented)
|
||||
|
||||
__version__ = "3.0.0a4"
|
||||
__version_info__ = (3, 0, 0, -99)
|
||||
__version__ = "3.0.0.dev10"
|
||||
__version_info__ = (3, 0, 0, 99)
|
||||
|
||||
if "dev" in __version__:
|
||||
try:
|
||||
|
@ -10,7 +10,7 @@ authors = [
|
||||
{ name = "Jeremy Grossmann", email = "developers@gns3.com" }
|
||||
]
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.7"
|
||||
requires-python = ">=3.8"
|
||||
classifiers = [
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Environment :: Console",
|
||||
@ -21,11 +21,11 @@ classifiers = [
|
||||
"Natural Language :: English",
|
||||
"Operating System :: POSIX :: Linux",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.7",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Programming Language :: Python :: Implementation :: CPython"
|
||||
]
|
||||
|
||||
|
@ -1,8 +1,9 @@
|
||||
uvicorn==0.22.0 # v0.22.0 is the last to support Python 3.7
|
||||
fastapi==0.103.2
|
||||
uvicorn==0.23.2
|
||||
fastapi==0.104.0
|
||||
python-multipart==0.0.6
|
||||
websockets==11.0.3
|
||||
aiohttp==3.8.6,<3.9
|
||||
websockets==12.0
|
||||
aiohttp>=3.8.6,<3.9; python_version < '3.12'
|
||||
aiohttp==3.9.0b0; python_version == '3.12'
|
||||
async-timeout==4.0.3
|
||||
aiofiles==23.2.1
|
||||
Jinja2>=3.1.2,<3.2
|
||||
@ -16,7 +17,7 @@ alembic==1.12.0
|
||||
passlib[bcrypt]==1.7.4
|
||||
python-jose==3.3.0
|
||||
email-validator==2.0.0.post2
|
||||
watchfiles==0.20.0 # v0.20.0 is the last to support Python 3.7
|
||||
watchfiles==0.21.0
|
||||
zstandard==0.21.0
|
||||
platformdirs==3.11.0
|
||||
importlib-resources>=1.3; python_version <= '3.9'
|
||||
|
@ -20,6 +20,10 @@ from fastapi import FastAPI, status
|
||||
from fastapi.routing import APIRoute, APIWebSocketRoute
|
||||
from starlette.routing import Mount
|
||||
from httpx import AsyncClient
|
||||
from httpx_ws import aconnect_ws
|
||||
from httpx_ws.transport import ASGIWebSocketTransport
|
||||
|
||||
|
||||
|
||||
pytestmark = pytest.mark.asyncio
|
||||
|
||||
@ -37,6 +41,7 @@ ALLOWED_CONTROLLER_ENDPOINTS = [
|
||||
("/v3/symbols/default_symbols", "GET")
|
||||
]
|
||||
|
||||
|
||||
# Controller endpoints have a OAuth2 bearer token authentication
|
||||
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)
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
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
|
||||
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:
|
||||
if isinstance(route, Mount):
|
||||
for compute_route in route.routes:
|
||||
if isinstance(compute_route, APIRoute): # APIWebSocketRoute
|
||||
if isinstance(compute_route, APIRoute):
|
||||
for method in list(compute_route.methods):
|
||||
response = await getattr(unauthorized_client, method.lower())(route.path + compute_route.path)
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
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'
|
||||
}
|
||||
|
@ -214,19 +214,19 @@ async def test_termination_callback_error(vm, tmpdir):
|
||||
@pytest.mark.asyncio
|
||||
async def test_reload(vm):
|
||||
|
||||
with asyncio_patch("gns3server.compute.qemu.QemuVM._control_vm") as mock:
|
||||
with asyncio_patch("gns3server.compute.qemu.QemuVM._control_vm") as m:
|
||||
await vm.reload()
|
||||
assert mock.called_with("system_reset")
|
||||
m.assert_called_with("system_reset")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_suspend(vm):
|
||||
async def test_suspend(vm, running_subprocess_mock):
|
||||
|
||||
control_vm_result = MagicMock()
|
||||
control_vm_result.match.group.decode.return_value = "running"
|
||||
with asyncio_patch("gns3server.compute.qemu.QemuVM._control_vm", return_value=control_vm_result) as mock:
|
||||
vm._process = running_subprocess_mock
|
||||
with asyncio_patch("gns3server.compute.qemu.QemuVM._get_vm_status", return_value="running"):
|
||||
with asyncio_patch("gns3server.compute.qemu.QemuVM._control_vm") as m:
|
||||
await vm.suspend()
|
||||
assert mock.called_with("system_reset")
|
||||
m.assert_called_with("stop")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@ -500,14 +500,15 @@ def test_json(vm, compute_project):
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_control_vm(vm):
|
||||
async def test_control_vm(vm, running_subprocess_mock):
|
||||
|
||||
vm._process = MagicMock()
|
||||
vm._process = running_subprocess_mock
|
||||
vm._monitor = 4242
|
||||
reader = MagicMock()
|
||||
writer = MagicMock()
|
||||
with asyncio_patch("asyncio.open_connection", return_value=(reader, writer)):
|
||||
res = await vm._control_vm("test")
|
||||
assert writer.write.called_with("test")
|
||||
writer.write.assert_called_with(b"test\n")
|
||||
assert res is None
|
||||
|
||||
|
||||
@ -525,7 +526,7 @@ async def test_control_vm_expect_text(vm, running_subprocess_mock):
|
||||
|
||||
vm._monitor = 4242
|
||||
res = await vm._control_vm("test", [b"epic"])
|
||||
assert writer.write.called_with("test")
|
||||
writer.write.assert_called_with(b"test\n")
|
||||
|
||||
assert res == "epic product"
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user