mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-04 14:31:13 +02:00
Migration to FastAPI
This commit is contained in:
1 parent
c12b675691
commit
eb3cb8a41f
224 files changed
+11254
-13517
No files matched your search
@@ -0,0 +1,155 @@
|
||||
#
|
||||
# Copyright (C) 2020 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# 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 asyncio
|
||||
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.responses import JSONResponse
|
||||
from gns3server.controller.gns3vm.gns3_vm_error import GNS3VMError
|
||||
from gns3server.compute.error import ImageMissingError, NodeError
|
||||
from gns3server.ubridge.ubridge_error import UbridgeError
|
||||
|
||||
from gns3server.compute.compute_error import (
|
||||
ComputeError,
|
||||
ComputeNotFoundError,
|
||||
ComputeTimeoutError,
|
||||
ComputeForbiddenError,
|
||||
ComputeUnauthorizedError
|
||||
)
|
||||
|
||||
from . import capabilities
|
||||
from . import compute
|
||||
from . import projects
|
||||
from . import notifications
|
||||
from . import images
|
||||
from . import atm_switch_nodes
|
||||
from . import cloud_nodes
|
||||
from . import docker_nodes
|
||||
from . import dynamips_nodes
|
||||
from . import ethernet_hub_nodes
|
||||
from . import ethernet_switch_nodes
|
||||
from . import frame_relay_switch_nodes
|
||||
from . import iou_nodes
|
||||
from . import nat_nodes
|
||||
from . import qemu_nodes
|
||||
from . import virtualbox_nodes
|
||||
from . import vmware_nodes
|
||||
from . import vpcs_nodes
|
||||
|
||||
|
||||
compute_api = FastAPI(title="GNS3 compute API",
|
||||
description="This page describes the private compute API for GNS3",
|
||||
version="v2")
|
||||
|
||||
|
||||
@compute_api.exception_handler(ComputeError)
|
||||
async def controller_error_handler(request: Request, exc: ComputeError):
|
||||
return JSONResponse(
|
||||
status_code=409,
|
||||
content={"message": str(exc)},
|
||||
)
|
||||
|
||||
|
||||
@compute_api.exception_handler(ComputeTimeoutError)
|
||||
async def controller_timeout_error_handler(request: Request, exc: ComputeTimeoutError):
|
||||
return JSONResponse(
|
||||
status_code=408,
|
||||
content={"message": str(exc)},
|
||||
)
|
||||
|
||||
|
||||
@compute_api.exception_handler(ComputeUnauthorizedError)
|
||||
async def controller_unauthorized_error_handler(request: Request, exc: ComputeUnauthorizedError):
|
||||
return JSONResponse(
|
||||
status_code=401,
|
||||
content={"message": str(exc)},
|
||||
)
|
||||
|
||||
|
||||
@compute_api.exception_handler(ComputeForbiddenError)
|
||||
async def controller_forbidden_error_handler(request: Request, exc: ComputeForbiddenError):
|
||||
return JSONResponse(
|
||||
status_code=403,
|
||||
content={"message": str(exc)},
|
||||
)
|
||||
|
||||
|
||||
@compute_api.exception_handler(ComputeNotFoundError)
|
||||
async def controller_not_found_error_handler(request: Request, exc: ComputeNotFoundError):
|
||||
return JSONResponse(
|
||||
status_code=404,
|
||||
content={"message": str(exc)},
|
||||
)
|
||||
|
||||
|
||||
@compute_api.exception_handler(GNS3VMError)
|
||||
async def controller_error_handler(request: Request, exc: GNS3VMError):
|
||||
return JSONResponse(
|
||||
status_code=409,
|
||||
content={"message": str(exc)},
|
||||
)
|
||||
|
||||
|
||||
@compute_api.exception_handler(ImageMissingError)
|
||||
async def image_missing_error_handler(request: Request, exc: ImageMissingError):
|
||||
return JSONResponse(
|
||||
status_code=409,
|
||||
content={"message": str(exc), "image": exc.image, "exception": exc.__class__.__name__},
|
||||
)
|
||||
|
||||
|
||||
@compute_api.exception_handler(NodeError)
|
||||
async def image_missing_error_handler(request: Request, exc: NodeError):
|
||||
return JSONResponse(
|
||||
status_code=409,
|
||||
content={"message": str(exc), "exception": exc.__class__.__name__},
|
||||
)
|
||||
|
||||
|
||||
@compute_api.exception_handler(UbridgeError)
|
||||
async def image_missing_error_handler(request: Request, exc: UbridgeError):
|
||||
return JSONResponse(
|
||||
status_code=409,
|
||||
content={"message": str(exc), "exception": exc.__class__.__name__},
|
||||
)
|
||||
|
||||
|
||||
@compute_api.exception_handler(asyncio.CancelledError)
|
||||
async def image_missing_error_handler(request: Request, exc: asyncio.CancelledError):
|
||||
return JSONResponse(
|
||||
status_code=408,
|
||||
content={"message": "Request for '{}' cancelled".format(request.url.path)},
|
||||
)
|
||||
|
||||
|
||||
compute_api.include_router(capabilities.router, tags=["Capabilities"])
|
||||
compute_api.include_router(compute.router, tags=["Compute"])
|
||||
compute_api.include_router(notifications.router, tags=["Notifications"])
|
||||
compute_api.include_router(projects.router, tags=["Projects"])
|
||||
compute_api.include_router(images.router, tags=["Images"])
|
||||
compute_api.include_router(atm_switch_nodes.router, prefix="/projects/{project_id}/atm_switch/nodes", tags=["ATM switch"])
|
||||
compute_api.include_router(cloud_nodes.router, prefix="/projects/{project_id}/cloud/nodes", tags=["Cloud nodes"])
|
||||
compute_api.include_router(docker_nodes.router, prefix="/projects/{project_id}/docker/nodes", tags=["Docker nodes"])
|
||||
compute_api.include_router(dynamips_nodes.router, prefix="/projects/{project_id}/dynamips/nodes", tags=["Dynamips nodes"])
|
||||
compute_api.include_router(ethernet_hub_nodes.router, prefix="/projects/{project_id}/ethernet_hub/nodes", tags=["Ethernet hub nodes"])
|
||||
compute_api.include_router(ethernet_switch_nodes.router, prefix="/projects/{project_id}/ethernet_switch/nodes", tags=["Ethernet switch nodes"])
|
||||
compute_api.include_router(frame_relay_switch_nodes.router, prefix="/projects/{project_id}/frame_relay_switch/nodes", tags=["Frame Relay switch nodes"])
|
||||
compute_api.include_router(iou_nodes.router, prefix="/projects/{project_id}/iou/nodes", tags=["IOU nodes"])
|
||||
compute_api.include_router(nat_nodes.router, prefix="/projects/{project_id}/nat/nodes", tags=["NAT nodes"])
|
||||
compute_api.include_router(qemu_nodes.router, prefix="/projects/{project_id}/qemu/nodes", tags=["Qemu nodes"])
|
||||
compute_api.include_router(virtualbox_nodes.router, prefix="/projects/{project_id}/virtualbox/nodes", tags=["VirtualBox nodes"])
|
||||
compute_api.include_router(vmware_nodes.router, prefix="/projects/{project_id}/vmware/nodes", tags=["VMware nodes"])
|
||||
compute_api.include_router(vpcs_nodes.router, prefix="/projects/{project_id}/vpcs/nodes", tags=["VPCS nodes"])
|
||||
@@ -0,0 +1,221 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2020 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
API endpoints for ATM switch nodes.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from fastapi import APIRouter, Body, status
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from fastapi.responses import StreamingResponse
|
||||
from uuid import UUID
|
||||
|
||||
from gns3server.endpoints import schemas
|
||||
from gns3server.compute.dynamips import Dynamips
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/",
|
||||
response_model=schemas.ATMSwitch,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create ATM switch node"}})
|
||||
async def create_atm_switch(project_id: UUID, node_data: schemas.ATMSwitchCreate):
|
||||
"""
|
||||
Create a new ATM switch node.
|
||||
"""
|
||||
|
||||
# Use the Dynamips ATM switch to simulate this node
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
node = await dynamips_manager.create_node(node_data.pop("name"),
|
||||
str(project_id),
|
||||
node_data.get("node_id"),
|
||||
node_type="atm_switch",
|
||||
mappings=node_data.get("mappings"))
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.get("/{node_id}",
|
||||
response_model=schemas.ATMSwitch,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
def get_atm_switch(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Return an ATM switch node.
|
||||
"""
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.post("/{node_id}/duplicate",
|
||||
response_model=schemas.ATMSwitch,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def duplicate_atm_switch(project_id: UUID, node_id: UUID, destination_node_id: UUID = Body(..., embed=True)):
|
||||
"""
|
||||
Duplicate an ATM switch node.
|
||||
"""
|
||||
|
||||
new_node = await Dynamips.instance().duplicate_node(str(node_id), str(destination_node_id))
|
||||
return new_node.__json__()
|
||||
|
||||
|
||||
@router.put("/{node_id}",
|
||||
response_model=schemas.ATMSwitch,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def update_atm_switch(project_id: UUID, node_id: UUID, node_data: schemas.ATMSwitchUpdate):
|
||||
"""
|
||||
Update an ATM switch node.
|
||||
"""
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
if "name" in node_data and node.name != node_data["name"]:
|
||||
await node.set_name(node_data["name"])
|
||||
if "mappings" in node_data:
|
||||
node.mappings = node_data["mappings"]
|
||||
node.updated()
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.delete("/{node_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def delete_atm_switch_node(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Delete an ATM switch node.
|
||||
"""
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
await dynamips_manager.delete_node(str(node_id))
|
||||
|
||||
|
||||
@router.post("/{node_id}/start",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
def start_atm_switch(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Start an ATM switch node.
|
||||
This endpoint results in no action since ATM switch nodes are always on.
|
||||
"""
|
||||
|
||||
Dynamips.instance().get_node(str(node_id), project_id=str(project_id))
|
||||
|
||||
|
||||
@router.post("/{node_id}/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
def stop_atm_switch(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Stop an ATM switch node.
|
||||
This endpoint results in no action since ATM switch nodes are always on.
|
||||
"""
|
||||
|
||||
Dynamips.instance().get_node(str(node_id), project_id=str(project_id))
|
||||
|
||||
|
||||
@router.post("/{node_id}/suspend",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
def suspend_atm_switch(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Suspend an ATM switch node.
|
||||
This endpoint results in no action since ATM switch nodes are always on.
|
||||
"""
|
||||
|
||||
Dynamips.instance().get_node(str(node_id), project_id=str(project_id))
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def create_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, nio_data: schemas.UDPNIO):
|
||||
"""
|
||||
Add a NIO (Network Input/Output) to the node.
|
||||
The adapter number on the switch is always 0.
|
||||
"""
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
nio = await dynamips_manager.create_nio(node, jsonable_encoder(nio_data, exclude_unset=True))
|
||||
await node.add_nio(nio, port_number)
|
||||
return nio.__json__()
|
||||
|
||||
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def delete_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int):
|
||||
"""
|
||||
Remove a NIO (Network Input/Output) from the node.
|
||||
The adapter number on the switch is always 0.
|
||||
"""
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
nio = await node.remove_nio(port_number)
|
||||
await nio.delete()
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/start_capture",
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def start_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, node_capture_data: schemas.NodeCapture):
|
||||
"""
|
||||
Start a packet capture on the node.
|
||||
The adapter number on the switch is always 0.
|
||||
"""
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
pcap_file_path = os.path.join(node.project.capture_working_directory(), node_capture_data.capture_file_name)
|
||||
await node.start_capture(port_number, pcap_file_path, node_capture_data.data_link_type)
|
||||
return {"pcap_file_path": pcap_file_path}
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/stop_capture",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def stop_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int):
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
The adapter number on the switch is always 0.
|
||||
"""
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await node.stop_capture(port_number)
|
||||
|
||||
|
||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/pcap",
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def stream_pcap_file(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int):
|
||||
"""
|
||||
Stream the pcap capture file.
|
||||
The adapter number on the switch is always 0.
|
||||
"""
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
nio = node.get_nio(port_number)
|
||||
stream = dynamips_manager.stream_pcap_file(nio, node.project.id)
|
||||
return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap")
|
||||
@@ -0,0 +1,49 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2020 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
API endpoints for capabilities
|
||||
"""
|
||||
|
||||
import sys
|
||||
import psutil
|
||||
|
||||
from fastapi import APIRouter
|
||||
|
||||
from gns3server.version import __version__
|
||||
from gns3server.compute import MODULES
|
||||
from gns3server.utils.path import get_default_project_directory
|
||||
from gns3server.endpoints import schemas
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/capabilities",
|
||||
response_model=schemas.Capabilities
|
||||
)
|
||||
def get_compute_capabilities():
|
||||
|
||||
node_types = []
|
||||
for module in MODULES:
|
||||
node_types.extend(module.node_types())
|
||||
|
||||
return {"version": __version__,
|
||||
"platform": sys.platform,
|
||||
"cpus": psutil.cpu_count(logical=True),
|
||||
"memory": psutil.virtual_memory().total,
|
||||
"disk_size": psutil.disk_usage(get_default_project_directory()).total,
|
||||
"node_types": node_types}
|
||||
@@ -0,0 +1,240 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2020 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
API endpoints for cloud nodes.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from fastapi import APIRouter, status
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from fastapi.responses import StreamingResponse
|
||||
from typing import Union
|
||||
from uuid import UUID
|
||||
|
||||
from gns3server.endpoints import schemas
|
||||
from gns3server.compute.builtin import Builtin
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/",
|
||||
response_model=schemas.Cloud,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create cloud node"}})
|
||||
async def create_cloud(project_id: UUID, node_data: schemas.CloudCreate):
|
||||
"""
|
||||
Create a new cloud node.
|
||||
"""
|
||||
|
||||
builtin_manager = Builtin.instance()
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
node = await builtin_manager.create_node(node_data.pop("name"),
|
||||
str(project_id),
|
||||
node_data.get("node_id"),
|
||||
node_type="cloud",
|
||||
ports=node_data.get("ports_mapping"))
|
||||
|
||||
# add the remote console settings
|
||||
node.remote_console_host = node_data.get("remote_console_host", node.remote_console_host)
|
||||
node.remote_console_port = node_data.get("remote_console_port", node.remote_console_port)
|
||||
node.remote_console_type = node_data.get("remote_console_type", node.remote_console_type)
|
||||
node.remote_console_http_path = node_data.get("remote_console_http_path", node.remote_console_http_path)
|
||||
node.usage = node_data.get("usage", "")
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.get("/{node_id}",
|
||||
response_model=schemas.Cloud,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
def get_cloud(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Return a cloud node.
|
||||
"""
|
||||
|
||||
builtin_manager = Builtin.instance()
|
||||
node = builtin_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.put("/{node_id}",
|
||||
response_model=schemas.Cloud,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
def update_cloud(project_id: UUID, node_id: UUID, node_data: schemas.CloudUpdate):
|
||||
"""
|
||||
Update a cloud node.
|
||||
"""
|
||||
|
||||
builtin_manager = Builtin.instance()
|
||||
node = builtin_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
for name, value in node_data.items():
|
||||
if hasattr(node, name) and getattr(node, name) != value:
|
||||
setattr(node, name, value)
|
||||
node.updated()
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.delete("/{node_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def delete_node(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Delete a cloud node.
|
||||
"""
|
||||
|
||||
builtin_manager = Builtin.instance()
|
||||
await builtin_manager.delete_node(str(node_id))
|
||||
|
||||
|
||||
@router.post("/{node_id}/start",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def start_cloud(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Start a cloud node.
|
||||
"""
|
||||
|
||||
node = Builtin.instance().get_node(str(node_id), project_id=str(project_id))
|
||||
await node.start()
|
||||
|
||||
|
||||
@router.post("/{node_id}/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def stop_cloud(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Stop a cloud node.
|
||||
This endpoint results in no action since cloud nodes cannot be stopped.
|
||||
"""
|
||||
|
||||
Builtin.instance().get_node(str(node_id), project_id=str(project_id))
|
||||
|
||||
|
||||
@router.post("/{node_id}/suspend",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def suspend_cloud(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Suspend a cloud node.
|
||||
This endpoint results in no action since cloud nodes cannot be suspended.
|
||||
"""
|
||||
|
||||
Builtin.instance().get_node(str(node_id), project_id=str(project_id))
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def create_nio(project_id: UUID,
|
||||
node_id: UUID,
|
||||
adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO]):
|
||||
"""
|
||||
Add a NIO (Network Input/Output) to the node.
|
||||
The adapter number on the cloud is always 0.
|
||||
"""
|
||||
|
||||
builtin_manager = Builtin.instance()
|
||||
node = builtin_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
nio = builtin_manager.create_nio(jsonable_encoder(nio_data, exclude_unset=True))
|
||||
await node.add_nio(nio, port_number)
|
||||
return nio.__json__()
|
||||
|
||||
|
||||
@router.put("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def update_nio(project_id: UUID,
|
||||
node_id: UUID,
|
||||
adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO]):
|
||||
"""
|
||||
Update a NIO (Network Input/Output) to the node.
|
||||
The adapter number on the cloud is always 0.
|
||||
"""
|
||||
|
||||
builtin_manager = Builtin.instance()
|
||||
node = builtin_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
nio = node.get_nio(port_number)
|
||||
if nio_data.filters:
|
||||
nio.filters = nio_data.filters
|
||||
await node.update_nio(port_number, nio)
|
||||
return nio.__json__()
|
||||
|
||||
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def delete_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int):
|
||||
"""
|
||||
Remove a NIO (Network Input/Output) from the node.
|
||||
The adapter number on the cloud is always 0.
|
||||
"""
|
||||
|
||||
builtin_manager = Builtin.instance()
|
||||
node = builtin_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await node.remove_nio(port_number)
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/start_capture",
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def start_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, node_capture_data: schemas.NodeCapture):
|
||||
"""
|
||||
Start a packet capture on the node.
|
||||
The adapter number on the cloud is always 0.
|
||||
"""
|
||||
|
||||
builtin_manager = Builtin.instance()
|
||||
node = builtin_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
pcap_file_path = os.path.join(node.project.capture_working_directory(), node_capture_data.capture_file_name)
|
||||
await node.start_capture(port_number, pcap_file_path, node_capture_data.data_link_type)
|
||||
return {"pcap_file_path": pcap_file_path}
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/stop_capture",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def stop_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int):
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
The adapter number on the cloud is always 0.
|
||||
"""
|
||||
|
||||
builtin_manager = Builtin.instance()
|
||||
node = builtin_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await node.stop_capture(port_number)
|
||||
|
||||
|
||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/pcap",
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def stream_pcap_file(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int):
|
||||
"""
|
||||
Stream the pcap capture file.
|
||||
The adapter number on the cloud is always 0.
|
||||
"""
|
||||
|
||||
builtin_manager = Builtin.instance()
|
||||
node = builtin_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
nio = node.get_nio(port_number)
|
||||
stream = builtin_manager.stream_pcap_file(nio, node.project.id)
|
||||
return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap")
|
||||
@@ -0,0 +1,193 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2020 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
"""
|
||||
API endpoints for compute.
|
||||
"""
|
||||
|
||||
import os
|
||||
import psutil
|
||||
|
||||
from gns3server.config import Config
|
||||
from gns3server.utils.cpu_percent import CpuPercent
|
||||
from gns3server.version import __version__
|
||||
from gns3server.utils.path import get_default_project_directory
|
||||
from gns3server.compute.port_manager import PortManager
|
||||
from gns3server.compute.project_manager import ProjectManager
|
||||
from gns3server.utils.interfaces import interfaces
|
||||
from gns3server.compute.qemu import Qemu
|
||||
from gns3server.compute.virtualbox import VirtualBox
|
||||
from gns3server.compute.vmware import VMware
|
||||
from gns3server.endpoints import schemas
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Body, status
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from uuid import UUID
|
||||
from typing import Optional, List
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/projects/{project_id}/ports/udp",
|
||||
status_code=status.HTTP_201_CREATED)
|
||||
def allocate_udp_port(project_id: UUID) -> dict:
|
||||
"""
|
||||
Allocate an UDP port on the compute.
|
||||
"""
|
||||
|
||||
pm = ProjectManager.instance()
|
||||
project = pm.get_project(str(project_id))
|
||||
m = PortManager.instance()
|
||||
udp_port = m.get_free_udp_port(project)
|
||||
return {"udp_port": udp_port}
|
||||
|
||||
|
||||
@router.get("/network/interfaces")
|
||||
def network_interfaces() -> dict:
|
||||
"""
|
||||
List all the network interfaces available on the compute"
|
||||
"""
|
||||
|
||||
network_interfaces = interfaces()
|
||||
return network_interfaces
|
||||
|
||||
|
||||
@router.get("/network/ports")
|
||||
def network_ports() -> dict:
|
||||
"""
|
||||
List all the ports used on the compute"
|
||||
"""
|
||||
|
||||
m = PortManager.instance()
|
||||
return m.__json__()
|
||||
|
||||
|
||||
@router.get("/version")
|
||||
def version() -> dict:
|
||||
"""
|
||||
Retrieve the server version number.
|
||||
"""
|
||||
|
||||
config = Config.instance()
|
||||
local_server = config.get_section_config("Server").getboolean("local", False)
|
||||
return {"version": __version__, "local": local_server}
|
||||
|
||||
|
||||
@router.get("/statistics")
|
||||
def statistics() -> dict:
|
||||
"""
|
||||
Retrieve the server version number.
|
||||
"""
|
||||
|
||||
try:
|
||||
memory_total = psutil.virtual_memory().total
|
||||
memory_free = psutil.virtual_memory().available
|
||||
memory_used = memory_total - memory_free # actual memory usage in a cross platform fashion
|
||||
swap_total = psutil.swap_memory().total
|
||||
swap_free = psutil.swap_memory().free
|
||||
swap_used = psutil.swap_memory().used
|
||||
cpu_percent = int(CpuPercent.get())
|
||||
load_average_percent = [int(x / psutil.cpu_count() * 100) for x in psutil.getloadavg()]
|
||||
memory_percent = int(psutil.virtual_memory().percent)
|
||||
swap_percent = int(psutil.swap_memory().percent)
|
||||
disk_usage_percent = int(psutil.disk_usage(get_default_project_directory()).percent)
|
||||
except psutil.Error as e:
|
||||
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
#raise HTTPConflict(text="Psutil error detected: {}".format(e))
|
||||
|
||||
return {"memory_total": memory_total,
|
||||
"memory_free": memory_free,
|
||||
"memory_used": memory_used,
|
||||
"swap_total": swap_total,
|
||||
"swap_free": swap_free,
|
||||
"swap_used": swap_used,
|
||||
"cpu_usage_percent": cpu_percent,
|
||||
"memory_usage_percent": memory_percent,
|
||||
"swap_usage_percent": swap_percent,
|
||||
"disk_usage_percent": disk_usage_percent,
|
||||
"load_average_percent": load_average_percent}
|
||||
|
||||
|
||||
@router.get("/qemu/binaries")
|
||||
async def list_binaries(archs: Optional[List[str]] = Body(None, embed=True)):
|
||||
|
||||
return await Qemu.binary_list(archs)
|
||||
|
||||
|
||||
@router.get("/qemu/img-binaries")
|
||||
async def list_img_binaries():
|
||||
|
||||
return await Qemu.img_binary_list()
|
||||
|
||||
|
||||
@router.get("/qemu/capabilities")
|
||||
async def get_capabilities() -> dict:
|
||||
capabilities = {"kvm": []}
|
||||
kvms = await Qemu.get_kvm_archs()
|
||||
if kvms:
|
||||
capabilities["kvm"] = kvms
|
||||
return capabilities
|
||||
|
||||
|
||||
@router.post("/qemu/img",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={403: {"model": schemas.ErrorMessage, "description": "Forbidden to create Qemu image"}})
|
||||
async def create_img(image_data: schemas.QemuImageCreate):
|
||||
"""
|
||||
Create a Qemu image.
|
||||
"""
|
||||
|
||||
if os.path.isabs(image_data.path):
|
||||
config = Config.instance()
|
||||
if config.get_section_config("Server").getboolean("local", False) is False:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
|
||||
|
||||
await Qemu.instance().create_disk(image_data.qemu_img, image_data.path, jsonable_encoder(image_data, exclude_unset=True))
|
||||
|
||||
|
||||
@router.put("/qemu/img",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={403: {"model": schemas.ErrorMessage, "description": "Forbidden to update Qemu image"}})
|
||||
async def update_img(image_data: schemas.QemuImageUpdate):
|
||||
"""
|
||||
Update a Qemu image.
|
||||
"""
|
||||
|
||||
if os.path.isabs(image_data.path):
|
||||
config = Config.instance()
|
||||
if config.get_section_config("Server").getboolean("local", False) is False:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
|
||||
|
||||
if image_data.extend:
|
||||
await Qemu.instance().resize_disk(image_data.qemu_img, image_data.path, image_data.extend)
|
||||
|
||||
|
||||
@router.get("/virtualbox/vms",
|
||||
response_model=List[dict])
|
||||
async def get_virtualbox_vms():
|
||||
|
||||
vbox_manager = VirtualBox.instance()
|
||||
return await vbox_manager.list_vms()
|
||||
|
||||
|
||||
@router.get("/vmware/vms",
|
||||
response_model=List[dict])
|
||||
async def get_vms():
|
||||
vmware_manager = VMware.instance()
|
||||
return await vmware_manager.list_vms()
|
||||
|
||||
@@ -0,0 +1,338 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2020 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
API endpoints for Docker nodes.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from fastapi import APIRouter, Body, status
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from fastapi.responses import StreamingResponse
|
||||
from uuid import UUID
|
||||
|
||||
from gns3server.endpoints import schemas
|
||||
from gns3server.compute.docker import Docker
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/",
|
||||
response_model=schemas.Docker,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Docker node"}})
|
||||
async def create_docker_node(project_id: UUID, node_data: schemas.DockerCreate):
|
||||
"""
|
||||
Create a new Docker node.
|
||||
"""
|
||||
|
||||
docker_manager = Docker.instance()
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
container = await docker_manager.create_node(node_data.pop("name"),
|
||||
str(project_id),
|
||||
node_data.get("node_id"),
|
||||
image=node_data.pop("image"),
|
||||
start_command=node_data.get("start_command"),
|
||||
environment=node_data.get("environment"),
|
||||
adapters=node_data.get("adapters"),
|
||||
console=node_data.get("console"),
|
||||
console_type=node_data.get("console_type"),
|
||||
console_resolution=node_data.get("console_resolution", "1024x768"),
|
||||
console_http_port=node_data.get("console_http_port", 80),
|
||||
console_http_path=node_data.get("console_http_path", "/"),
|
||||
aux=node_data.get("aux"),
|
||||
aux_type=node_data.pop("aux_type", "none"),
|
||||
extra_hosts=node_data.get("extra_hosts"),
|
||||
extra_volumes=node_data.get("extra_volumes"),
|
||||
memory=node_data.get("memory", 0),
|
||||
cpus=node_data.get("cpus", 0))
|
||||
for name, value in node_data.items():
|
||||
if name != "node_id":
|
||||
if hasattr(container, name) and getattr(container, name) != value:
|
||||
setattr(container, name, value)
|
||||
|
||||
return container.__json__()
|
||||
|
||||
|
||||
@router.get("/{node_id}",
|
||||
response_model=schemas.Docker,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
def get_docker_node(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Return a Docker node.
|
||||
"""
|
||||
|
||||
docker_manager = Docker.instance()
|
||||
container = docker_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
return container.__json__()
|
||||
|
||||
|
||||
@router.put("/{node_id}",
|
||||
response_model=schemas.Docker,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def update_docker(project_id: UUID, node_id: UUID, node_data: schemas.DockerUpdate):
|
||||
"""
|
||||
Update a Docker node.
|
||||
"""
|
||||
|
||||
docker_manager = Docker.instance()
|
||||
container = docker_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
|
||||
props = [
|
||||
"name", "console", "console_type", "aux", "aux_type", "console_resolution",
|
||||
"console_http_port", "console_http_path", "start_command",
|
||||
"environment", "adapters", "extra_hosts", "extra_volumes",
|
||||
"memory", "cpus"
|
||||
]
|
||||
|
||||
changed = False
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
for prop in props:
|
||||
if prop in node_data and node_data[prop] != getattr(container, prop):
|
||||
setattr(container, prop, node_data[prop])
|
||||
changed = True
|
||||
# We don't call container.update for nothing because it will restart the container
|
||||
if changed:
|
||||
await container.update()
|
||||
container.updated()
|
||||
return container.__json__()
|
||||
|
||||
|
||||
@router.post("/{node_id}/start",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def start_docker_node(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Start a Docker node.
|
||||
"""
|
||||
|
||||
docker_manager = Docker.instance()
|
||||
container = docker_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await container.start()
|
||||
|
||||
|
||||
@router.post("/{node_id}/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def stop_docker_node(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Stop a Docker node.
|
||||
"""
|
||||
|
||||
docker_manager = Docker.instance()
|
||||
container = docker_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await container.stop()
|
||||
|
||||
|
||||
@router.post("/{node_id}/suspend",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def suspend_docker_node(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Suspend a Docker node.
|
||||
"""
|
||||
|
||||
docker_manager = Docker.instance()
|
||||
container = docker_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await container.pause()
|
||||
|
||||
|
||||
@router.post("/{node_id}/reload",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def reload_docker_node(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Reload a Docker node.
|
||||
"""
|
||||
|
||||
docker_manager = Docker.instance()
|
||||
container = docker_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await container.restart()
|
||||
|
||||
|
||||
@router.post("/{node_id}/pause",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def pause_docker_node(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Pause a Docker node.
|
||||
"""
|
||||
|
||||
docker_manager = Docker.instance()
|
||||
container = docker_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await container.pause()
|
||||
|
||||
|
||||
@router.post("/{node_id}/unpause",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def unpause_docker_node(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Unpause a Docker node.
|
||||
"""
|
||||
|
||||
docker_manager = Docker.instance()
|
||||
container = docker_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await container.unpause()
|
||||
|
||||
|
||||
@router.delete("/{node_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def delete_docker_node(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Delete a Docker node.
|
||||
"""
|
||||
|
||||
docker_manager = Docker.instance()
|
||||
container = docker_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await container.delete()
|
||||
|
||||
|
||||
@router.post("/{node_id}/duplicate",
|
||||
response_model=schemas.Docker,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def duplicate_docker_node(project_id: UUID, node_id: UUID, destination_node_id: UUID = Body(..., embed=True)):
|
||||
"""
|
||||
Duplicate a Docker node.
|
||||
"""
|
||||
|
||||
docker_manager = Docker.instance()
|
||||
new_node = await docker_manager.duplicate_node(str(node_id), str(destination_node_id))
|
||||
return new_node.__json__()
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def create_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, nio_data: schemas.UDPNIO):
|
||||
"""
|
||||
Add a NIO (Network Input/Output) to the node.
|
||||
The port number on the Docker node is always 0.
|
||||
"""
|
||||
|
||||
docker_manager = Docker.instance()
|
||||
container = docker_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
nio = docker_manager.create_nio(jsonable_encoder(nio_data, exclude_unset=True))
|
||||
await container.adapter_add_nio_binding(adapter_number, nio)
|
||||
return nio.__json__()
|
||||
|
||||
|
||||
@router.put("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def update_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, nio_data: schemas.UDPNIO):
|
||||
"""
|
||||
Update a NIO (Network Input/Output) on the node.
|
||||
The port number on the Docker node is always 0.
|
||||
"""
|
||||
|
||||
docker_manager = Docker.instance()
|
||||
container = docker_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
nio = container.get_nio(adapter_number)
|
||||
if nio_data.filters:
|
||||
nio.filters = nio_data.filters
|
||||
await container.adapter_update_nio_binding(adapter_number, nio)
|
||||
return nio.__json__()
|
||||
|
||||
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def delete_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int):
|
||||
"""
|
||||
Delete a NIO (Network Input/Output) from the node.
|
||||
The port number on the Docker node is always 0.
|
||||
"""
|
||||
|
||||
docker_manager = Docker.instance()
|
||||
container = docker_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await container.adapter_remove_nio_binding(adapter_number)
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/start_capture",
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def start_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, node_capture_data: schemas.NodeCapture):
|
||||
"""
|
||||
Start a packet capture on the node.
|
||||
The port number on the Docker node is always 0.
|
||||
"""
|
||||
|
||||
docker_manager = Docker.instance()
|
||||
container = docker_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
pcap_file_path = os.path.join(container.project.capture_working_directory(), node_capture_data.capture_file_name)
|
||||
await container.start_capture(adapter_number, pcap_file_path)
|
||||
return {"pcap_file_path": str(pcap_file_path)}
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/stop_capture",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def stop_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int):
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
The port number on the Docker node is always 0.
|
||||
"""
|
||||
|
||||
docker_manager = Docker.instance()
|
||||
container = docker_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await container.stop_capture(adapter_number)
|
||||
|
||||
|
||||
@router.post("/{node_id}/console/reset",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def reset_console(project_id: UUID, node_id: UUID):
|
||||
|
||||
docker_manager = Docker.instance()
|
||||
container = docker_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await container.reset_console()
|
||||
|
||||
|
||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/pcap",
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def stream_pcap_file(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int):
|
||||
"""
|
||||
Stream the pcap capture file.
|
||||
The port number on the Docker node is always 0.
|
||||
"""
|
||||
|
||||
docker_manager = Docker.instance()
|
||||
container = docker_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
nio = container.get_nio(adapter_number)
|
||||
stream = docker_manager.stream_pcap_file(nio, container.project.id)
|
||||
return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap")
|
||||
|
||||
# @Route.get(
|
||||
# r"/projects/{project_id}/docker/nodes/{node_id}/console/ws",
|
||||
# description="WebSocket for console",
|
||||
# parameters={
|
||||
# "project_id": "Project UUID",
|
||||
# "node_id": "Node UUID",
|
||||
# })
|
||||
# async def console_ws(request, response):
|
||||
#
|
||||
# docker_manager = Docker.instance()
|
||||
# container = docker_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
# return await container.start_websocket_console(request)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,338 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2020 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
API endpoints for Dynamips nodes.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from fastapi import APIRouter, status
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from fastapi.responses import StreamingResponse
|
||||
from typing import List
|
||||
from uuid import UUID
|
||||
|
||||
from gns3server.compute.dynamips import Dynamips
|
||||
from gns3server.compute.dynamips.dynamips_error import DynamipsError
|
||||
from gns3server.compute.project_manager import ProjectManager
|
||||
from gns3server.endpoints import schemas
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
DEFAULT_CHASSIS = {
|
||||
"c1700": "1720",
|
||||
"c2600": "2610",
|
||||
"c3600": "3640"
|
||||
}
|
||||
|
||||
|
||||
@router.post("/",
|
||||
response_model=schemas.Dynamips,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Dynamips node"}})
|
||||
async def create_router(project_id: UUID, node_data: schemas.DynamipsCreate):
|
||||
"""
|
||||
Create a new Dynamips router.
|
||||
"""
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
platform = node_data.platform
|
||||
chassis = None
|
||||
if not node_data.chassis and platform in DEFAULT_CHASSIS:
|
||||
chassis = DEFAULT_CHASSIS[platform]
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
vm = await dynamips_manager.create_node(node_data.pop("name"),
|
||||
str(project_id),
|
||||
node_data.get("node_id"),
|
||||
dynamips_id=node_data.get("dynamips_id"),
|
||||
platform=platform,
|
||||
console=node_data.get("console"),
|
||||
console_type=node_data.get("console_type", "telnet"),
|
||||
aux=node_data.get("aux"),
|
||||
aux_type=node_data.pop("aux_type", "none"),
|
||||
chassis=chassis,
|
||||
node_type="dynamips")
|
||||
await dynamips_manager.update_vm_settings(vm, node_data)
|
||||
return vm.__json__()
|
||||
|
||||
|
||||
@router.get("/{node_id}",
|
||||
response_model=schemas.Dynamips,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
def get_router(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Return Dynamips router.
|
||||
"""
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vm = dynamips_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
return vm.__json__()
|
||||
|
||||
|
||||
@router.put("/{node_id}",
|
||||
response_model=schemas.Dynamips,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def update_router(project_id: UUID, node_id: UUID, node_data: schemas.DynamipsUpdate):
|
||||
"""
|
||||
Update a Dynamips router.
|
||||
"""
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vm = dynamips_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await dynamips_manager.update_vm_settings(vm, jsonable_encoder(node_data, exclude_unset=True))
|
||||
vm.updated()
|
||||
return vm.__json__()
|
||||
|
||||
|
||||
@router.delete("/{node_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def delete_router(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Delete a Dynamips router.
|
||||
"""
|
||||
|
||||
# check the project_id exists
|
||||
ProjectManager.instance().get_project(str(project_id))
|
||||
await Dynamips.instance().delete_node(str(node_id))
|
||||
|
||||
|
||||
@router.post("/{node_id}/start",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def start_router(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Start a Dynamips router.
|
||||
"""
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vm = dynamips_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
try:
|
||||
await dynamips_manager.ghost_ios_support(vm)
|
||||
except GeneratorExit:
|
||||
pass
|
||||
await vm.start()
|
||||
|
||||
|
||||
@router.post("/{node_id}/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def stop_router(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Stop a Dynamips router.
|
||||
"""
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vm = dynamips_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await vm.stop()
|
||||
|
||||
|
||||
@router.post("/{node_id}/suspend",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def suspend_router(project_id: UUID, node_id: UUID):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vm = dynamips_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await vm.suspend()
|
||||
|
||||
|
||||
@router.post("/{node_id}/resume",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def resume_router(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Resume a suspended Dynamips router.
|
||||
"""
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vm = dynamips_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await vm.resume()
|
||||
|
||||
|
||||
@router.post("/{node_id}/reload",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def reload(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Reload a suspended Dynamips router.
|
||||
"""
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vm = dynamips_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await vm.reload()
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def create_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, nio_data: schemas.UDPNIO):
|
||||
"""
|
||||
Add a NIO (Network Input/Output) to the node.
|
||||
"""
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vm = dynamips_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
nio = await dynamips_manager.create_nio(vm, jsonable_encoder(nio_data, exclude_unset=True))
|
||||
await vm.slot_add_nio_binding(adapter_number, port_number, nio)
|
||||
return nio.__json__()
|
||||
|
||||
|
||||
@router.put("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def update_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, nio_data: schemas.UDPNIO):
|
||||
"""
|
||||
Update a NIO (Network Input/Output) on the node.
|
||||
"""
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vm = dynamips_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
nio = vm.get_nio(adapter_number, port_number)
|
||||
if nio_data.filters:
|
||||
nio.filters = nio_data.filters
|
||||
await vm.slot_update_nio_binding(adapter_number, port_number, nio)
|
||||
return nio.__json__()
|
||||
|
||||
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def delete_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int):
|
||||
"""
|
||||
Delete a NIO (Network Input/Output) from the node.
|
||||
"""
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vm = dynamips_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
nio = await vm.slot_remove_nio_binding(adapter_number, port_number)
|
||||
await nio.delete()
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/start_capture",
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def start_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, node_capture_data: schemas.NodeCapture):
|
||||
"""
|
||||
Start a packet capture on the node.
|
||||
"""
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vm = dynamips_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
pcap_file_path = os.path.join(vm.project.capture_working_directory(), node_capture_data.capture_file_name)
|
||||
|
||||
if sys.platform.startswith('win'):
|
||||
# FIXME: Dynamips (Cygwin actually) doesn't like non ascii paths on Windows
|
||||
try:
|
||||
pcap_file_path.encode('ascii')
|
||||
except UnicodeEncodeError:
|
||||
raise DynamipsError('The capture file path "{}" must only contain ASCII (English) characters'.format(pcap_file_path))
|
||||
|
||||
await vm.start_capture(adapter_number, port_number, pcap_file_path, node_capture_data.data_link_type)
|
||||
return {"pcap_file_path": pcap_file_path}
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/stop_capture",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def stop_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int):
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
"""
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vm = dynamips_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await vm.stop_capture(adapter_number, port_number)
|
||||
|
||||
|
||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/pcap",
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def stream_pcap_file(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int):
|
||||
"""
|
||||
Stream the pcap capture file.
|
||||
"""
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vm = dynamips_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
nio = vm.get_nio(adapter_number, port_number)
|
||||
stream = dynamips_manager.stream_pcap_file(nio, vm.project.id)
|
||||
return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap")
|
||||
|
||||
|
||||
@router.get("/{node_id}/idlepc_proposals",
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def get_idlepcs(project_id: UUID, node_id: UUID) -> List[str]:
|
||||
"""
|
||||
Retrieve Dynamips idle-pc proposals
|
||||
"""
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vm = dynamips_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await vm.set_idlepc("0x0")
|
||||
return await vm.get_idle_pc_prop()
|
||||
|
||||
|
||||
@router.get("/{node_id}/auto_idlepc",
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def get_auto_idlepc(project_id: UUID, node_id: UUID) -> dict:
|
||||
"""
|
||||
Get an automatically guessed best idle-pc value.
|
||||
"""
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vm = dynamips_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
idlepc = await dynamips_manager.auto_idlepc(vm)
|
||||
return {"idlepc": idlepc}
|
||||
|
||||
|
||||
@router.post("/{node_id}/duplicate",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def duplicate_router(project_id: UUID, node_id: UUID, destination_node_id: UUID):
|
||||
"""
|
||||
Duplicate a router.
|
||||
"""
|
||||
|
||||
new_node = await Dynamips.instance().duplicate_node(str(node_id), str(destination_node_id))
|
||||
return new_node.__json__()
|
||||
|
||||
|
||||
# @Route.get(
|
||||
# r"/projects/{project_id}/dynamips/nodes/{node_id}/console/ws",
|
||||
# description="WebSocket for console",
|
||||
# parameters={
|
||||
# "project_id": "Project UUID",
|
||||
# "node_id": "Node UUID",
|
||||
# })
|
||||
# async def console_ws(request, response):
|
||||
#
|
||||
# dynamips_manager = Dynamips.instance()
|
||||
# vm = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
# return await vm.start_websocket_console(request)
|
||||
|
||||
|
||||
@router.post("/{node_id}/console/reset",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def reset_console(project_id: UUID, node_id: UUID):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vm = dynamips_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await vm.reset_console()
|
||||
@@ -0,0 +1,222 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2020 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
API endpoints for Ethernet hub nodes.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from fastapi import APIRouter, Body, status
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from fastapi.responses import StreamingResponse
|
||||
from uuid import UUID
|
||||
|
||||
from gns3server.compute.dynamips import Dynamips
|
||||
from gns3server.endpoints import schemas
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/",
|
||||
response_model=schemas.EthernetHub,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Ethernet hub node"}})
|
||||
async def create_ethernet_hub(project_id: UUID, node_data: schemas.EthernetHubCreate):
|
||||
"""
|
||||
Create a new Ethernet hub.
|
||||
"""
|
||||
|
||||
# Use the Dynamips Ethernet hub to simulate this node
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
node = await dynamips_manager.create_node(node_data.pop("name"),
|
||||
str(project_id),
|
||||
node_data.get("node_id"),
|
||||
node_type="ethernet_hub",
|
||||
ports=node_data.get("ports_mapping"))
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.get("/{node_id}",
|
||||
response_model=schemas.EthernetHub,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
def get_ethernet_hub(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Return an Ethernet hub.
|
||||
"""
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.post("/{node_id}/duplicate",
|
||||
response_model=schemas.EthernetHub,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def duplicate_ethernet_hub(project_id: UUID, node_id: UUID, destination_node_id: UUID = Body(..., embed=True)):
|
||||
"""
|
||||
Duplicate an Ethernet hub.
|
||||
"""
|
||||
|
||||
new_node = await Dynamips.instance().duplicate_node(str(node_id), str(destination_node_id))
|
||||
return new_node.__json__()
|
||||
|
||||
|
||||
@router.put("/{node_id}",
|
||||
response_model=schemas.EthernetHub,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def update_ethernet_hub(project_id: UUID, node_id: UUID, node_data: schemas.EthernetHubUpdate):
|
||||
"""
|
||||
Update an Ethernet hub.
|
||||
"""
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
if "name" in node_data and node.name != node_data["name"]:
|
||||
await node.set_name(node_data["name"])
|
||||
if "ports_mapping" in node_data:
|
||||
node.ports_mapping = node_data["ports_mapping"]
|
||||
node.updated()
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.delete("/{node_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def delete_ethernet_hub(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Delete an Ethernet hub.
|
||||
"""
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
await dynamips_manager.delete_node(str(node_id))
|
||||
|
||||
|
||||
@router.post("/{node_id}/start",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
def start_ethernet_hub(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Start an Ethernet hub.
|
||||
This endpoint results in no action since Ethernet hub nodes are always on.
|
||||
"""
|
||||
|
||||
Dynamips.instance().get_node(str(node_id), project_id=str(project_id))
|
||||
|
||||
|
||||
@router.post("/{node_id}/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
def stop_ethernet_hub(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Stop an Ethernet hub.
|
||||
This endpoint results in no action since Ethernet hub nodes are always on.
|
||||
"""
|
||||
|
||||
Dynamips.instance().get_node(str(node_id), project_id=str(project_id))
|
||||
|
||||
|
||||
@router.post("/{node_id}/suspend",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
def suspend_ethernet_hub(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Suspend an Ethernet hub.
|
||||
This endpoint results in no action since Ethernet hub nodes are always on.
|
||||
"""
|
||||
|
||||
Dynamips.instance().get_node(str(node_id), project_id=str(project_id))
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def create_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, nio_data: schemas.UDPNIO):
|
||||
"""
|
||||
Add a NIO (Network Input/Output) to the node.
|
||||
The adapter number on the hub is always 0.
|
||||
"""
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
nio = await dynamips_manager.create_nio(node, jsonable_encoder(nio_data, exclude_unset=True))
|
||||
await node.add_nio(nio, port_number)
|
||||
return nio.__json__()
|
||||
|
||||
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def delete_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int):
|
||||
"""
|
||||
Delete a NIO (Network Input/Output) from the node.
|
||||
The adapter number on the hub is always 0.
|
||||
"""
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
nio = await node.remove_nio(port_number)
|
||||
await nio.delete()
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/start_capture",
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def start_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, node_capture_data: schemas.NodeCapture):
|
||||
"""
|
||||
Start a packet capture on the node.
|
||||
The adapter number on the hub is always 0.
|
||||
"""
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
pcap_file_path = os.path.join(node.project.capture_working_directory(), node_capture_data.capture_file_name)
|
||||
await node.start_capture(port_number, pcap_file_path, node_capture_data.data_link_type)
|
||||
return {"pcap_file_path": pcap_file_path}
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/stop_capture",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def stop_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int):
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
The adapter number on the hub is always 0.
|
||||
"""
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await node.stop_capture(port_number)
|
||||
|
||||
|
||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/pcap",
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def stream_pcap_file(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int):
|
||||
"""
|
||||
Stream the pcap capture file.
|
||||
The adapter number on the hub is always 0.
|
||||
"""
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
nio = node.get_nio(port_number)
|
||||
stream = dynamips_manager.stream_pcap_file(nio, node.project.id)
|
||||
return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap")
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2020 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
API endpoints for Ethernet switch nodes.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from fastapi import APIRouter, Body, status
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from fastapi.responses import StreamingResponse
|
||||
from uuid import UUID
|
||||
|
||||
from gns3server.compute.dynamips import Dynamips
|
||||
from gns3server.endpoints import schemas
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/",
|
||||
response_model=schemas.EthernetSwitch,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Ethernet hub node"}})
|
||||
async def create_ethernet_switch(project_id: UUID, node_data: schemas.EthernetSwitchCreate):
|
||||
"""
|
||||
Create a new Ethernet switch.
|
||||
"""
|
||||
|
||||
# Use the Dynamips Ethernet switch to simulate this node
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
node = await dynamips_manager.create_node(node_data.pop("name"),
|
||||
str(project_id),
|
||||
node_data.get("node_id"),
|
||||
console=node_data.get("console"),
|
||||
console_type=node_data.get("console_type"),
|
||||
node_type="ethernet_switch",
|
||||
ports=node_data.get("ports_mapping"))
|
||||
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.get("/{node_id}",
|
||||
response_model=schemas.EthernetSwitch,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
def get_ethernet_switch(project_id: UUID, node_id: UUID):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.post("/{node_id}/duplicate",
|
||||
response_model=schemas.EthernetSwitch,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def duplicate_ethernet_switch(project_id: UUID, node_id: UUID, destination_node_id: UUID = Body(..., embed=True)):
|
||||
"""
|
||||
Duplicate an Ethernet switch.
|
||||
"""
|
||||
|
||||
new_node = await Dynamips.instance().duplicate_node(str(node_id), str(destination_node_id))
|
||||
return new_node.__json__()
|
||||
|
||||
|
||||
@router.put("/{node_id}",
|
||||
response_model=schemas.EthernetSwitch,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def update_ethernet_switch(project_id: UUID, node_id: UUID, node_data: schemas.EthernetSwitchUpdate):
|
||||
"""
|
||||
Update an Ethernet switch.
|
||||
"""
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
if "name" in node_data and node.name != node_data["name"]:
|
||||
await node.set_name(node_data["name"])
|
||||
if "ports_mapping" in node_data:
|
||||
node.ports_mapping = node_data["ports_mapping"]
|
||||
await node.update_port_settings()
|
||||
if "console_type" in node_data:
|
||||
node.console_type = node_data["console_type"]
|
||||
node.updated()
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.delete("/{node_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def delete_ethernet_switch(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Delete an Ethernet switch.
|
||||
"""
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
await dynamips_manager.delete_node(str(node_id))
|
||||
|
||||
|
||||
@router.post("/{node_id}/start",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
def start_ethernet_switch(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Start an Ethernet switch.
|
||||
This endpoint results in no action since Ethernet switch nodes are always on.
|
||||
"""
|
||||
|
||||
Dynamips.instance().get_node(str(node_id), project_id=str(project_id))
|
||||
|
||||
|
||||
@router.post("/{node_id}/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
def stop_ethernet_switch(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Stop an Ethernet switch.
|
||||
This endpoint results in no action since Ethernet switch nodes are always on.
|
||||
"""
|
||||
|
||||
Dynamips.instance().get_node(str(node_id), project_id=str(project_id))
|
||||
|
||||
|
||||
@router.post("/{node_id}/suspend",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
def suspend(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Suspend an Ethernet switch.
|
||||
This endpoint results in no action since Ethernet switch nodes are always on.
|
||||
"""
|
||||
|
||||
Dynamips.instance().get_node(str(node_id), project_id=str(project_id))
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def create_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, nio_data: schemas.UDPNIO):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
nio = await dynamips_manager.create_nio(node, jsonable_encoder(nio_data, exclude_unset=True))
|
||||
await node.add_nio(nio, port_number)
|
||||
return nio.__json__()
|
||||
|
||||
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def delete_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int):
|
||||
"""
|
||||
Delete a NIO (Network Input/Output) from the node.
|
||||
The adapter number on the switch is always 0.
|
||||
"""
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
nio = await node.remove_nio(port_number)
|
||||
await nio.delete()
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/start_capture",
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def start_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, node_capture_data: schemas.NodeCapture):
|
||||
"""
|
||||
Start a packet capture on the node.
|
||||
The adapter number on the switch is always 0.
|
||||
"""
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
pcap_file_path = os.path.join(node.project.capture_working_directory(), node_capture_data.capture_file_name)
|
||||
await node.start_capture(port_number, pcap_file_path, node_capture_data.data_link_type)
|
||||
return {"pcap_file_path": pcap_file_path}
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/stop_capture",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def stop_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int):
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
The adapter number on the switch is always 0.
|
||||
"""
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await node.stop_capture(port_number)
|
||||
|
||||
|
||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/pcap",
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def stream_pcap_file(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int):
|
||||
"""
|
||||
Stream the pcap capture file.
|
||||
The adapter number on the switch is always 0.
|
||||
"""
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
nio = node.get_nio(port_number)
|
||||
stream = dynamips_manager.stream_pcap_file(nio, node.project.id)
|
||||
return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap")
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2020 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
API endpoints for Frame Relay switch nodes.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from fastapi import APIRouter, Body, status
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from fastapi.responses import StreamingResponse
|
||||
from uuid import UUID
|
||||
|
||||
from gns3server.endpoints import schemas
|
||||
from gns3server.compute.dynamips import Dynamips
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/",
|
||||
response_model=schemas.FrameRelaySwitch,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Frame Relay switch node"}})
|
||||
async def create_frame_relay_switch(project_id: UUID, node_data: schemas.FrameRelaySwitchCreate):
|
||||
"""
|
||||
Create a new Frame Relay switch node.
|
||||
"""
|
||||
|
||||
# Use the Dynamips Frame Relay switch to simulate this node
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
node = await dynamips_manager.create_node(node_data.pop("name"),
|
||||
str(project_id),
|
||||
node_data.get("node_id"),
|
||||
node_type="frame_relay_switch",
|
||||
mappings=node_data.get("mappings"))
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.get("/{node_id}",
|
||||
response_model=schemas.FrameRelaySwitch,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
def get_frame_relay_switch(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Return a Frame Relay switch node.
|
||||
"""
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.post("/{node_id}/duplicate",
|
||||
response_model=schemas.FrameRelaySwitch,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def duplicate_frame_relay_switch(project_id: UUID, node_id: UUID, destination_node_id: UUID = Body(..., embed=True)):
|
||||
"""
|
||||
Duplicate a Frame Relay switch node.
|
||||
"""
|
||||
|
||||
new_node = await Dynamips.instance().duplicate_node(str(node_id), str(destination_node_id))
|
||||
return new_node.__json__()
|
||||
|
||||
|
||||
@router.put("/{node_id}",
|
||||
response_model=schemas.FrameRelaySwitch,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def update_frame_relay_switch(project_id: UUID, node_id: UUID, node_data: schemas.FrameRelaySwitchUpdate):
|
||||
"""
|
||||
Update an Frame Relay switch node.
|
||||
"""
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
if "name" in node_data and node.name != node_data["name"]:
|
||||
await node.set_name(node_data["name"])
|
||||
if "mappings" in node_data:
|
||||
node.mappings = node_data["mappings"]
|
||||
node.updated()
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.delete("/{node_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def delete_frame_relay_switch_node(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Delete a Frame Relay switch node.
|
||||
"""
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
await dynamips_manager.delete_node(str(node_id))
|
||||
|
||||
|
||||
@router.post("/{node_id}/start",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
def start_frame_relay_switch(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Start a Frame Relay switch node.
|
||||
This endpoint results in no action since Frame Relay switch nodes are always on.
|
||||
"""
|
||||
|
||||
Dynamips.instance().get_node(str(node_id), project_id=str(project_id))
|
||||
|
||||
|
||||
@router.post("/{node_id}/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
def stop_frame_relay_switch(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Stop a Frame Relay switch node.
|
||||
This endpoint results in no action since Frame Relay switch nodes are always on.
|
||||
"""
|
||||
|
||||
Dynamips.instance().get_node(str(node_id), project_id=str(project_id))
|
||||
|
||||
|
||||
@router.post("/{node_id}/suspend",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
def suspend_frame_relay_switch(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Suspend a Frame Relay switch node.
|
||||
This endpoint results in no action since Frame Relay switch nodes are always on.
|
||||
"""
|
||||
|
||||
Dynamips.instance().get_node(str(node_id), project_id=str(project_id))
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def create_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, nio_data: schemas.UDPNIO):
|
||||
"""
|
||||
Add a NIO (Network Input/Output) to the node.
|
||||
The adapter number on the switch is always 0.
|
||||
"""
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
nio = await dynamips_manager.create_nio(node, jsonable_encoder(nio_data, exclude_unset=True))
|
||||
await node.add_nio(nio, port_number)
|
||||
return nio.__json__()
|
||||
|
||||
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def delete_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int):
|
||||
"""
|
||||
Remove a NIO (Network Input/Output) from the node.
|
||||
The adapter number on the switch is always 0.
|
||||
"""
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
nio = await node.remove_nio(port_number)
|
||||
await nio.delete()
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/start_capture",
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def start_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, node_capture_data: schemas.NodeCapture):
|
||||
"""
|
||||
Start a packet capture on the node.
|
||||
The adapter number on the switch is always 0.
|
||||
"""
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
pcap_file_path = os.path.join(node.project.capture_working_directory(), node_capture_data.capture_file_name)
|
||||
await node.start_capture(port_number, pcap_file_path, node_capture_data.data_link_type)
|
||||
return {"pcap_file_path": pcap_file_path}
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/stop_capture",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def stop_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int):
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
The adapter number on the switch is always 0.
|
||||
"""
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await node.stop_capture(port_number)
|
||||
|
||||
|
||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/pcap",
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def stream_pcap_file(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int):
|
||||
"""
|
||||
Stream the pcap capture file.
|
||||
The adapter number on the hub is always 0.
|
||||
"""
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
nio = node.get_nio(port_number)
|
||||
stream = dynamips_manager.stream_pcap_file(nio, node.project.id)
|
||||
return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap")
|
||||
@@ -0,0 +1,157 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2020 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
API endpoints for images.
|
||||
"""
|
||||
|
||||
import os
|
||||
import urllib.parse
|
||||
|
||||
from fastapi import APIRouter, Request, status, HTTPException
|
||||
from fastapi.responses import FileResponse
|
||||
from typing import List
|
||||
|
||||
from gns3server.compute.docker import Docker
|
||||
from gns3server.compute.dynamips import Dynamips
|
||||
from gns3server.compute.iou import IOU
|
||||
from gns3server.compute.qemu import Qemu
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/docker/images")
|
||||
async def get_docker_images() -> List[str]:
|
||||
"""
|
||||
Get all Docker images.
|
||||
"""
|
||||
|
||||
docker_manager = Docker.instance()
|
||||
return await docker_manager.list_images()
|
||||
|
||||
|
||||
@router.get("/dynamips/images")
|
||||
async def get_dynamips_images() -> List[str]:
|
||||
"""
|
||||
Get all Dynamips images.
|
||||
"""
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
return await dynamips_manager.list_images()
|
||||
|
||||
|
||||
@router.post("/dynamips/images/{filename:path}",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def upload_dynamips_image(filename: str, request: Request):
|
||||
"""
|
||||
Upload a Dynamips IOS image.
|
||||
"""
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
await dynamips_manager.write_image(urllib.parse.unquote(filename), request.stream())
|
||||
|
||||
|
||||
@router.get("/dynamips/images/{filename:path}")
|
||||
async def download_dynamips_image(filename: str):
|
||||
"""
|
||||
Download a Dynamips IOS image.
|
||||
"""
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
filename = urllib.parse.unquote(filename)
|
||||
image_path = dynamips_manager.get_abs_image_path(filename)
|
||||
|
||||
if filename[0] == ".":
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
|
||||
|
||||
if not os.path.exists(image_path):
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
return FileResponse(image_path, media_type="application/octet-stream")
|
||||
|
||||
|
||||
@router.get("/iou/images")
|
||||
async def get_iou_images() -> List[str]:
|
||||
"""
|
||||
Get all IOU images.
|
||||
"""
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
return await iou_manager.list_images()
|
||||
|
||||
|
||||
@router.post("/iou/images/{filename:path}",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def upload_iou_image(filename: str, request: Request):
|
||||
"""
|
||||
Upload an IOU image.
|
||||
"""
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
await iou_manager.write_image(urllib.parse.unquote(filename), request.stream())
|
||||
|
||||
|
||||
@router.get("/iou/images/{filename:path}")
|
||||
async def download_iou_image(filename: str):
|
||||
"""
|
||||
Download an IOU image.
|
||||
"""
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
filename = urllib.parse.unquote(filename)
|
||||
image_path = iou_manager.get_abs_image_path(filename)
|
||||
|
||||
if filename[0] == ".":
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
|
||||
|
||||
if not os.path.exists(image_path):
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
return FileResponse(image_path, media_type="application/octet-stream")
|
||||
|
||||
|
||||
@router.get("/qemu/images")
|
||||
async def list_qemu_images() -> List[str]:
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
return await qemu_manager.list_images()
|
||||
|
||||
|
||||
@router.post("/qemu/images/{filename:path}",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def upload_qemu_image(filename: str, request: Request):
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
await qemu_manager.write_image(urllib.parse.unquote(filename), request.stream())
|
||||
|
||||
|
||||
@router.get("/qemu/images/{filename:path}")
|
||||
async def download_qemu_image(filename: str):
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
filename = urllib.parse.unquote(filename)
|
||||
|
||||
# Raise error if user try to escape
|
||||
if filename[0] == ".":
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
|
||||
|
||||
image_path = qemu_manager.get_abs_image_path(filename)
|
||||
|
||||
if not os.path.exists(image_path):
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
return FileResponse(image_path, media_type="application/octet-stream")
|
||||
@@ -0,0 +1,307 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2020 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
API endpoints for IOU nodes.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from fastapi import APIRouter, Body, status
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from fastapi.responses import StreamingResponse
|
||||
from typing import Union
|
||||
from uuid import UUID
|
||||
|
||||
from gns3server.endpoints import schemas
|
||||
from gns3server.compute.iou import IOU
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/",
|
||||
response_model=schemas.IOU,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create IOU node"}})
|
||||
async def create_iou_node(project_id: UUID, node_data: schemas.IOUCreate):
|
||||
"""
|
||||
Create a new IOU node.
|
||||
"""
|
||||
|
||||
iou = IOU.instance()
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
vm = await iou.create_node(node_data.pop("name"),
|
||||
str(project_id),
|
||||
node_data.get("node_id"),
|
||||
application_id=node_data.get("application_id"),
|
||||
path=node_data.get("path"),
|
||||
console=node_data.get("console"),
|
||||
console_type=node_data.get("console_type", "telnet"))
|
||||
|
||||
for name, value in node_data.items():
|
||||
if hasattr(vm, name) and getattr(vm, name) != value:
|
||||
if name == "application_id":
|
||||
continue # we must ignore this to avoid overwriting the application_id allocated by the controller
|
||||
if name == "startup_config_content" and (vm.startup_config_content and len(vm.startup_config_content) > 0):
|
||||
continue
|
||||
if name == "private_config_content" and (vm.private_config_content and len(vm.private_config_content) > 0):
|
||||
continue
|
||||
if node_data.get("use_default_iou_values") and (name == "ram" or name == "nvram"):
|
||||
continue
|
||||
setattr(vm, name, value)
|
||||
return vm.__json__()
|
||||
|
||||
|
||||
@router.get("/{node_id}",
|
||||
response_model=schemas.IOU,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
def get_iou_node(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Return an IOU node.
|
||||
"""
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
vm = iou_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
return vm.__json__()
|
||||
|
||||
|
||||
@router.put("/{node_id}",
|
||||
response_model=schemas.IOU,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def update_iou_node(project_id: UUID, node_id: UUID, node_data: schemas.IOUUpdate):
|
||||
"""
|
||||
Update an IOU node.
|
||||
"""
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
vm = iou_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
for name, value in node_data.items():
|
||||
if hasattr(vm, name) and getattr(vm, name) != value:
|
||||
if name == "application_id":
|
||||
continue # we must ignore this to avoid overwriting the application_id allocated by the IOU manager
|
||||
setattr(vm, name, value)
|
||||
|
||||
if vm.use_default_iou_values:
|
||||
# update the default IOU values in case the image or use_default_iou_values have changed
|
||||
# this is important to have the correct NVRAM amount in order to correctly push the configs to the NVRAM
|
||||
await vm.update_default_iou_values()
|
||||
vm.updated()
|
||||
return vm.__json__()
|
||||
|
||||
|
||||
@router.delete("/{node_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def delete_iou_node(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Delete an IOU node.
|
||||
"""
|
||||
|
||||
await IOU.instance().delete_node(str(node_id))
|
||||
|
||||
|
||||
@router.post("/{node_id}/duplicate",
|
||||
response_model=schemas.IOU,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def duplicate_iou_node(project_id: UUID, node_id: UUID, destination_node_id: UUID = Body(..., embed=True)):
|
||||
"""
|
||||
Duplicate an IOU node.
|
||||
"""
|
||||
|
||||
new_node = await IOU.instance().duplicate_node(str(node_id), str(destination_node_id))
|
||||
return new_node.__json__()
|
||||
|
||||
|
||||
@router.post("/{node_id}/start",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def start_iou_node(project_id: UUID, node_id: UUID, start_data: schemas.IOUStart):
|
||||
"""
|
||||
Start an IOU node.
|
||||
"""
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
vm = iou_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
start_data = jsonable_encoder(start_data, exclude_unset=True)
|
||||
for name, value in start_data.items():
|
||||
if hasattr(vm, name) and getattr(vm, name) != value:
|
||||
setattr(vm, name, value)
|
||||
|
||||
await vm.start()
|
||||
return vm.__json__()
|
||||
|
||||
|
||||
@router.post("/{node_id}/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def stop(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Stop an IOU node.
|
||||
"""
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
vm = iou_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await vm.stop()
|
||||
|
||||
|
||||
@router.post("/{node_id}/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
def suspend_iou_node(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Suspend an IOU node.
|
||||
Does nothing since IOU doesn't support being suspended.
|
||||
"""
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
iou_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
|
||||
|
||||
@router.post("/{node_id}/reload",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def reload_iou_node(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Reload an IOU node.
|
||||
"""
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
vm = iou_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await vm.reload()
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def create_nio(project_id: UUID,
|
||||
node_id: UUID,
|
||||
adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO]):
|
||||
"""
|
||||
Add a NIO (Network Input/Output) to the node.
|
||||
"""
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
vm = iou_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
nio = iou_manager.create_nio(jsonable_encoder(nio_data, exclude_unset=True))
|
||||
await vm.adapter_add_nio_binding(adapter_number, port_number, nio)
|
||||
return nio.__json__()
|
||||
|
||||
|
||||
@router.put("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def update_nio(project_id: UUID,
|
||||
node_id: UUID,
|
||||
adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO]):
|
||||
"""
|
||||
Update a NIO (Network Input/Output) on the node.
|
||||
"""
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
vm = iou_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
nio = vm.get_nio(adapter_number, port_number)
|
||||
if nio_data.filters:
|
||||
nio.filters = nio_data.filters
|
||||
await vm.adapter_update_nio_binding(adapter_number, port_number, nio)
|
||||
return nio.__json__()
|
||||
return nio.__json__()
|
||||
|
||||
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def delete_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int):
|
||||
"""
|
||||
Delete a NIO (Network Input/Output) from the node.
|
||||
"""
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
vm = iou_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await vm.adapter_remove_nio_binding(adapter_number, port_number)
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/start_capture",
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def start_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, node_capture_data: schemas.NodeCapture):
|
||||
"""
|
||||
Start a packet capture on the node.
|
||||
"""
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
vm = iou_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
pcap_file_path = os.path.join(vm.project.capture_working_directory(), node_capture_data.capture_file_name)
|
||||
await vm.start_capture(adapter_number, pcap_file_path)
|
||||
return {"pcap_file_path": str(pcap_file_path)}
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/stop_capture",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def stop_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int):
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
"""
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
vm = iou_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await vm.stop_capture(adapter_number, port_number)
|
||||
|
||||
|
||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/pcap",
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def stream_pcap_file(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int):
|
||||
"""
|
||||
Stream the pcap capture file.
|
||||
"""
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
vm = iou_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
nio = vm.get_nio(adapter_number, port_number)
|
||||
stream = iou_manager.stream_pcap_file(nio, vm.project.id)
|
||||
return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap")
|
||||
|
||||
|
||||
@router.post("/{node_id}/console/reset",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def reset_console(project_id: UUID, node_id: UUID):
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
vm = iou_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await vm.reset_console()
|
||||
|
||||
|
||||
# @Route.get(
|
||||
# r"/projects/{project_id}/iou/nodes/{node_id}/console/ws",
|
||||
# description="WebSocket for console",
|
||||
# parameters={
|
||||
# "project_id": "Project UUID",
|
||||
# "node_id": "Node UUID",
|
||||
# })
|
||||
# async def console_ws(request, response):
|
||||
#
|
||||
# iou_manager = IOU.instance()
|
||||
# vm = iou_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
# return await vm.start_websocket_console(request)
|
||||
@@ -0,0 +1,235 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2020 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
API endpoints for NAT nodes.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from fastapi import APIRouter, status
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from fastapi.responses import StreamingResponse
|
||||
from typing import Union
|
||||
from uuid import UUID
|
||||
|
||||
from gns3server.endpoints import schemas
|
||||
from gns3server.compute.builtin import Builtin
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/",
|
||||
response_model=schemas.NAT,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create NAT node"}})
|
||||
async def create_nat(project_id: UUID, node_data: schemas.NATCreate):
|
||||
"""
|
||||
Create a new NAT node.
|
||||
"""
|
||||
|
||||
builtin_manager = Builtin.instance()
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
node = await builtin_manager.create_node(node_data.pop("name"),
|
||||
str(project_id),
|
||||
node_data.get("node_id"),
|
||||
node_type="nat",
|
||||
ports=node_data.get("ports_mapping"))
|
||||
|
||||
node.usage = node_data.get("usage", "")
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.get("/{node_id}",
|
||||
response_model=schemas.NAT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
def get_nat(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Return a NAT node.
|
||||
"""
|
||||
|
||||
builtin_manager = Builtin.instance()
|
||||
node = builtin_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.put("/{node_id}",
|
||||
response_model=schemas.NAT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
def update_nat(project_id: UUID, node_id: UUID, node_data: schemas.NATUpdate):
|
||||
"""
|
||||
Update a NAT node.
|
||||
"""
|
||||
|
||||
builtin_manager = Builtin.instance()
|
||||
node = builtin_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
for name, value in node_data.items():
|
||||
if hasattr(node, name) and getattr(node, name) != value:
|
||||
setattr(node, name, value)
|
||||
node.updated()
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.delete("/{node_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def delete_nat(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Delete a cloud node.
|
||||
"""
|
||||
|
||||
builtin_manager = Builtin.instance()
|
||||
await builtin_manager.delete_node(str(node_id))
|
||||
|
||||
|
||||
@router.post("/{node_id}/start",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def start_nat(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Start a NAT node.
|
||||
"""
|
||||
|
||||
node = Builtin.instance().get_node(str(node_id), project_id=str(project_id))
|
||||
await node.start()
|
||||
|
||||
|
||||
@router.post("/{node_id}/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def stop_nat(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Stop a NAT node.
|
||||
This endpoint results in no action since cloud nodes cannot be stopped.
|
||||
"""
|
||||
|
||||
Builtin.instance().get_node(str(node_id), project_id=str(project_id))
|
||||
|
||||
|
||||
@router.post("/{node_id}/suspend",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def suspend_nat(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Suspend a NAT node.
|
||||
This endpoint results in no action since NAT nodes cannot be suspended.
|
||||
"""
|
||||
|
||||
Builtin.instance().get_node(str(node_id), project_id=str(project_id))
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def create_nio(project_id: UUID,
|
||||
node_id: UUID,
|
||||
adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO]):
|
||||
"""
|
||||
Add a NIO (Network Input/Output) to the node.
|
||||
The adapter number on the cloud is always 0.
|
||||
"""
|
||||
|
||||
builtin_manager = Builtin.instance()
|
||||
node = builtin_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
nio = builtin_manager.create_nio(jsonable_encoder(nio_data, exclude_unset=True))
|
||||
await node.add_nio(nio, port_number)
|
||||
return nio.__json__()
|
||||
|
||||
|
||||
@router.put("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def update_nio(project_id: UUID,
|
||||
node_id: UUID,
|
||||
adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO]):
|
||||
"""
|
||||
Update a NIO (Network Input/Output) to the node.
|
||||
The adapter number on the cloud is always 0.
|
||||
"""
|
||||
|
||||
builtin_manager = Builtin.instance()
|
||||
node = builtin_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
nio = node.get_nio(port_number)
|
||||
if nio_data.filters:
|
||||
nio.filters = nio_data.filters
|
||||
await node.update_nio(port_number, nio)
|
||||
return nio.__json__()
|
||||
|
||||
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def delete_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int):
|
||||
"""
|
||||
Remove a NIO (Network Input/Output) from the node.
|
||||
The adapter number on the cloud is always 0.
|
||||
"""
|
||||
|
||||
builtin_manager = Builtin.instance()
|
||||
node = builtin_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await node.remove_nio(port_number)
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/start_capture",
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def start_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, node_capture_data: schemas.NodeCapture):
|
||||
"""
|
||||
Start a packet capture on the node.
|
||||
The adapter number on the cloud is always 0.
|
||||
"""
|
||||
|
||||
builtin_manager = Builtin.instance()
|
||||
node = builtin_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
pcap_file_path = os.path.join(node.project.capture_working_directory(), node_capture_data.capture_file_name)
|
||||
await node.start_capture(port_number, pcap_file_path, node_capture_data.data_link_type)
|
||||
return {"pcap_file_path": pcap_file_path}
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/stop_capture",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def stop_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int):
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
The adapter number on the cloud is always 0.
|
||||
"""
|
||||
|
||||
builtin_manager = Builtin.instance()
|
||||
node = builtin_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await node.stop_capture(port_number)
|
||||
|
||||
|
||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/pcap",
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def stream_pcap_file(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int):
|
||||
"""
|
||||
Stream the pcap capture file.
|
||||
The adapter number on the cloud is always 0.
|
||||
"""
|
||||
|
||||
builtin_manager = Builtin.instance()
|
||||
node = builtin_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
nio = node.get_nio(port_number)
|
||||
stream = builtin_manager.stream_pcap_file(nio, node.project.id)
|
||||
return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap")
|
||||
@@ -0,0 +1,78 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2020 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
API endpoints for compute notifications.
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, WebSocket, WebSocketDisconnect
|
||||
from websockets.exceptions import WebSocketException
|
||||
from typing import List
|
||||
|
||||
from gns3server.compute.notification_manager import NotificationManager
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
class ConnectionManager:
|
||||
def __init__(self):
|
||||
self.active_connections: List[WebSocket] = []
|
||||
|
||||
async def connect(self, websocket: WebSocket):
|
||||
await websocket.accept()
|
||||
self.active_connections.append(websocket)
|
||||
|
||||
def disconnect(self, websocket: WebSocket):
|
||||
|
||||
self.active_connections.remove(websocket)
|
||||
|
||||
async def close_active_connections(self):
|
||||
|
||||
for websocket in self.active_connections:
|
||||
await websocket.close()
|
||||
|
||||
async def send_text(self, message: str, websocket: WebSocket):
|
||||
await websocket.send_text(message)
|
||||
|
||||
async def broadcast(self, message: str):
|
||||
for connection in self.active_connections:
|
||||
await connection.send_text(message)
|
||||
|
||||
|
||||
manager = ConnectionManager()
|
||||
|
||||
|
||||
@router.websocket("/notifications/ws")
|
||||
async def compute_notifications(websocket: WebSocket):
|
||||
|
||||
log.info("Client has disconnected from compute WebSocket")
|
||||
notifications = NotificationManager.instance()
|
||||
await manager.connect(websocket)
|
||||
try:
|
||||
log.info("New client has connected to compute WebSocket")
|
||||
with notifications.queue() as queue:
|
||||
while True:
|
||||
notification = await queue.get_json(5)
|
||||
await manager.send_text(notification, websocket)
|
||||
except (WebSocketException, WebSocketDisconnect) as e:
|
||||
log.info("Client has disconnected from compute WebSocket: {}".format(e))
|
||||
finally:
|
||||
await websocket.close()
|
||||
manager.disconnect(websocket)
|
||||
@@ -0,0 +1,244 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2015 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
API endpoints for projects.
|
||||
"""
|
||||
|
||||
import shutil
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Request, status
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from fastapi.responses import FileResponse
|
||||
from typing import List
|
||||
from uuid import UUID
|
||||
|
||||
from gns3server.endpoints import schemas
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
import aiohttp
|
||||
import os
|
||||
|
||||
from gns3server.compute.project_manager import ProjectManager
|
||||
|
||||
import logging
|
||||
log = logging.getLogger()
|
||||
|
||||
|
||||
# How many clients have subscribed to notifications
|
||||
_notifications_listening = {}
|
||||
|
||||
|
||||
@router.get("/projects", response_model=List[schemas.Project])
|
||||
def get_projects():
|
||||
"""
|
||||
Get all projects opened on the compute.
|
||||
"""
|
||||
|
||||
pm = ProjectManager.instance()
|
||||
return [p.__json__() for p in pm.projects]
|
||||
|
||||
|
||||
@router.post("/projects",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.Project)
|
||||
def create_project(project_data: schemas.ProjectCreate):
|
||||
"""
|
||||
Create a new project on the compute.
|
||||
"""
|
||||
|
||||
pm = ProjectManager.instance()
|
||||
project_data = jsonable_encoder(project_data, exclude_unset=True)
|
||||
project = pm.create_project(name=project_data.get("name"),
|
||||
path=project_data.get("path"),
|
||||
project_id=project_data.get("project_id"),
|
||||
variables=project_data.get("variables", None))
|
||||
return project.__json__()
|
||||
|
||||
|
||||
@router.put("/projects/{project_id}",
|
||||
response_model=schemas.Project)
|
||||
async def update_project(project_id: UUID, project_data: schemas.ProjectUpdate):
|
||||
"""
|
||||
Update project on the compute.
|
||||
"""
|
||||
|
||||
pm = ProjectManager.instance()
|
||||
project = pm.get_project(str(project_id))
|
||||
await project.update(variables=project_data.variables)
|
||||
return project.__json__()
|
||||
|
||||
|
||||
@router.get("/projects/{project_id}",
|
||||
response_model=schemas.Project)
|
||||
def get_project(project_id: UUID):
|
||||
"""
|
||||
Return a project from the compute.
|
||||
"""
|
||||
|
||||
pm = ProjectManager.instance()
|
||||
project = pm.get_project(str(project_id))
|
||||
return project.__json__()
|
||||
|
||||
|
||||
@router.post("/projects/{project_id}/close",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def close_project(project_id: UUID):
|
||||
"""
|
||||
Close a project on the compute.
|
||||
"""
|
||||
|
||||
pm = ProjectManager.instance()
|
||||
project = pm.get_project(str(project_id))
|
||||
if _notifications_listening.setdefault(project.id, 0) <= 1:
|
||||
await project.close()
|
||||
pm.remove_project(project.id)
|
||||
try:
|
||||
del _notifications_listening[project.id]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
log.warning("Skip project closing, another client is listening for project notifications")
|
||||
|
||||
|
||||
@router.delete("/projects/{project_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_project(project_id: UUID):
|
||||
"""
|
||||
Delete project from the compute.
|
||||
"""
|
||||
|
||||
pm = ProjectManager.instance()
|
||||
project = pm.get_project(str(project_id))
|
||||
await project.delete()
|
||||
pm.remove_project(project.id)
|
||||
|
||||
# @Route.get(
|
||||
# r"/projects/{project_id}/notifications",
|
||||
# description="Receive notifications about the project",
|
||||
# parameters={
|
||||
# "project_id": "Project UUID",
|
||||
# },
|
||||
# status_codes={
|
||||
# 200: "End of stream",
|
||||
# 404: "The project doesn't exist"
|
||||
# })
|
||||
# async def notification(request, response):
|
||||
#
|
||||
# pm = ProjectManager.instance()
|
||||
# project = pm.get_project(request.match_info["project_id"])
|
||||
#
|
||||
# response.content_type = "application/json"
|
||||
# response.set_status(200)
|
||||
# response.enable_chunked_encoding()
|
||||
#
|
||||
# response.start(request)
|
||||
# queue = project.get_listen_queue()
|
||||
# ProjectHandler._notifications_listening.setdefault(project.id, 0)
|
||||
# ProjectHandler._notifications_listening[project.id] += 1
|
||||
# await response.write("{}\n".format(json.dumps(ProjectHandler._getPingMessage())).encode("utf-8"))
|
||||
# while True:
|
||||
# try:
|
||||
# (action, msg) = await asyncio.wait_for(queue.get(), 5)
|
||||
# if hasattr(msg, "__json__"):
|
||||
# msg = json.dumps({"action": action, "event": msg.__json__()}, sort_keys=True)
|
||||
# else:
|
||||
# msg = json.dumps({"action": action, "event": msg}, sort_keys=True)
|
||||
# log.debug("Send notification: %s", msg)
|
||||
# await response.write(("{}\n".format(msg)).encode("utf-8"))
|
||||
# except asyncio.TimeoutError:
|
||||
# await response.write("{}\n".format(json.dumps(ProjectHandler._getPingMessage())).encode("utf-8"))
|
||||
# project.stop_listen_queue(queue)
|
||||
# if project.id in ProjectHandler._notifications_listening:
|
||||
# ProjectHandler._notifications_listening[project.id] -= 1
|
||||
|
||||
# def _getPingMessage(cls):
|
||||
# """
|
||||
# Ping messages are regularly sent to the client to
|
||||
# keep the connection open. We send with it some information about server load.
|
||||
#
|
||||
# :returns: hash
|
||||
# """
|
||||
# stats = {}
|
||||
# # Non blocking call in order to get cpu usage. First call will return 0
|
||||
# stats["cpu_usage_percent"] = CpuPercent.get(interval=None)
|
||||
# stats["memory_usage_percent"] = psutil.virtual_memory().percent
|
||||
# stats["disk_usage_percent"] = psutil.disk_usage(get_default_project_directory()).percent
|
||||
# return {"action": "ping", "event": stats}
|
||||
|
||||
|
||||
@router.get("/projects/{project_id}/files",
|
||||
response_model=List[schemas.ProjectFile])
|
||||
async def get_project_files(project_id: UUID):
|
||||
"""
|
||||
Return files belonging to a project.
|
||||
"""
|
||||
|
||||
pm = ProjectManager.instance()
|
||||
project = pm.get_project(str(project_id))
|
||||
return await project.list_files()
|
||||
|
||||
|
||||
@router.get("/projects/{project_id}/files/{file_path:path}")
|
||||
async def get_file(project_id: UUID, file_path: str):
|
||||
"""
|
||||
Get a file from a project.
|
||||
"""
|
||||
|
||||
pm = ProjectManager.instance()
|
||||
project = pm.get_project(str(project_id))
|
||||
path = os.path.normpath(file_path)
|
||||
|
||||
# Raise error if user try to escape
|
||||
if path[0] == ".":
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
|
||||
|
||||
path = os.path.join(project.path, path)
|
||||
if not os.path.exists(path):
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
return FileResponse(path, media_type="application/octet-stream")
|
||||
|
||||
|
||||
@router.post("/projects/{project_id}/files/{file_path:path}",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def write_file(project_id: UUID, file_path: str, request: Request):
|
||||
|
||||
pm = ProjectManager.instance()
|
||||
project = pm.get_project(str(project_id))
|
||||
path = os.path.normpath(file_path)
|
||||
|
||||
# Raise error if user try to escape
|
||||
if path[0] == ".":
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
|
||||
|
||||
path = os.path.join(project.path, path)
|
||||
try:
|
||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||
|
||||
try:
|
||||
with open(path, "wb+") as f:
|
||||
async for chunk in request.stream():
|
||||
f.write(chunk)
|
||||
except (UnicodeEncodeError, OSError) as e:
|
||||
pass # FIXME
|
||||
|
||||
except FileNotFoundError:
|
||||
raise aiohttp.web.HTTPNotFound()
|
||||
except PermissionError:
|
||||
raise aiohttp.web.HTTPForbidden()
|
||||
@@ -0,0 +1,332 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2020 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
API endpoints for Qemu nodes.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from fastapi import APIRouter, Body, status
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from fastapi.responses import StreamingResponse
|
||||
from uuid import UUID
|
||||
|
||||
from gns3server.endpoints import schemas
|
||||
from gns3server.compute.project_manager import ProjectManager
|
||||
from gns3server.compute.compute_error import ComputeError
|
||||
from gns3server.compute.qemu import Qemu
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/",
|
||||
response_model=schemas.Qemu,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Qemu node"}})
|
||||
async def create_qemu_node(project_id: UUID, node_data: schemas.QemuCreate):
|
||||
"""
|
||||
Create a new Qemu node.
|
||||
"""
|
||||
|
||||
qemu = Qemu.instance()
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
vm = await qemu.create_node(node_data.pop("name"),
|
||||
str(project_id),
|
||||
node_data.pop("node_id", None),
|
||||
linked_clone=node_data.get("linked_clone", True),
|
||||
qemu_path=node_data.pop("qemu_path", None),
|
||||
console=node_data.pop("console", None),
|
||||
console_type=node_data.pop("console_type", "telnet"),
|
||||
aux=node_data.get("aux"),
|
||||
aux_type=node_data.pop("aux_type", "none"),
|
||||
platform=node_data.pop("platform", None))
|
||||
|
||||
for name, value in node_data.items():
|
||||
if hasattr(vm, name) and getattr(vm, name) != value:
|
||||
setattr(vm, name, value)
|
||||
|
||||
return vm.__json__()
|
||||
|
||||
|
||||
@router.get("/{node_id}",
|
||||
response_model=schemas.Qemu,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
def get_qemu_node(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Return a Qemu node.
|
||||
"""
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
vm = qemu_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
return vm.__json__()
|
||||
|
||||
|
||||
@router.put("/{node_id}",
|
||||
response_model=schemas.Qemu,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def update_qemu_node(project_id: UUID, node_id: UUID, node_data: schemas.QemuUpdate):
|
||||
"""
|
||||
Update a Qemu node.
|
||||
"""
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
vm = qemu_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
# update the console first to avoid issue if updating console type
|
||||
vm.console = node_data.pop("console", vm.console)
|
||||
for name, value in node_data.items():
|
||||
if hasattr(vm, name) and getattr(vm, name) != value:
|
||||
setattr(vm, name, value)
|
||||
if name == "cdrom_image":
|
||||
# let the guest know about the new cdrom image
|
||||
await vm.update_cdrom_image()
|
||||
|
||||
vm.updated()
|
||||
return vm.__json__()
|
||||
|
||||
|
||||
@router.delete("/{node_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def delete_qemu_node(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Delete a Qemu node.
|
||||
"""
|
||||
|
||||
await Qemu.instance().delete_node(str(node_id))
|
||||
|
||||
|
||||
@router.post("/{node_id}/duplicate",
|
||||
response_model=schemas.Qemu,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def duplicate_qemu_node(project_id: UUID, node_id: UUID, destination_node_id: UUID = Body(..., embed=True)):
|
||||
"""
|
||||
Duplicate a Qemu node.
|
||||
"""
|
||||
|
||||
new_node = await Qemu.instance().duplicate_node(str(node_id), str(destination_node_id))
|
||||
return new_node.__json__()
|
||||
|
||||
|
||||
@router.post("/{node_id}/resize_disk",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def resize_qemu_node_disk(project_id: UUID, node_id: UUID, node_data: schemas.QemuDiskResize):
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
vm = qemu_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await vm.resize_disk(node_data.drive_name, node_data.extend)
|
||||
|
||||
|
||||
@router.post("/{node_id}/start",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def start_qemu_node(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Start a Qemu node.
|
||||
"""
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
vm = qemu_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
hardware_accel = qemu_manager.config.get_section_config("Qemu").getboolean("enable_hardware_acceleration", True)
|
||||
if sys.platform.startswith("linux"):
|
||||
# the enable_kvm option was used before version 2.0 and has priority
|
||||
enable_kvm = qemu_manager.config.get_section_config("Qemu").getboolean("enable_kvm")
|
||||
if enable_kvm is not None:
|
||||
hardware_accel = enable_kvm
|
||||
if hardware_accel and "-no-kvm" not in vm.options and "-no-hax" not in vm.options:
|
||||
pm = ProjectManager.instance()
|
||||
if pm.check_hardware_virtualization(vm) is False:
|
||||
pass #FIXME: check this
|
||||
#raise ComputeError("Cannot start VM with hardware acceleration (KVM/HAX) enabled because hardware virtualization (VT-x/AMD-V) is already used by another software like VMware or VirtualBox")
|
||||
await vm.start()
|
||||
|
||||
|
||||
@router.post("/{node_id}/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def stop_qemu_node(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Stop a Qemu node.
|
||||
"""
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
vm = qemu_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await vm.stop()
|
||||
|
||||
|
||||
@router.post("/{node_id}/reload",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def reload_qemu_node(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Reload a Qemu node.
|
||||
"""
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
vm = qemu_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await vm.reload()
|
||||
|
||||
|
||||
@router.post("/{node_id}/suspend",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def suspend_qemu_node(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Suspend a Qemu node.
|
||||
"""
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
vm = qemu_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await vm.suspend()
|
||||
|
||||
|
||||
@router.post("/{node_id}/resume",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def resume_qemu_node(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Resume a Qemu node.
|
||||
"""
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
vm = qemu_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await vm.resume()
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def create_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, nio_data: schemas.UDPNIO):
|
||||
"""
|
||||
Add a NIO (Network Input/Output) to the node.
|
||||
The port number on the Qemu node is always 0.
|
||||
"""
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
vm = qemu_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
nio = qemu_manager.create_nio(jsonable_encoder(nio_data, exclude_unset=True))
|
||||
await vm.adapter_add_nio_binding(adapter_number, nio)
|
||||
return nio.__json__()
|
||||
|
||||
|
||||
@router.put("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def update_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, nio_data: schemas.UDPNIO):
|
||||
"""
|
||||
Update a NIO (Network Input/Output) on the node.
|
||||
The port number on the Qemu node is always 0.
|
||||
"""
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
vm = qemu_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
nio = vm.get_nio(adapter_number)
|
||||
if nio_data.filters:
|
||||
nio.filters = nio_data.filters
|
||||
if nio_data.suspend:
|
||||
nio.suspend = nio_data.suspend
|
||||
await vm.adapter_update_nio_binding(adapter_number, nio)
|
||||
return nio.__json__()
|
||||
|
||||
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def delete_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int):
|
||||
"""
|
||||
Delete a NIO (Network Input/Output) from the node.
|
||||
The port number on the Qemu node is always 0.
|
||||
"""
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
vm = qemu_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await vm.adapter_remove_nio_binding(adapter_number)
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/start_capture",
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def start_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, node_capture_data: schemas.NodeCapture):
|
||||
"""
|
||||
Start a packet capture on the node.
|
||||
The port number on the Qemu node is always 0.
|
||||
"""
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
vm = qemu_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
pcap_file_path = os.path.join(vm.project.capture_working_directory(), node_capture_data.capture_file_name)
|
||||
await vm.start_capture(adapter_number, pcap_file_path)
|
||||
return {"pcap_file_path": str(pcap_file_path)}
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/stop_capture",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def stop_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int):
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
The port number on the Qemu node is always 0.
|
||||
"""
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
vm = qemu_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await vm.stop_capture(adapter_number)
|
||||
|
||||
|
||||
@router.post("/{node_id}/console/reset",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def reset_console(project_id: UUID, node_id: UUID):
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
vm = qemu_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await vm.reset_console()
|
||||
|
||||
|
||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/pcap",
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def stream_pcap_file(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int):
|
||||
"""
|
||||
Stream the pcap capture file.
|
||||
The port number on the Qemu node is always 0.
|
||||
"""
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
vm = qemu_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
nio = vm.get_nio(adapter_number)
|
||||
stream = qemu_manager.stream_pcap_file(nio, vm.project.id)
|
||||
return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap")
|
||||
|
||||
|
||||
# @Route.get(
|
||||
# r"/projects/{project_id}/qemu/nodes/{node_id}/console/ws",
|
||||
# description="WebSocket for console",
|
||||
# parameters={
|
||||
# "project_id": "Project UUID",
|
||||
# "node_id": "Node UUID",
|
||||
# })
|
||||
# async def console_ws(request, response):
|
||||
#
|
||||
# qemu_manager = Qemu.instance()
|
||||
# vm = qemu_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
# return await vm.start_websocket_console(request)
|
||||
|
||||
@@ -0,0 +1,330 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2020 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
API endpoints for VirtualBox nodes.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from fastapi import APIRouter, status
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from fastapi.responses import StreamingResponse
|
||||
from uuid import UUID
|
||||
|
||||
from gns3server.endpoints import schemas
|
||||
from gns3server.compute.virtualbox import VirtualBox
|
||||
from gns3server.compute.virtualbox.virtualbox_error import VirtualBoxError
|
||||
from gns3server.compute.project_manager import ProjectManager
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/",
|
||||
response_model=schemas.VirtualBox,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create VirtualBox node"}})
|
||||
async def create_virtualbox_node(project_id: UUID, node_data: schemas.VirtualBoxCreate):
|
||||
"""
|
||||
Create a new VirtualBox node.
|
||||
"""
|
||||
|
||||
vbox_manager = VirtualBox.instance()
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
vm = await vbox_manager.create_node(node_data.pop("name"),
|
||||
str(project_id),
|
||||
node_data.get("node_id"),
|
||||
node_data.pop("vmname"),
|
||||
linked_clone=node_data.pop("linked_clone", False),
|
||||
console=node_data.get("console", None),
|
||||
console_type=node_data.get("console_type", "telnet"),
|
||||
adapters=node_data.get("adapters", 0))
|
||||
|
||||
if "ram" in node_data:
|
||||
ram = node_data.pop("ram")
|
||||
if ram != vm.ram:
|
||||
await vm.set_ram(ram)
|
||||
|
||||
for name, value in node_data.items():
|
||||
if name != "node_id":
|
||||
if hasattr(vm, name) and getattr(vm, name) != value:
|
||||
setattr(vm, name, value)
|
||||
|
||||
return vm.__json__()
|
||||
|
||||
|
||||
@router.get("/{node_id}",
|
||||
response_model=schemas.VirtualBox,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
def get_virtualbox_node(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Return a VirtualBox node.
|
||||
"""
|
||||
|
||||
vbox_manager = VirtualBox.instance()
|
||||
vm = vbox_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
return vm.__json__()
|
||||
|
||||
|
||||
@router.put("/{node_id}",
|
||||
response_model=schemas.VirtualBox,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def update_virtualbox_node(project_id: UUID, node_id: UUID, node_data: schemas.VirtualBoxUpdate):
|
||||
"""
|
||||
Update a VirtualBox node.
|
||||
"""
|
||||
|
||||
vbox_manager = VirtualBox.instance()
|
||||
vm = vbox_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
|
||||
if "name" in node_data:
|
||||
name = node_data.pop("name")
|
||||
vmname = node_data.pop("vmname", None)
|
||||
if name != vm.name:
|
||||
oldname = vm.name
|
||||
vm.name = name
|
||||
if vm.linked_clone:
|
||||
try:
|
||||
await vm.set_vmname(vm.name)
|
||||
except VirtualBoxError as e: # In case of error we rollback (we can't change the name when running)
|
||||
vm.name = oldname
|
||||
vm.updated()
|
||||
raise e
|
||||
|
||||
if "adapters" in node_data:
|
||||
adapters = node_data.pop("adapters")
|
||||
if adapters != vm.adapters:
|
||||
await vm.set_adapters(adapters)
|
||||
|
||||
if "ram" in node_data:
|
||||
ram = node_data.pop("ram")
|
||||
if ram != vm.ram:
|
||||
await vm.set_ram(ram)
|
||||
|
||||
# update the console first to avoid issue if updating console type
|
||||
vm.console = node_data.pop("console", vm.console)
|
||||
|
||||
for name, value in node_data.items():
|
||||
if hasattr(vm, name) and getattr(vm, name) != value:
|
||||
setattr(vm, name, value)
|
||||
|
||||
vm.updated()
|
||||
return vm.__json__()
|
||||
|
||||
|
||||
@router.delete("/{node_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def delete_virtualbox_node(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Delete a VirtualBox node.
|
||||
"""
|
||||
|
||||
# check the project_id exists
|
||||
ProjectManager.instance().get_project(str(project_id))
|
||||
await VirtualBox.instance().delete_node(str(node_id))
|
||||
|
||||
|
||||
@router.post("/{node_id}/start",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def start_virtualbox_node(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Start a VirtualBox node.
|
||||
"""
|
||||
|
||||
vbox_manager = VirtualBox.instance()
|
||||
vm = vbox_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
if await vm.check_hw_virtualization():
|
||||
pm = ProjectManager.instance()
|
||||
if pm.check_hardware_virtualization(vm) is False:
|
||||
pass # FIXME: check this
|
||||
#raise ComputeError("Cannot start VM with hardware acceleration (KVM/HAX) enabled because hardware virtualization (VT-x/AMD-V) is already used by another software like VMware or VirtualBox")
|
||||
await vm.start()
|
||||
|
||||
|
||||
@router.post("/{node_id}/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def stop_virtualbox_node(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Stop a VirtualBox node.
|
||||
"""
|
||||
|
||||
vbox_manager = VirtualBox.instance()
|
||||
vm = vbox_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await vm.stop()
|
||||
|
||||
|
||||
@router.post("/{node_id}/suspend",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def suspend_virtualbox_node(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Suspend a VirtualBox node.
|
||||
"""
|
||||
|
||||
vbox_manager = VirtualBox.instance()
|
||||
vm = vbox_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await vm.suspend()
|
||||
|
||||
|
||||
@router.post("/{node_id}/resume",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def resume_virtualbox_node(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Resume a VirtualBox node.
|
||||
"""
|
||||
|
||||
vbox_manager = VirtualBox.instance()
|
||||
vm = vbox_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await vm.resume()
|
||||
|
||||
|
||||
@router.post("/{node_id}/reload",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def reload_virtualbox_node(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Reload a VirtualBox node.
|
||||
"""
|
||||
|
||||
vbox_manager = VirtualBox.instance()
|
||||
vm = vbox_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await vm.reload()
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def create_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, nio_data: schemas.UDPNIO):
|
||||
"""
|
||||
Add a NIO (Network Input/Output) to the node.
|
||||
The port number on the VirtualBox node is always 0.
|
||||
"""
|
||||
|
||||
vbox_manager = VirtualBox.instance()
|
||||
vm = vbox_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
nio = vbox_manager.create_nio(jsonable_encoder(nio_data, exclude_unset=True))
|
||||
await vm.adapter_add_nio_binding(adapter_number, nio)
|
||||
return nio.__json__()
|
||||
|
||||
|
||||
@router.put("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def update_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, nio_data: schemas.UDPNIO):
|
||||
"""
|
||||
Update a NIO (Network Input/Output) on the node.
|
||||
The port number on the VirtualBox node is always 0.
|
||||
"""
|
||||
|
||||
virtualbox_manager = VirtualBox.instance()
|
||||
vm = virtualbox_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
nio = vm.get_nio(adapter_number)
|
||||
if nio_data.filters:
|
||||
nio.filters = nio_data.filters
|
||||
if nio_data.suspend:
|
||||
nio.suspend = nio_data.suspend
|
||||
await vm.adapter_update_nio_binding(adapter_number, nio)
|
||||
return nio.__json__()
|
||||
|
||||
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def delete_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int):
|
||||
"""
|
||||
Delete a NIO (Network Input/Output) from the node.
|
||||
The port number on the VirtualBox node is always 0.
|
||||
"""
|
||||
|
||||
vbox_manager = VirtualBox.instance()
|
||||
vm = vbox_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await vm.adapter_remove_nio_binding(adapter_number)
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/start_capture",
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def start_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, node_capture_data: schemas.NodeCapture):
|
||||
"""
|
||||
Start a packet capture on the node.
|
||||
The port number on the VirtualBox node is always 0.
|
||||
"""
|
||||
|
||||
vbox_manager = VirtualBox.instance()
|
||||
vm = vbox_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
pcap_file_path = os.path.join(vm.project.capture_working_directory(), node_capture_data.capture_file_name)
|
||||
await vm.start_capture(adapter_number, pcap_file_path)
|
||||
return {"pcap_file_path": str(pcap_file_path)}
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/stop_capture",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def stop_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int):
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
The port number on the VirtualBox node is always 0.
|
||||
"""
|
||||
|
||||
vbox_manager = VirtualBox.instance()
|
||||
vm = vbox_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await vm.stop_capture(adapter_number)
|
||||
|
||||
|
||||
@router.post("/{node_id}/console/reset",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def reset_console(project_id: UUID, node_id: UUID):
|
||||
|
||||
virtualbox_manager = VirtualBox.instance()
|
||||
vm = virtualbox_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await vm.reset_console()
|
||||
|
||||
|
||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/pcap",
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def stream_pcap_file(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int):
|
||||
"""
|
||||
Stream the pcap capture file.
|
||||
The port number on the VirtualBox node is always 0.
|
||||
"""
|
||||
|
||||
virtualbox_manager = VirtualBox.instance()
|
||||
vm = virtualbox_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
nio = vm.get_nio(adapter_number)
|
||||
stream = virtualbox_manager.stream_pcap_file(nio, vm.project.id)
|
||||
return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap")
|
||||
|
||||
|
||||
# @Route.get(
|
||||
# r"/projects/{project_id}/virtualbox/nodes/{node_id}/console/ws",
|
||||
# description="WebSocket for console",
|
||||
# parameters={
|
||||
# "project_id": "Project UUID",
|
||||
# "node_id": "Node UUID",
|
||||
# })
|
||||
# async def console_ws(request, response):
|
||||
#
|
||||
# virtualbox_manager = VirtualBox.instance()
|
||||
# vm = virtualbox_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
# return await vm.start_websocket_console(request)
|
||||
@@ -0,0 +1,312 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2020 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
API endpoints for VMware nodes.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from fastapi import APIRouter, status
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from fastapi.responses import StreamingResponse
|
||||
from uuid import UUID
|
||||
|
||||
from gns3server.endpoints import schemas
|
||||
from gns3server.compute.vmware import VMware
|
||||
from gns3server.compute.project_manager import ProjectManager
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/",
|
||||
response_model=schemas.VMware,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create VMware node"}})
|
||||
async def create_vmware_node(project_id: UUID, node_data: schemas.VMwareCreate):
|
||||
"""
|
||||
Create a new VMware node.
|
||||
"""
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
vm = await vmware_manager.create_node(node_data.pop("name"),
|
||||
str(project_id),
|
||||
node_data.get("node_id"),
|
||||
node_data.pop("vmx_path"),
|
||||
linked_clone=node_data.pop("linked_clone"),
|
||||
console=node_data.get("console", None),
|
||||
console_type=node_data.get("console_type", "telnet"))
|
||||
|
||||
for name, value in node_data.items():
|
||||
if name != "node_id":
|
||||
if hasattr(vm, name) and getattr(vm, name) != value:
|
||||
setattr(vm, name, value)
|
||||
|
||||
return vm.__json__()
|
||||
|
||||
|
||||
@router.get("/{node_id}",
|
||||
response_model=schemas.VMware,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
def get_vmware_node(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Return a VMware node.
|
||||
"""
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
vm = vmware_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
return vm.__json__()
|
||||
|
||||
|
||||
@router.put("/{node_id}",
|
||||
response_model=schemas.VMware,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
def update_vmware_node(project_id: UUID, node_id: UUID, node_data: schemas.VMwareUpdate):
|
||||
"""
|
||||
Update a VMware node.
|
||||
"""
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
vm = vmware_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
# update the console first to avoid issue if updating console type
|
||||
vm.console = node_data.pop("console", vm.console)
|
||||
for name, value in node_data.items():
|
||||
if hasattr(vm, name) and getattr(vm, name) != value:
|
||||
setattr(vm, name, value)
|
||||
|
||||
vm.updated()
|
||||
return vm.__json__()
|
||||
|
||||
|
||||
@router.delete("/{node_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def delete_vmware_node(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Delete a VMware node.
|
||||
"""
|
||||
|
||||
# check the project_id exists
|
||||
ProjectManager.instance().get_project(str(project_id))
|
||||
await VMware.instance().delete_node(str(node_id))
|
||||
|
||||
|
||||
@router.post("/{node_id}/start",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def start_vmware_node(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Start a VMware node.
|
||||
"""
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
vm = vmware_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
if vm.check_hw_virtualization():
|
||||
pm = ProjectManager.instance()
|
||||
if pm.check_hardware_virtualization(vm) is False:
|
||||
pass # FIXME: check this
|
||||
#raise ComputeError("Cannot start VM with hardware acceleration (KVM/HAX) enabled because hardware virtualization (VT-x/AMD-V) is already used by another software like VMware or VirtualBox")
|
||||
await vm.start()
|
||||
|
||||
|
||||
@router.post("/{node_id}/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def stop_vmware_node(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Stop a VMware node.
|
||||
"""
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
vm = vmware_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await vm.stop()
|
||||
|
||||
|
||||
@router.post("/{node_id}/suspend",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def suspend_vmware_node(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Suspend a VMware node.
|
||||
"""
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
vm = vmware_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await vm.suspend()
|
||||
|
||||
|
||||
@router.post("/{node_id}/resume",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def resume_vmware_node(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Resume a VMware node.
|
||||
"""
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
vm = vmware_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await vm.resume()
|
||||
|
||||
|
||||
@router.post("/{node_id}/reload",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def reload_vmware_node(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Reload a VMware node.
|
||||
"""
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
vm = vmware_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await vm.reload()
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def create_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, nio_data: schemas.UDPNIO):
|
||||
"""
|
||||
Add a NIO (Network Input/Output) to the node.
|
||||
The port number on the VMware node is always 0.
|
||||
"""
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
vm = vmware_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
nio = vmware_manager.create_nio(jsonable_encoder(nio_data, exclude_unset=True))
|
||||
await vm.adapter_add_nio_binding(adapter_number, nio)
|
||||
return nio.__json__()
|
||||
|
||||
|
||||
@router.put("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def update_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, nio_data: schemas.UDPNIO):
|
||||
"""
|
||||
Update a NIO (Network Input/Output) on the node.
|
||||
The port number on the VMware node is always 0.
|
||||
"""
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
vm = vmware_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
nio = vm.get_nio(adapter_number)
|
||||
if nio_data.filters:
|
||||
nio.filters = nio_data.filters
|
||||
await vm.adapter_update_nio_binding(adapter_number, nio)
|
||||
return nio.__json__()
|
||||
|
||||
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def delete_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int):
|
||||
"""
|
||||
Delete a NIO (Network Input/Output) from the node.
|
||||
The port number on the VMware node is always 0.
|
||||
"""
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
vm = vmware_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await vm.adapter_remove_nio_binding(adapter_number)
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/start_capture",
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def start_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, node_capture_data: schemas.NodeCapture):
|
||||
"""
|
||||
Start a packet capture on the node.
|
||||
The port number on the VMware node is always 0.
|
||||
"""
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
vm = vmware_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
pcap_file_path = os.path.join(vm.project.capture_working_directory(), node_capture_data.capture_file_name)
|
||||
await vm.start_capture(adapter_number, pcap_file_path)
|
||||
return {"pcap_file_path": pcap_file_path}
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/stop_capture",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def stop_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int):
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
The port number on the VMware node is always 0.
|
||||
"""
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
vm = vmware_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await vm.stop_capture(adapter_number)
|
||||
|
||||
|
||||
@router.post("/{node_id}/console/reset",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def reset_console(project_id: UUID, node_id: UUID):
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
vm = vmware_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await vm.reset_console()
|
||||
|
||||
|
||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/pcap",
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def stream_pcap_file(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int):
|
||||
"""
|
||||
Stream the pcap capture file.
|
||||
The port number on the VMware node is always 0.
|
||||
"""
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
vm = vmware_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
nio = vm.get_nio(adapter_number)
|
||||
stream = vmware_manager.stream_pcap_file(nio, vm.project.id)
|
||||
return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap")
|
||||
|
||||
|
||||
@router.post("/{node_id}/interfaces/vmnet",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
def allocate_vmnet(project_id: UUID, node_id: UUID) -> dict:
|
||||
"""
|
||||
Allocate a VMware VMnet interface on the server.
|
||||
"""
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
vm = vmware_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
vmware_manager.refresh_vmnet_list(ubridge=False)
|
||||
vmnet = vmware_manager.allocate_vmnet()
|
||||
vm.vmnets.append(vmnet)
|
||||
return {"vmnet": vmnet}
|
||||
|
||||
|
||||
# @Route.get(
|
||||
# r"/projects/{project_id}/vmware/nodes/{node_id}/console/ws",
|
||||
# description="WebSocket for console",
|
||||
# parameters={
|
||||
# "project_id": "Project UUID",
|
||||
# "node_id": "Node UUID",
|
||||
# })
|
||||
# async def console_ws(request, response):
|
||||
#
|
||||
# vmware_manager = VMware.instance()
|
||||
# vm = vmware_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
# return await vm.start_websocket_console(request)
|
||||
#
|
||||
@@ -0,0 +1,283 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2020 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
API endpoints for VPCS nodes.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from fastapi import APIRouter, Body, status
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from fastapi.responses import StreamingResponse
|
||||
from uuid import UUID
|
||||
|
||||
from gns3server.endpoints import schemas
|
||||
from gns3server.compute.vpcs import VPCS
|
||||
from gns3server.compute.project_manager import ProjectManager
|
||||
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/",
|
||||
response_model=schemas.VPCS,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create VMware node"}})
|
||||
async def create_vpcs_node(project_id: UUID, node_data: schemas.VPCSCreate):
|
||||
"""
|
||||
Create a new VPCS node.
|
||||
"""
|
||||
|
||||
vpcs = VPCS.instance()
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
vm = await vpcs.create_node(node_data["name"],
|
||||
str(project_id),
|
||||
node_data.get("node_id"),
|
||||
console=node_data.get("console"),
|
||||
console_type=node_data.get("console_type", "telnet"),
|
||||
startup_script=node_data.get("startup_script"))
|
||||
|
||||
return vm.__json__()
|
||||
|
||||
|
||||
@router.get("/{node_id}",
|
||||
response_model=schemas.VPCS,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
def get_vpcs_node(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Return a VPCS node.
|
||||
"""
|
||||
|
||||
vpcs_manager = VPCS.instance()
|
||||
vm = vpcs_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
return vm.__json__()
|
||||
|
||||
|
||||
@router.put("/{node_id}",
|
||||
response_model=schemas.VPCS,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
def update_vpcs_node(project_id: UUID, node_id: UUID, node_data: schemas.VPCSUpdate):
|
||||
"""
|
||||
Update a VPCS node.
|
||||
"""
|
||||
|
||||
vpcs_manager = VPCS.instance()
|
||||
vm = vpcs_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
vm.name = node_data.get("name", vm.name)
|
||||
vm.console = node_data.get("console", vm.console)
|
||||
vm.console_type = node_data.get("console_type", vm.console_type)
|
||||
vm.updated()
|
||||
return vm.__json__()
|
||||
|
||||
|
||||
@router.delete("/{node_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def delete_vpcs_node(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Delete a VPCS node.
|
||||
"""
|
||||
|
||||
# check the project_id exists
|
||||
ProjectManager.instance().get_project(str(project_id))
|
||||
await VPCS.instance().delete_node(str(node_id))
|
||||
|
||||
|
||||
@router.post("/{node_id}/duplicate",
|
||||
response_model=schemas.VPCS,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def duplicate_vpcs_node(project_id: UUID, node_id: UUID, destination_node_id: UUID = Body(..., embed=True)):
|
||||
"""
|
||||
Duplicate a VPCS node.
|
||||
"""
|
||||
|
||||
new_node = await VPCS.instance().duplicate_node(str(node_id), str(destination_node_id))
|
||||
return new_node.__json__()
|
||||
|
||||
|
||||
@router.post("/{node_id}/start",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def start_vpcs_node(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Start a VPCS node.
|
||||
"""
|
||||
|
||||
vpcs_manager = VPCS.instance()
|
||||
vm = vpcs_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await vm.start()
|
||||
|
||||
|
||||
@router.post("/{node_id}/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def stop_vpcs_node(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Stop a VPCS node.
|
||||
"""
|
||||
|
||||
vpcs_manager = VPCS.instance()
|
||||
vm = vpcs_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await vm.stop()
|
||||
|
||||
|
||||
@router.post("/{node_id}/suspend",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def suspend_vpcs_node(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Suspend a VPCS node.
|
||||
Does nothing, suspend is not supported by VPCS.
|
||||
"""
|
||||
|
||||
vpcs_manager = VPCS.instance()
|
||||
vpcs_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
|
||||
|
||||
@router.post("/{node_id}/reload",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def reload_vpcs_node(project_id: UUID, node_id: UUID):
|
||||
"""
|
||||
Reload a VPCS node.
|
||||
"""
|
||||
|
||||
vpcs_manager = VPCS.instance()
|
||||
vm = vpcs_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await vm.reload()
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def create_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, nio_data: schemas.UDPNIO):
|
||||
"""
|
||||
Add a NIO (Network Input/Output) to the node.
|
||||
The adapter number on the VPCS node is always 0.
|
||||
"""
|
||||
|
||||
vpcs_manager = VPCS.instance()
|
||||
vm = vpcs_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
nio = vpcs_manager.create_nio(jsonable_encoder(nio_data, exclude_unset=True))
|
||||
await vm.port_add_nio_binding(port_number, nio)
|
||||
return nio.__json__()
|
||||
|
||||
|
||||
@router.put("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def update_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, nio_data: schemas.UDPNIO):
|
||||
"""
|
||||
Update a NIO (Network Input/Output) on the node.
|
||||
The adapter number on the VPCS node is always 0.
|
||||
"""
|
||||
|
||||
|
||||
vpcs_manager = VPCS.instance()
|
||||
vm = vpcs_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
nio = vm.get_nio(port_number)
|
||||
if nio_data.filters:
|
||||
nio.filters = nio_data.filters
|
||||
await vm.port_update_nio_binding(port_number, nio)
|
||||
return nio.__json__()
|
||||
|
||||
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def delete_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int):
|
||||
"""
|
||||
Delete a NIO (Network Input/Output) from the node.
|
||||
The adapter number on the VPCS node is always 0.
|
||||
"""
|
||||
|
||||
vpcs_manager = VPCS.instance()
|
||||
vm = vpcs_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await vm.port_remove_nio_binding(port_number)
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/start_capture",
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def start_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, node_capture_data: schemas.NodeCapture):
|
||||
"""
|
||||
Start a packet capture on the node.
|
||||
The adapter number on the VPCS node is always 0.
|
||||
"""
|
||||
|
||||
vpcs_manager = VPCS.instance()
|
||||
vm = vpcs_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
pcap_file_path = os.path.join(vm.project.capture_working_directory(), node_capture_data.capture_file_name)
|
||||
await vm.start_capture(adapter_number, pcap_file_path)
|
||||
return {"pcap_file_path": pcap_file_path}
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/stop_capture",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def stop_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int):
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
The adapter number on the VPCS node is always 0.
|
||||
"""
|
||||
|
||||
vpcs_manager = VPCS.instance()
|
||||
vm = vpcs_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await vm.stop_capture(port_number)
|
||||
|
||||
|
||||
@router.post("/{node_id}/console/reset",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def reset_console(project_id: UUID, node_id: UUID):
|
||||
|
||||
vpcs_manager = VPCS.instance()
|
||||
vm = vpcs_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
await vm.reset_console()
|
||||
|
||||
|
||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/pcap",
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}})
|
||||
async def stream_pcap_file(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int):
|
||||
"""
|
||||
Stream the pcap capture file.
|
||||
The adapter number on the VPCS node is always 0.
|
||||
"""
|
||||
|
||||
vpcs_manager = VPCS.instance()
|
||||
vm = vpcs_manager.get_node(str(node_id), project_id=str(project_id))
|
||||
nio = vm.get_nio(port_number)
|
||||
stream = vpcs_manager.stream_pcap_file(nio, vm.project.id)
|
||||
return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap")
|
||||
|
||||
|
||||
# @Route.get(
|
||||
# r"/projects/{project_id}/vpcs/nodes/{node_id}/console/ws",
|
||||
# description="WebSocket for console",
|
||||
# parameters={
|
||||
# "project_id": "Project UUID",
|
||||
# "node_id": "Node UUID",
|
||||
# })
|
||||
# async def console_ws(request, response):
|
||||
#
|
||||
# vpcs_manager = VPCS.instance()
|
||||
# vm = vpcs_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
# return await vm.start_websocket_console(request)
|
||||
Reference in new issue
Block a user