mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-03 05:51:15 +02:00
Migration to FastAPI
This commit is contained in:
1 parent
c12b675691
commit
eb3cb8a41f
224 files changed
+11254
-13517
No files matched your search
@@ -22,7 +22,6 @@ import stat
|
||||
import asyncio
|
||||
import aiofiles
|
||||
|
||||
import aiohttp
|
||||
import socket
|
||||
import shutil
|
||||
import re
|
||||
@@ -30,6 +29,7 @@ import re
|
||||
import logging
|
||||
|
||||
from gns3server.utils.asyncio import cancellable_wait_run_in_executor
|
||||
from gns3server.compute.compute_error import ComputeError, ComputeForbiddenError, ComputeNotFoundError
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -168,15 +168,15 @@ class BaseManager:
|
||||
try:
|
||||
UUID(node_id, version=4)
|
||||
except ValueError:
|
||||
raise aiohttp.web.HTTPBadRequest(text="Node ID {} is not a valid UUID".format(node_id))
|
||||
raise ComputeError("Node ID {} is not a valid UUID".format(node_id))
|
||||
|
||||
if node_id not in self._nodes:
|
||||
raise aiohttp.web.HTTPNotFound(text="Node ID {} doesn't exist".format(node_id))
|
||||
raise ComputeNotFoundError("Node ID {} doesn't exist".format(node_id))
|
||||
|
||||
node = self._nodes[node_id]
|
||||
if project_id:
|
||||
if node.project.id != project.id:
|
||||
raise aiohttp.web.HTTPNotFound(text="Project ID {} doesn't belong to node {}".format(project_id, node.name))
|
||||
raise ComputeNotFoundError("Project ID {} doesn't belong to node {}".format(project_id, node.name))
|
||||
|
||||
return node
|
||||
|
||||
@@ -201,8 +201,8 @@ class BaseManager:
|
||||
log.info('Moving "{}" to "{}"'.format(legacy_project_files_path, new_project_files_path))
|
||||
await wait_run_in_executor(shutil.move, legacy_project_files_path, new_project_files_path)
|
||||
except OSError as e:
|
||||
raise aiohttp.web.HTTPInternalServerError(text="Could not move project files directory: {} to {} {}".format(legacy_project_files_path,
|
||||
new_project_files_path, e))
|
||||
raise ComputeError("Could not move project files directory: {} to {} {}".format(legacy_project_files_path,
|
||||
new_project_files_path, e))
|
||||
|
||||
if project.is_local() is False:
|
||||
legacy_remote_project_path = os.path.join(project.location, project.name, self.module_name.lower())
|
||||
@@ -214,8 +214,8 @@ class BaseManager:
|
||||
log.info('Moving "{}" to "{}"'.format(legacy_remote_project_path, new_remote_project_path))
|
||||
await wait_run_in_executor(shutil.move, legacy_remote_project_path, new_remote_project_path)
|
||||
except OSError as e:
|
||||
raise aiohttp.web.HTTPInternalServerError(text="Could not move directory: {} to {} {}".format(legacy_remote_project_path,
|
||||
new_remote_project_path, e))
|
||||
raise ComputeError("Could not move directory: {} to {} {}".format(legacy_remote_project_path,
|
||||
new_remote_project_path, e))
|
||||
|
||||
if hasattr(self, "get_legacy_vm_workdir"):
|
||||
# rename old project node working dir
|
||||
@@ -228,8 +228,8 @@ class BaseManager:
|
||||
log.info('Moving "{}" to "{}"'.format(legacy_vm_working_path, new_vm_working_path))
|
||||
await wait_run_in_executor(shutil.move, legacy_vm_working_path, new_vm_working_path)
|
||||
except OSError as e:
|
||||
raise aiohttp.web.HTTPInternalServerError(text="Could not move vm working directory: {} to {} {}".format(legacy_vm_working_path,
|
||||
new_vm_working_path, e))
|
||||
raise ComputeError("Could not move vm working directory: {} to {} {}".format(legacy_vm_working_path,
|
||||
new_vm_working_path, e))
|
||||
|
||||
return new_id
|
||||
|
||||
@@ -284,7 +284,7 @@ class BaseManager:
|
||||
shutil.rmtree(destination_dir)
|
||||
shutil.copytree(source_node.working_dir, destination_dir, symlinks=True, ignore_dangling_symlinks=True)
|
||||
except OSError as e:
|
||||
raise aiohttp.web.HTTPConflict(text="Cannot duplicate node data: {}".format(e))
|
||||
raise ComputeError("Cannot duplicate node data: {}".format(e))
|
||||
|
||||
# We force a refresh of the name. This forces the rewrite
|
||||
# of some configuration files
|
||||
@@ -405,13 +405,13 @@ class BaseManager:
|
||||
try:
|
||||
info = socket.getaddrinfo(rhost, rport, socket.AF_UNSPEC, socket.SOCK_DGRAM, 0, socket.AI_PASSIVE)
|
||||
if not info:
|
||||
raise aiohttp.web.HTTPInternalServerError(text="getaddrinfo returns an empty list on {}:{}".format(rhost, rport))
|
||||
raise ComputeError("getaddrinfo returns an empty list on {}:{}".format(rhost, rport))
|
||||
for res in info:
|
||||
af, socktype, proto, _, sa = res
|
||||
with socket.socket(af, socktype, proto) as sock:
|
||||
sock.connect(sa)
|
||||
except OSError as e:
|
||||
raise aiohttp.web.HTTPInternalServerError(text="Could not create an UDP connection to {}:{}: {}".format(rhost, rport, e))
|
||||
raise ComputeError("Could not create an UDP connection to {}:{}: {}".format(rhost, rport, e))
|
||||
nio = NIOUDP(lport, rhost, rport)
|
||||
nio.filters = nio_settings.get("filters", {})
|
||||
nio.suspend = nio_settings.get("suspend", False)
|
||||
@@ -426,48 +426,42 @@ class BaseManager:
|
||||
elif nio_settings["type"] in ("nio_generic_ethernet", "nio_ethernet"):
|
||||
ethernet_device = nio_settings["ethernet_device"]
|
||||
if not is_interface_up(ethernet_device):
|
||||
raise aiohttp.web.HTTPConflict(text="Ethernet interface {} does not exist or is down".format(ethernet_device))
|
||||
raise ComputeError("Ethernet interface {} does not exist or is down".format(ethernet_device))
|
||||
nio = NIOEthernet(ethernet_device)
|
||||
assert nio is not None
|
||||
return nio
|
||||
|
||||
async def stream_pcap_file(self, nio, project_id, request, response):
|
||||
async def stream_pcap_file(self, nio, project_id):
|
||||
"""
|
||||
Streams a PCAP file.
|
||||
|
||||
:param nio: NIO object
|
||||
:param project_id: Project identifier
|
||||
:param request: request object
|
||||
:param response: response object
|
||||
"""
|
||||
|
||||
if not nio.capturing:
|
||||
raise aiohttp.web.HTTPConflict(text="Nothing to stream because there is no packet capture active")
|
||||
raise ComputeError("Nothing to stream because there is no packet capture active")
|
||||
|
||||
project = ProjectManager.instance().get_project(project_id)
|
||||
path = os.path.normpath(os.path.join(project.capture_working_directory(), nio.pcap_output_file))
|
||||
# Raise an error if user try to escape
|
||||
#if path[0] == ".":
|
||||
# raise aiohttp.web.HTTPForbidden()
|
||||
#path = os.path.join(project.path, path)
|
||||
|
||||
response.content_type = "application/vnd.tcpdump.pcap"
|
||||
response.set_status(200)
|
||||
response.enable_chunked_encoding()
|
||||
# Raise an error if user try to escape
|
||||
if path[0] == ".":
|
||||
raise ComputeForbiddenError("Cannot stream PCAP file outside the capture working directory")
|
||||
|
||||
try:
|
||||
with open(path, "rb") as f:
|
||||
await response.prepare(request)
|
||||
while nio.capturing:
|
||||
data = f.read(CHUNK_SIZE)
|
||||
if not data:
|
||||
await asyncio.sleep(0.1)
|
||||
continue
|
||||
await response.write(data)
|
||||
yield data
|
||||
except FileNotFoundError:
|
||||
raise aiohttp.web.HTTPNotFound()
|
||||
raise ComputeNotFoundError("File '{}' not found".format(path))
|
||||
except PermissionError:
|
||||
raise aiohttp.web.HTTPForbidden()
|
||||
raise ComputeForbiddenError("File '{}' cannot be accessed".format(path))
|
||||
|
||||
|
||||
def get_abs_image_path(self, path, extra_dir=None):
|
||||
"""
|
||||
@@ -584,7 +578,7 @@ class BaseManager:
|
||||
try:
|
||||
return list_images(self._NODE_TYPE)
|
||||
except OSError as e:
|
||||
raise aiohttp.web.HTTPConflict(text="Can not list images {}".format(e))
|
||||
raise ComputeError("Can not list images {}".format(e))
|
||||
|
||||
def get_images_directory(self):
|
||||
"""
|
||||
@@ -600,7 +594,7 @@ class BaseManager:
|
||||
directory = self.get_images_directory()
|
||||
path = os.path.abspath(os.path.join(directory, *os.path.split(filename)))
|
||||
if os.path.commonprefix([directory, path]) != directory:
|
||||
raise aiohttp.web.HTTPForbidden(text="Could not write image: {}, {} is forbidden".format(filename, path))
|
||||
raise ComputeForbiddenError("Could not write image: {}, {} is forbidden".format(filename, path))
|
||||
log.info("Writing image file to '{}'".format(path))
|
||||
try:
|
||||
remove_checksum(path)
|
||||
@@ -608,16 +602,13 @@ class BaseManager:
|
||||
tmp_path = path + ".tmp"
|
||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||
async with aiofiles.open(tmp_path, 'wb') as f:
|
||||
while True:
|
||||
chunk = await stream.read(CHUNK_SIZE)
|
||||
if not chunk:
|
||||
break
|
||||
async for chunk in stream:
|
||||
await f.write(chunk)
|
||||
os.chmod(tmp_path, stat.S_IWRITE | stat.S_IREAD | stat.S_IEXEC)
|
||||
shutil.move(tmp_path, path)
|
||||
await cancellable_wait_run_in_executor(md5sum, path)
|
||||
except OSError as e:
|
||||
raise aiohttp.web.HTTPConflict(text="Could not write image: {} because {}".format(filename, e))
|
||||
raise ComputeError("Could not write image: {} because {}".format(filename, e))
|
||||
|
||||
def reset(self):
|
||||
"""
|
||||
|
||||
@@ -29,6 +29,7 @@ import re
|
||||
|
||||
from aiohttp.web import WebSocketResponse
|
||||
from gns3server.utils.interfaces import interfaces
|
||||
from gns3server.compute.compute_error import ComputeError
|
||||
from ..compute.port_manager import PortManager
|
||||
from ..utils.asyncio import wait_run_in_executor, locking
|
||||
from ..utils.asyncio.telnet_server import AsyncioTelnetServer
|
||||
@@ -308,7 +309,7 @@ class BaseNode:
|
||||
try:
|
||||
await wait_run_in_executor(shutil.rmtree, directory, onerror=set_rw)
|
||||
except OSError as e:
|
||||
raise aiohttp.web.HTTPInternalServerError(text="Could not delete the node working directory: {}".format(e))
|
||||
raise ComputeError("Could not delete the node working directory: {}".format(e))
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
|
||||
class ComputeError(Exception):
|
||||
|
||||
def __init__(self, message: str):
|
||||
super().__init__(message)
|
||||
self._message = message
|
||||
|
||||
def __repr__(self):
|
||||
return self._message
|
||||
|
||||
def __str__(self):
|
||||
return self._message
|
||||
|
||||
|
||||
class ComputeNotFoundError(ComputeError):
|
||||
|
||||
def __init__(self, message: str):
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class ComputeUnauthorizedError(ComputeError):
|
||||
|
||||
def __init__(self, message: str):
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class ComputeForbiddenError(ComputeError):
|
||||
|
||||
def __init__(self, message: str):
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class ComputeTimeoutError(ComputeError):
|
||||
|
||||
def __init__(self, message: str):
|
||||
super().__init__(message)
|
||||
@@ -361,7 +361,7 @@ class Dynamips(BaseManager):
|
||||
else:
|
||||
ethernet_device = npf_interface
|
||||
if not is_interface_up(ethernet_device):
|
||||
raise aiohttp.web.HTTPConflict(text="Ethernet interface {} is down".format(ethernet_device))
|
||||
raise DynamipsError("Ethernet interface {} is down".format(ethernet_device))
|
||||
nio = NIOGenericEthernet(node.hypervisor, ethernet_device)
|
||||
elif nio_settings["type"] == "nio_linux_ethernet":
|
||||
if sys.platform.startswith("win"):
|
||||
@@ -373,7 +373,7 @@ class Dynamips(BaseManager):
|
||||
nio = NIOTAP(node.hypervisor, tap_device)
|
||||
if not is_interface_up(tap_device):
|
||||
# test after the TAP interface has been created (if it doesn't exist yet)
|
||||
raise aiohttp.web.HTTPConflict(text="TAP interface {} is down".format(tap_device))
|
||||
raise DynamipsError("TAP interface {} is down".format(tap_device))
|
||||
elif nio_settings["type"] == "nio_unix":
|
||||
local_file = nio_settings["local_file"]
|
||||
remote_file = nio_settings["remote_file"]
|
||||
@@ -385,7 +385,7 @@ class Dynamips(BaseManager):
|
||||
elif nio_settings["type"] == "nio_null":
|
||||
nio = NIONull(node.hypervisor)
|
||||
else:
|
||||
raise aiohttp.web.HTTPConflict(text="NIO of type {} is not supported".format(nio_settings["type"]))
|
||||
raise DynamipsError("NIO of type {} is not supported".format(nio_settings["type"]))
|
||||
|
||||
await nio.create()
|
||||
return nio
|
||||
|
||||
@@ -16,13 +16,13 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import aiohttp
|
||||
import shutil
|
||||
import asyncio
|
||||
import hashlib
|
||||
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
from gns3server.compute.compute_error import ComputeError, ComputeNotFoundError, ComputeForbiddenError
|
||||
from .port_manager import PortManager
|
||||
from .notification_manager import NotificationManager
|
||||
from ..config import Config
|
||||
@@ -50,7 +50,7 @@ class Project:
|
||||
try:
|
||||
UUID(project_id, version=4)
|
||||
except ValueError:
|
||||
raise aiohttp.web.HTTPBadRequest(text="{} is not a valid UUID".format(project_id))
|
||||
raise ComputeError("{} is not a valid UUID".format(project_id))
|
||||
else:
|
||||
project_id = str(uuid4())
|
||||
self._id = project_id
|
||||
@@ -66,14 +66,14 @@ class Project:
|
||||
try:
|
||||
os.makedirs(path, exist_ok=True)
|
||||
except OSError as e:
|
||||
raise aiohttp.web.HTTPInternalServerError(text="Could not create project directory: {}".format(e))
|
||||
raise ComputeError("Could not create project directory: {}".format(e))
|
||||
self.path = path
|
||||
|
||||
try:
|
||||
if os.path.exists(self.tmp_working_directory()):
|
||||
shutil.rmtree(self.tmp_working_directory())
|
||||
except OSError as e:
|
||||
raise aiohttp.web.HTTPInternalServerError(text="Could not clean project directory: {}".format(e))
|
||||
raise ComputeError("Could not clean project directory: {}".format(e))
|
||||
|
||||
log.info("Project {id} with path '{path}' created".format(path=self._path, id=self._id))
|
||||
|
||||
@@ -109,7 +109,7 @@ class Project:
|
||||
|
||||
if hasattr(self, "_path"):
|
||||
if path != self._path and self.is_local() is False:
|
||||
raise aiohttp.web.HTTPForbidden(text="Changing the project directory path is not allowed")
|
||||
raise ComputeForbiddenError("Changing the project directory path is not allowed")
|
||||
|
||||
self._path = path
|
||||
|
||||
@@ -122,7 +122,7 @@ class Project:
|
||||
def name(self, name):
|
||||
|
||||
if "/" in name or "\\" in name:
|
||||
raise aiohttp.web.HTTPForbidden(text="Project names cannot contain path separators")
|
||||
raise ComputeForbiddenError("Project names cannot contain path separators")
|
||||
self._name = name
|
||||
|
||||
@property
|
||||
@@ -192,7 +192,7 @@ class Project:
|
||||
try:
|
||||
os.makedirs(workdir, exist_ok=True)
|
||||
except OSError as e:
|
||||
raise aiohttp.web.HTTPInternalServerError(text="Could not create module working directory: {}".format(e))
|
||||
raise ComputeError("Could not create module working directory: {}".format(e))
|
||||
return workdir
|
||||
|
||||
def module_working_path(self, module_name):
|
||||
@@ -219,7 +219,7 @@ class Project:
|
||||
try:
|
||||
os.makedirs(workdir, exist_ok=True)
|
||||
except OSError as e:
|
||||
raise aiohttp.web.HTTPInternalServerError(text="Could not create the node working directory: {}".format(e))
|
||||
raise ComputeError("Could not create the node working directory: {}".format(e))
|
||||
return workdir
|
||||
|
||||
def node_working_path(self, node):
|
||||
@@ -249,7 +249,7 @@ class Project:
|
||||
try:
|
||||
os.makedirs(workdir, exist_ok=True)
|
||||
except OSError as e:
|
||||
raise aiohttp.web.HTTPInternalServerError(text="Could not create the capture working directory: {}".format(e))
|
||||
raise ComputeError("Could not create the capture working directory: {}".format(e))
|
||||
return workdir
|
||||
|
||||
def add_node(self, node):
|
||||
@@ -274,13 +274,13 @@ class Project:
|
||||
try:
|
||||
UUID(node_id, version=4)
|
||||
except ValueError:
|
||||
raise aiohttp.web.HTTPBadRequest(text="Node ID {} is not a valid UUID".format(node_id))
|
||||
raise ComputeError("Node ID {} is not a valid UUID".format(node_id))
|
||||
|
||||
for node in self._nodes:
|
||||
if node.id == node_id:
|
||||
return node
|
||||
|
||||
raise aiohttp.web.HTTPNotFound(text="Node ID {} doesn't exist".format(node_id))
|
||||
raise ComputeNotFoundError("Node ID {} doesn't exist".format(node_id))
|
||||
|
||||
async def remove_node(self, node):
|
||||
"""
|
||||
@@ -356,7 +356,7 @@ class Project:
|
||||
await wait_run_in_executor(shutil.rmtree, self.path)
|
||||
log.info("Project {id} with path '{path}' deleted".format(path=self._path, id=self._id))
|
||||
except OSError as e:
|
||||
raise aiohttp.web.HTTPInternalServerError(text="Could not delete the project directory: {}".format(e))
|
||||
raise ComputeError("Could not delete the project directory: {}".format(e))
|
||||
else:
|
||||
log.info("Project {id} with path '{path}' closed".format(path=self._path, id=self._id))
|
||||
|
||||
|
||||
@@ -15,13 +15,14 @@
|
||||
# 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 aiohttp
|
||||
import asyncio
|
||||
|
||||
import psutil
|
||||
import platform
|
||||
from .project import Project
|
||||
from uuid import UUID
|
||||
|
||||
from gns3server.compute.compute_error import ComputeError, ComputeNotFoundError
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -70,10 +71,10 @@ class ProjectManager:
|
||||
try:
|
||||
UUID(project_id, version=4)
|
||||
except ValueError:
|
||||
raise aiohttp.web.HTTPBadRequest(text="Project ID {} is not a valid UUID".format(project_id))
|
||||
raise ComputeError("Project ID {} is not a valid UUID".format(project_id))
|
||||
|
||||
if project_id not in self._projects:
|
||||
raise aiohttp.web.HTTPNotFound(text="Project ID {} doesn't exist".format(project_id))
|
||||
raise ComputeNotFoundError("Project ID {} doesn't exist".format(project_id))
|
||||
return self._projects[project_id]
|
||||
|
||||
def _check_available_disk_space(self, project):
|
||||
@@ -118,7 +119,7 @@ class ProjectManager:
|
||||
"""
|
||||
|
||||
if project_id not in self._projects:
|
||||
raise aiohttp.web.HTTPNotFound(text="Project ID {} doesn't exist".format(project_id))
|
||||
raise ComputeNotFoundError("Project ID {} doesn't exist".format(project_id))
|
||||
del self._projects[project_id]
|
||||
|
||||
def check_hardware_virtualization(self, source_node):
|
||||
|
||||
@@ -1060,8 +1060,8 @@ class VirtualBoxVM(BaseNode):
|
||||
if self.is_running():
|
||||
try:
|
||||
await self.update_ubridge_udp_connection("VBOX-{}-{}".format(self._id, adapter_number),
|
||||
self._local_udp_tunnels[adapter_number][1],
|
||||
nio)
|
||||
self._local_udp_tunnels[adapter_number][1],
|
||||
nio)
|
||||
if nio.suspend:
|
||||
await self._control_vm("setlinkstate{} off".format(adapter_number + 1))
|
||||
else:
|
||||
|
||||
Reference in new issue
Block a user