Migration to FastAPI

This commit is contained in:
grossmj committed 2020-10-02 16:07:50 +09:30
1 parent c12b675691
commit eb3cb8a41f
224 files changed
+11254 -13517

No files matched your search

+155
View File
@@ -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}
+240
View File
@@ -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")
+193
View File
@@ -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")
+157
View File
@@ -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")
+307
View File
@@ -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)
+235
View File
@@ -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)
+244
View File
@@ -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()
+332
View File
@@ -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)
#
+283
View File
@@ -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)