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
@@ -21,7 +21,6 @@ import json
|
||||
import uuid
|
||||
import socket
|
||||
import shutil
|
||||
import aiohttp
|
||||
|
||||
from ..config import Config
|
||||
from .project import Project
|
||||
@@ -37,6 +36,7 @@ from .topology import load_topology
|
||||
from .gns3vm import GNS3VM
|
||||
from ..utils.get_resource import get_resource
|
||||
from .gns3vm.gns3_vm_error import GNS3VMError
|
||||
from .controller_error import ControllerError, ControllerNotFoundError
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -90,14 +90,15 @@ class Controller:
|
||||
port=port,
|
||||
user=server_config.get("user", ""),
|
||||
password=server_config.get("password", ""),
|
||||
force=True)
|
||||
except aiohttp.web.HTTPConflict:
|
||||
force=True,
|
||||
connect=True) # FIXME: not connection for now
|
||||
except ControllerError:
|
||||
log.fatal("Cannot access to the local server, make sure something else is not running on the TCP port {}".format(port))
|
||||
sys.exit(1)
|
||||
for c in computes:
|
||||
try:
|
||||
await self.add_compute(**c)
|
||||
except (aiohttp.web.HTTPError, KeyError):
|
||||
await self.add_compute(**c, connect=False) # FIXME: not connection for now
|
||||
except (ControllerError, KeyError):
|
||||
pass # Skip not available servers at loading
|
||||
|
||||
try:
|
||||
@@ -127,7 +128,7 @@ class Controller:
|
||||
try:
|
||||
await compute.close()
|
||||
# We don't care if a compute is down at this step
|
||||
except (ComputeError, aiohttp.web.HTTPError, OSError):
|
||||
except (ComputeError, ControllerError, OSError):
|
||||
pass
|
||||
await self.gns3vm.exit_vm()
|
||||
#self.save()
|
||||
@@ -150,9 +151,9 @@ class Controller:
|
||||
try:
|
||||
os.makedirs(os.path.dirname(self._config_file), exist_ok=True)
|
||||
if not os.access(self._config_file, os.W_OK):
|
||||
raise aiohttp.web.HTTPConflict(text="Change rejected, cannot write to controller configuration file '{}'".format(self._config_file))
|
||||
raise ControllerNotFoundError("Change rejected, cannot write to controller configuration file '{}'".format(self._config_file))
|
||||
except OSError as e:
|
||||
raise aiohttp.web.HTTPConflict(text="Change rejected: {}".format(e))
|
||||
raise ControllerError("Change rejected: {}".format(e))
|
||||
|
||||
def save(self):
|
||||
"""
|
||||
@@ -240,7 +241,7 @@ class Controller:
|
||||
if file.endswith(".gns3"):
|
||||
try:
|
||||
await self.load_project(os.path.join(project_dir, file), load=False)
|
||||
except (aiohttp.web.HTTPConflict, aiohttp.web.HTTPNotFound, NotImplementedError):
|
||||
except (ControllerError, NotImplementedError):
|
||||
pass # Skip not compatible projects
|
||||
except OSError as e:
|
||||
log.error(str(e))
|
||||
@@ -304,7 +305,7 @@ class Controller:
|
||||
|
||||
for compute in self._computes.values():
|
||||
if name and compute.name == name and not force:
|
||||
raise aiohttp.web.HTTPConflict(text='Compute name "{}" already exists'.format(name))
|
||||
raise ControllerError('Compute name "{}" already exists'.format(name))
|
||||
|
||||
compute = Compute(compute_id=compute_id, controller=self, name=name, **kwargs)
|
||||
self._computes[compute.id] = compute
|
||||
@@ -349,7 +350,7 @@ class Controller:
|
||||
|
||||
try:
|
||||
compute = self.get_compute(compute_id)
|
||||
except aiohttp.web.HTTPNotFound:
|
||||
except ControllerNotFoundError:
|
||||
return
|
||||
await self.close_compute_projects(compute)
|
||||
await compute.close()
|
||||
@@ -382,8 +383,8 @@ class Controller:
|
||||
return self._computes[compute_id]
|
||||
except KeyError:
|
||||
if compute_id == "vm":
|
||||
raise aiohttp.web.HTTPNotFound(text="Cannot use a node on the GNS3 VM server with the GNS3 VM not configured")
|
||||
raise aiohttp.web.HTTPNotFound(text="Compute ID {} doesn't exist".format(compute_id))
|
||||
raise ControllerNotFoundError("Cannot use a node on the GNS3 VM server with the GNS3 VM not configured")
|
||||
raise ControllerNotFoundError("Compute ID {} doesn't exist".format(compute_id))
|
||||
|
||||
def has_compute(self, compute_id):
|
||||
"""
|
||||
@@ -405,9 +406,9 @@ class Controller:
|
||||
for project in self._projects.values():
|
||||
if name and project.name == name:
|
||||
if path and path == project.path:
|
||||
raise aiohttp.web.HTTPConflict(text='Project "{}" already exists in location "{}"'.format(name, path))
|
||||
raise ControllerError('Project "{}" already exists in location "{}"'.format(name, path))
|
||||
else:
|
||||
raise aiohttp.web.HTTPConflict(text='Project "{}" already exists'.format(name))
|
||||
raise ControllerError('Project "{}" already exists'.format(name))
|
||||
project = Project(project_id=project_id, controller=self, name=name, path=path, **kwargs)
|
||||
self._projects[project.id] = project
|
||||
return self._projects[project.id]
|
||||
@@ -421,7 +422,7 @@ class Controller:
|
||||
try:
|
||||
return self._projects[project_id]
|
||||
except KeyError:
|
||||
raise aiohttp.web.HTTPNotFound(text="Project ID {} doesn't exist".format(project_id))
|
||||
raise ControllerNotFoundError("Project ID {} doesn't exist".format(project_id))
|
||||
|
||||
async def get_loaded_project(self, project_id):
|
||||
"""
|
||||
@@ -488,7 +489,7 @@ class Controller:
|
||||
break
|
||||
i += 1
|
||||
if i > 1000000:
|
||||
raise aiohttp.web.HTTPConflict(text="A project name could not be allocated (node limit reached?)")
|
||||
raise ControllerError("A project name could not be allocated (node limit reached?)")
|
||||
return new_name
|
||||
|
||||
@property
|
||||
|
||||
@@ -25,6 +25,7 @@ from .appliance import Appliance
|
||||
from ..config import Config
|
||||
from ..utils.asyncio import locking
|
||||
from ..utils.get_resource import get_resource
|
||||
from .controller_error import ControllerError
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -173,7 +174,7 @@ class ApplianceManager:
|
||||
log.info("Appliances are already up-to-date (ETag {})".format(self._appliances_etag))
|
||||
return
|
||||
elif response.status != 200:
|
||||
raise aiohttp.web.HTTPConflict(text="Could not retrieve appliances from GitHub due to HTTP error code {}".format(response.status))
|
||||
raise ControllerError("Could not retrieve appliances from GitHub due to HTTP error code {}".format(response.status))
|
||||
etag = response.headers.get("ETag")
|
||||
if etag:
|
||||
self._appliances_etag = etag
|
||||
@@ -200,9 +201,9 @@ class ApplianceManager:
|
||||
with open(path, 'wb') as f:
|
||||
f.write(appliance_data)
|
||||
except OSError as e:
|
||||
raise aiohttp.web.HTTPConflict(text="Could not write appliance file '{}': {}".format(path, e))
|
||||
raise ControllerError("Could not write appliance file '{}': {}".format(path, e))
|
||||
except ValueError as e:
|
||||
raise aiohttp.web.HTTPConflict(text="Could not read appliances information from GitHub: {}".format(e))
|
||||
raise ControllerError("Could not read appliances information from GitHub: {}".format(e))
|
||||
|
||||
# download the custom symbols
|
||||
await self.download_custom_symbols()
|
||||
@@ -28,7 +28,12 @@ from operator import itemgetter
|
||||
|
||||
from ..utils import parse_version
|
||||
from ..utils.asyncio import locking
|
||||
from ..controller.controller_error import ControllerError
|
||||
from ..controller.controller_error import (
|
||||
ControllerError,
|
||||
ControllerNotFoundError,
|
||||
ControllerForbiddenError,
|
||||
ControllerTimeoutError,
|
||||
ControllerUnauthorizedError)
|
||||
from ..version import __version__, __version_info__
|
||||
|
||||
|
||||
@@ -40,7 +45,8 @@ class ComputeError(ControllerError):
|
||||
pass
|
||||
|
||||
|
||||
class ComputeConflict(aiohttp.web.HTTPConflict):
|
||||
# FIXME: broken
|
||||
class ComputeConflict(ComputeError):
|
||||
"""
|
||||
Raise when the compute send a 409 that we can handle
|
||||
|
||||
@@ -48,7 +54,7 @@ class ComputeConflict(aiohttp.web.HTTPConflict):
|
||||
"""
|
||||
|
||||
def __init__(self, response):
|
||||
super().__init__(text=response["message"])
|
||||
super().__init__(response["message"])
|
||||
self.response = response
|
||||
|
||||
|
||||
@@ -78,15 +84,16 @@ class Compute:
|
||||
self._closed = False # Close mean we are destroying the compute node
|
||||
self._controller = controller
|
||||
self._set_auth(user, password)
|
||||
self._cpu_usage_percent = None
|
||||
self._memory_usage_percent = None
|
||||
self._disk_usage_percent = None
|
||||
self._cpu_usage_percent = 0
|
||||
self._memory_usage_percent = 0
|
||||
self._disk_usage_percent = 0
|
||||
self._last_error = None
|
||||
self._capabilities = {
|
||||
"version": None,
|
||||
"cpus": None,
|
||||
"memory": None,
|
||||
"disk_size": None,
|
||||
"version": "",
|
||||
"platform": "",
|
||||
"cpus": 0,
|
||||
"memory": 0,
|
||||
"disk_size": 0,
|
||||
"node_types": []
|
||||
}
|
||||
self.name = name
|
||||
@@ -317,7 +324,7 @@ class Compute:
|
||||
url = self._getUrl("/projects/{}/files/{}".format(project.id, path))
|
||||
response = await self._session().request("GET", url, auth=self._auth)
|
||||
if response.status == 404:
|
||||
raise aiohttp.web.HTTPNotFound(text="{} not found on compute".format(path))
|
||||
raise ControllerNotFoundError("{} not found on compute".format(path))
|
||||
return response
|
||||
|
||||
async def download_image(self, image_type, image):
|
||||
@@ -332,7 +339,7 @@ class Compute:
|
||||
url = self._getUrl("/{}/images/{}".format(image_type, image))
|
||||
response = await self._session().request("GET", url, auth=self._auth)
|
||||
if response.status == 404:
|
||||
raise aiohttp.web.HTTPNotFound(text="{} not found on compute".format(image))
|
||||
raise ControllerNotFoundError("{} not found on compute".format(image))
|
||||
return response
|
||||
|
||||
async def http_query(self, method, path, data=None, dont_connect=False, **kwargs):
|
||||
@@ -355,7 +362,7 @@ class Compute:
|
||||
"""
|
||||
try:
|
||||
await self.connect()
|
||||
except aiohttp.web.HTTPConflict:
|
||||
except ControllerError:
|
||||
pass
|
||||
|
||||
@locking
|
||||
@@ -383,19 +390,19 @@ class Compute:
|
||||
asyncio.get_event_loop().call_later(5, lambda: asyncio.ensure_future(self._try_reconnect()))
|
||||
return
|
||||
except aiohttp.web.HTTPNotFound:
|
||||
raise aiohttp.web.HTTPConflict(text="The server {} is not a GNS3 server or it's a 1.X server".format(self._id))
|
||||
raise ControllerNotFoundError("The server {} is not a GNS3 server or it's a 1.X server".format(self._id))
|
||||
except aiohttp.web.HTTPUnauthorized:
|
||||
raise aiohttp.web.HTTPConflict(text="Invalid auth for server {}".format(self._id))
|
||||
raise ControllerUnauthorizedError("Invalid auth for server {}".format(self._id))
|
||||
except aiohttp.web.HTTPServiceUnavailable:
|
||||
raise aiohttp.web.HTTPConflict(text="The server {} is unavailable".format(self._id))
|
||||
raise ControllerNotFoundError("The server {} is unavailable".format(self._id))
|
||||
except ValueError:
|
||||
raise aiohttp.web.HTTPConflict(text="Invalid server url for server {}".format(self._id))
|
||||
raise ComputeError("Invalid server url for server {}".format(self._id))
|
||||
|
||||
if "version" not in response.json:
|
||||
msg = "The server {} is not a GNS3 server".format(self._id)
|
||||
log.error(msg)
|
||||
await self._http_session.close()
|
||||
raise aiohttp.web.HTTPConflict(text=msg)
|
||||
raise ControllerNotFoundError(msg)
|
||||
self._capabilities = response.json
|
||||
|
||||
if response.json["version"].split("-")[0] != __version__.split("-")[0]:
|
||||
@@ -411,13 +418,13 @@ class Compute:
|
||||
log.error(msg)
|
||||
await self._http_session.close()
|
||||
self._last_error = msg
|
||||
raise aiohttp.web.HTTPConflict(text=msg)
|
||||
raise ControllerError(msg)
|
||||
elif parse_version(__version__)[:2] != parse_version(response.json["version"])[:2]:
|
||||
# We don't allow different major version to interact even with dev build
|
||||
log.error(msg)
|
||||
await self._http_session.close()
|
||||
self._last_error = msg
|
||||
raise aiohttp.web.HTTPConflict(text=msg)
|
||||
raise ControllerError(msg)
|
||||
else:
|
||||
msg = "{}\nUsing different versions may result in unexpected problems. Please use at your own risk.".format(msg)
|
||||
self._controller.notification.controller_emit("log.warning", {"message": msg})
|
||||
@@ -521,7 +528,7 @@ class Compute:
|
||||
response = await self._session().request(method, url, headers=headers, data=data, auth=self._auth, chunked=chunked, timeout=timeout)
|
||||
except asyncio.TimeoutError:
|
||||
raise ComputeError("Timeout error for {} call to {} after {}s".format(method, url, timeout))
|
||||
except (aiohttp.ClientError, aiohttp.ServerDisconnectedError, ValueError, KeyError, socket.gaierror) as e:
|
||||
except (aiohttp.ClientError, aiohttp.ServerDisconnectedError, aiohttp.ClientResponseError, ValueError, KeyError, socket.gaierror) as e:
|
||||
# aiohttp 2.3.1 raises socket.gaierror when cannot find host
|
||||
raise ComputeError(str(e))
|
||||
body = await response.read()
|
||||
@@ -538,22 +545,20 @@ class Compute:
|
||||
else:
|
||||
msg = ""
|
||||
|
||||
if response.status == 400:
|
||||
raise aiohttp.web.HTTPBadRequest(text="Bad request {} {}".format(url, body))
|
||||
elif response.status == 401:
|
||||
raise aiohttp.web.HTTPUnauthorized(text="Invalid authentication for compute {}".format(self.id))
|
||||
if response.status == 401:
|
||||
raise ControllerUnauthorizedError("Invalid authentication for compute {}".format(self.id))
|
||||
elif response.status == 403:
|
||||
raise aiohttp.web.HTTPForbidden(text=msg)
|
||||
raise ControllerForbiddenError(msg)
|
||||
elif response.status == 404:
|
||||
raise aiohttp.web.HTTPNotFound(text="{} {} not found".format(method, path))
|
||||
raise ControllerNotFoundError("{} {} not found".format(method, path))
|
||||
elif response.status == 408 or response.status == 504:
|
||||
raise aiohttp.web.HTTPRequestTimeout(text="{} {} request timeout".format(method, path))
|
||||
raise ControllerTimeoutError("{} {} request timeout".format(method, path))
|
||||
elif response.status == 409:
|
||||
try:
|
||||
raise ComputeConflict(json.loads(body))
|
||||
# If the 409 doesn't come from a GNS3 server
|
||||
except ValueError:
|
||||
raise aiohttp.web.HTTPConflict(text=msg)
|
||||
raise ControllerError(msg)
|
||||
elif response.status == 500:
|
||||
raise aiohttp.web.HTTPInternalServerError(text="Internal server error {}".format(url))
|
||||
elif response.status == 503:
|
||||
@@ -567,7 +572,7 @@ class Compute:
|
||||
try:
|
||||
response.json = json.loads(body)
|
||||
except ValueError:
|
||||
raise aiohttp.web.HTTPConflict(text="The server {} is not a GNS3 server".format(self._id))
|
||||
raise ControllerError("The server {} is not a GNS3 server".format(self._id))
|
||||
else:
|
||||
response.json = {}
|
||||
response.body = b""
|
||||
@@ -585,7 +590,7 @@ class Compute:
|
||||
return response
|
||||
|
||||
async def delete(self, path, **kwargs):
|
||||
return (await self.http_query("DELETE", path, **kwargs))
|
||||
return await self.http_query("DELETE", path, **kwargs)
|
||||
|
||||
async def forward(self, method, type, path, data=None):
|
||||
"""
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (C) 2016 GNS3 Technologies Inc.
|
||||
# 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
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
class ControllerError(Exception):
|
||||
|
||||
def __init__(self, message):
|
||||
def __init__(self, message: str):
|
||||
super().__init__(message)
|
||||
self._message = message
|
||||
|
||||
@@ -27,3 +27,27 @@ class ControllerError(Exception):
|
||||
|
||||
def __str__(self):
|
||||
return self._message
|
||||
|
||||
|
||||
class ControllerNotFoundError(ControllerError):
|
||||
|
||||
def __init__(self, message: str):
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class ControllerUnauthorizedError(ControllerError):
|
||||
|
||||
def __init__(self, message: str):
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class ControllerForbiddenError(ControllerError):
|
||||
|
||||
def __init__(self, message: str):
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class ControllerTimeoutError(ControllerError):
|
||||
|
||||
def __init__(self, message: str):
|
||||
super().__init__(message)
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (C) 2016 GNS3 Technologies Inc.
|
||||
# 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
|
||||
@@ -20,10 +20,11 @@ import sys
|
||||
import json
|
||||
import asyncio
|
||||
import aiofiles
|
||||
import aiohttp
|
||||
import zipfile
|
||||
import tempfile
|
||||
|
||||
from .controller_error import ControllerError, ControllerNotFoundError, ControllerTimeoutError
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
import logging
|
||||
@@ -51,13 +52,13 @@ async def export_project(zstream, project, temporary_dir, include_images=False,
|
||||
|
||||
# To avoid issue with data not saved we disallow the export of a running project
|
||||
if project.is_running():
|
||||
raise aiohttp.web.HTTPConflict(text="Project must be stopped in order to export it")
|
||||
raise ControllerError("Project must be stopped in order to export it")
|
||||
|
||||
# Make sure we save the project
|
||||
project.dump()
|
||||
|
||||
if not os.path.exists(project._path):
|
||||
raise aiohttp.web.HTTPNotFound(text="Project could not be found at '{}'".format(project._path))
|
||||
raise ControllerNotFoundError("Project could not be found at '{}'".format(project._path))
|
||||
|
||||
# First we process the .gns3 in order to be sure we don't have an error
|
||||
for file in os.listdir(project._path):
|
||||
@@ -99,7 +100,7 @@ async def export_project(zstream, project, temporary_dir, include_images=False,
|
||||
try:
|
||||
data = await response.content.read(CHUNK_SIZE)
|
||||
except asyncio.TimeoutError:
|
||||
raise aiohttp.web.HTTPRequestTimeout(text="Timeout when downloading file '{}' from remote compute {}:{}".format(compute_file["path"], compute.host, compute.port))
|
||||
raise ControllerTimeoutError("Timeout when downloading file '{}' from remote compute {}:{}".format(compute_file["path"], compute.host, compute.port))
|
||||
if not data:
|
||||
break
|
||||
await f.write(data)
|
||||
@@ -175,7 +176,7 @@ async def _patch_project_file(project, path, zstream, include_images, keep_compu
|
||||
with open(path) as f:
|
||||
topology = json.load(f)
|
||||
except (OSError, ValueError) as e:
|
||||
raise aiohttp.web.HTTPConflict(text="Project file '{}' cannot be read: {}".format(path, e))
|
||||
raise ControllerError("Project file '{}' cannot be read: {}".format(path, e))
|
||||
|
||||
if "topology" in topology:
|
||||
if "nodes" in topology["topology"]:
|
||||
@@ -183,9 +184,9 @@ async def _patch_project_file(project, path, zstream, include_images, keep_compu
|
||||
compute_id = node.get('compute_id', 'local')
|
||||
|
||||
if node["node_type"] == "virtualbox" and node.get("properties", {}).get("linked_clone"):
|
||||
raise aiohttp.web.HTTPConflict(text="Projects with a linked {} clone node cannot not be exported. Please use Qemu instead.".format(node["node_type"]))
|
||||
raise ControllerError("Projects with a linked {} clone node cannot not be exported. Please use Qemu instead.".format(node["node_type"]))
|
||||
if not allow_all_nodes and node["node_type"] in ["virtualbox", "vmware"]:
|
||||
raise aiohttp.web.HTTPConflict(text="Projects with a {} node cannot be exported".format(node["node_type"]))
|
||||
raise ControllerError("Projects with a {} node cannot be exported".format(node["node_type"]))
|
||||
|
||||
if not keep_compute_id:
|
||||
node["compute_id"] = "local" # To make project portable all node by default run on local
|
||||
@@ -272,11 +273,11 @@ async def _export_remote_images(project, compute_id, image_type, image, project_
|
||||
try:
|
||||
compute = [compute for compute in project.computes if compute.id == compute_id][0]
|
||||
except IndexError:
|
||||
raise aiohttp.web.HTTPConflict(text="Cannot export image from '{}' compute. Compute doesn't exist.".format(compute_id))
|
||||
raise ControllerNotFoundError("Cannot export image from '{}' compute. Compute doesn't exist.".format(compute_id))
|
||||
|
||||
response = await compute.download_image(image_type, image)
|
||||
if response.status != 200:
|
||||
raise aiohttp.web.HTTPConflict(text="Cannot export image from compute '{}'. Compute returned status code {}.".format(compute_id, response.status))
|
||||
raise ControllerError("Cannot export image from compute '{}'. Compute returned status code {}.".format(compute_id, response.status))
|
||||
|
||||
(fd, temp_path) = tempfile.mkstemp(dir=temporary_dir)
|
||||
async with aiofiles.open(fd, 'wb') as f:
|
||||
@@ -284,7 +285,7 @@ async def _export_remote_images(project, compute_id, image_type, image, project_
|
||||
try:
|
||||
data = await response.content.read(CHUNK_SIZE)
|
||||
except asyncio.TimeoutError:
|
||||
raise aiohttp.web.HTTPRequestTimeout(text="Timeout when downloading image '{}' from remote compute {}:{}".format(image, compute.host, compute.port))
|
||||
raise ControllerTimeoutError("Timeout when downloading image '{}' from remote compute {}:{}".format(image, compute.host, compute.port))
|
||||
if not data:
|
||||
break
|
||||
await f.write(data)
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
import sys
|
||||
import copy
|
||||
import asyncio
|
||||
import aiohttp
|
||||
import ipaddress
|
||||
|
||||
from ...utils.asyncio import locking
|
||||
@@ -29,6 +28,7 @@ from .remote_gns3_vm import RemoteGNS3VM
|
||||
from .gns3_vm_error import GNS3VMError
|
||||
from ...version import __version__
|
||||
from ..compute import ComputeError
|
||||
from ..controller_error import ControllerError
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -285,7 +285,7 @@ class GNS3VM:
|
||||
force=True)
|
||||
compute.set_last_error(str(e))
|
||||
|
||||
except aiohttp.web.HTTPConflict:
|
||||
except ControllerError:
|
||||
pass
|
||||
log.error("Cannot start the GNS3 VM: {}".format(e))
|
||||
|
||||
@@ -376,8 +376,8 @@ class GNS3VM:
|
||||
self._controller.notification.controller_emit("log.warning", {"message": msg})
|
||||
except ComputeError as e:
|
||||
log.warning("Could not check the VM is in the same subnet as the local server: {}".format(e))
|
||||
except aiohttp.web.HTTPConflict as e:
|
||||
log.warning("Could not check the VM is in the same subnet as the local server: {}".format(e.text))
|
||||
except ControllerError as e:
|
||||
log.warning("Could not check the VM is in the same subnet as the local server: {}".format(e))
|
||||
|
||||
@locking
|
||||
async def _suspend(self):
|
||||
|
||||
@@ -21,11 +21,11 @@ import json
|
||||
import uuid
|
||||
import shutil
|
||||
import zipfile
|
||||
import aiohttp
|
||||
import aiofiles
|
||||
import itertools
|
||||
import tempfile
|
||||
|
||||
from .controller_error import ControllerError
|
||||
from .topology import load_topology
|
||||
from ..utils.asyncio import wait_run_in_executor
|
||||
from ..utils.asyncio import aiozipstream
|
||||
@@ -55,15 +55,15 @@ async def import_project(controller, project_id, stream, location=None, name=Non
|
||||
"""
|
||||
|
||||
if location and ".gns3" in location:
|
||||
raise aiohttp.web.HTTPConflict(text="The destination path should not contain .gns3")
|
||||
raise ControllerError("The destination path should not contain .gns3")
|
||||
|
||||
try:
|
||||
with zipfile.ZipFile(stream) as zip_file:
|
||||
project_file = zip_file.read("project.gns3").decode()
|
||||
except zipfile.BadZipFile:
|
||||
raise aiohttp.web.HTTPConflict(text="Cannot import project, not a GNS3 project (invalid zip)")
|
||||
raise ControllerError("Cannot import project, not a GNS3 project (invalid zip)")
|
||||
except KeyError:
|
||||
raise aiohttp.web.HTTPConflict(text="Cannot import project, project.gns3 file could not be found")
|
||||
raise ControllerError("Cannot import project, project.gns3 file could not be found")
|
||||
|
||||
try:
|
||||
topology = json.loads(project_file)
|
||||
@@ -77,7 +77,7 @@ async def import_project(controller, project_id, stream, location=None, name=Non
|
||||
else:
|
||||
project_name = controller.get_free_project_name(topology["name"])
|
||||
except (ValueError, KeyError):
|
||||
raise aiohttp.web.HTTPConflict(text="Cannot import project, the project.gns3 file is corrupted")
|
||||
raise ControllerError("Cannot import project, the project.gns3 file is corrupted")
|
||||
|
||||
if location:
|
||||
path = location
|
||||
@@ -87,13 +87,13 @@ async def import_project(controller, project_id, stream, location=None, name=Non
|
||||
try:
|
||||
os.makedirs(path, exist_ok=True)
|
||||
except UnicodeEncodeError:
|
||||
raise aiohttp.web.HTTPConflict(text="The project name contain non supported or invalid characters")
|
||||
raise ControllerError("The project name contain non supported or invalid characters")
|
||||
|
||||
try:
|
||||
with zipfile.ZipFile(stream) as zip_file:
|
||||
await wait_run_in_executor(zip_file.extractall, path)
|
||||
except zipfile.BadZipFile:
|
||||
raise aiohttp.web.HTTPConflict(text="Cannot extract files from GNS3 project (invalid zip)")
|
||||
raise ControllerError("Cannot extract files from GNS3 project (invalid zip)")
|
||||
|
||||
topology = load_topology(os.path.join(path, "project.gns3"))
|
||||
topology["name"] = project_name
|
||||
@@ -257,9 +257,9 @@ async def _import_snapshots(snapshots_path, project_name, project_id):
|
||||
with zipfile.ZipFile(f) as zip_file:
|
||||
await wait_run_in_executor(zip_file.extractall, tmpdir)
|
||||
except OSError as e:
|
||||
raise aiohttp.web.HTTPConflict(text="Cannot open snapshot '{}': {}".format(os.path.basename(snapshot), e))
|
||||
raise ControllerError("Cannot open snapshot '{}': {}".format(os.path.basename(snapshot), e))
|
||||
except zipfile.BadZipFile:
|
||||
raise aiohttp.web.HTTPConflict(text="Cannot extract files from snapshot '{}': not a GNS3 project (invalid zip)".format(os.path.basename(snapshot)))
|
||||
raise ControllerError("Cannot extract files from snapshot '{}': not a GNS3 project (invalid zip)".format(os.path.basename(snapshot)))
|
||||
|
||||
# patch the topology with the correct project name and ID
|
||||
try:
|
||||
@@ -272,9 +272,9 @@ async def _import_snapshots(snapshots_path, project_name, project_id):
|
||||
with open(topology_file_path, "w+", encoding="utf-8") as f:
|
||||
json.dump(topology, f, indent=4, sort_keys=True)
|
||||
except OSError as e:
|
||||
raise aiohttp.web.HTTPConflict(text="Cannot update snapshot '{}': the project.gns3 file cannot be modified: {}".format(os.path.basename(snapshot), e))
|
||||
raise ControllerError("Cannot update snapshot '{}': the project.gns3 file cannot be modified: {}".format(os.path.basename(snapshot), e))
|
||||
except (ValueError, KeyError):
|
||||
raise aiohttp.web.HTTPConflict(text="Cannot update snapshot '{}': the project.gns3 file is corrupted".format(os.path.basename(snapshot)))
|
||||
raise ControllerError("Cannot update snapshot '{}': the project.gns3 file is corrupted".format(os.path.basename(snapshot)))
|
||||
|
||||
# write everything back to the original snapshot file
|
||||
try:
|
||||
@@ -287,4 +287,4 @@ async def _import_snapshots(snapshots_path, project_name, project_id):
|
||||
async for chunk in zstream:
|
||||
await f.write(chunk)
|
||||
except OSError as e:
|
||||
raise aiohttp.web.HTTPConflict(text="Cannot update snapshot '{}': the snapshot cannot be recreated: {}".format(os.path.basename(snapshot), e))
|
||||
raise ControllerError("Cannot update snapshot '{}': the snapshot cannot be recreated: {}".format(os.path.basename(snapshot), e))
|
||||
@@ -19,7 +19,8 @@ import os
|
||||
import re
|
||||
import uuid
|
||||
import html
|
||||
import aiohttp
|
||||
|
||||
from .controller_error import ControllerError, ControllerNotFoundError
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -225,26 +226,26 @@ class Link:
|
||||
|
||||
port = node.get_port(adapter_number, port_number)
|
||||
if port is None:
|
||||
raise aiohttp.web.HTTPNotFound(text="Port {}/{} for {} not found".format(adapter_number, port_number, node.name))
|
||||
raise ControllerNotFoundError("Port {}/{} for {} not found".format(adapter_number, port_number, node.name))
|
||||
if port.link is not None:
|
||||
raise aiohttp.web.HTTPConflict(text="Port is already used")
|
||||
raise ControllerError("Port is already used")
|
||||
|
||||
self._link_type = port.link_type
|
||||
|
||||
for other_node in self._nodes:
|
||||
if other_node["node"] == node:
|
||||
raise aiohttp.web.HTTPConflict(text="Cannot connect to itself")
|
||||
raise ControllerError("Cannot connect to itself")
|
||||
|
||||
if node.node_type in ["nat", "cloud"]:
|
||||
if other_node["node"].node_type in ["nat", "cloud"]:
|
||||
raise aiohttp.web.HTTPConflict(text="Connecting a {} to a {} is not allowed".format(other_node["node"].node_type, node.node_type))
|
||||
raise ControllerError("Connecting a {} to a {} is not allowed".format(other_node["node"].node_type, node.node_type))
|
||||
|
||||
# Check if user is not connecting serial => ethernet
|
||||
other_port = other_node["node"].get_port(other_node["adapter_number"], other_node["port_number"])
|
||||
if other_port is None:
|
||||
raise aiohttp.web.HTTPNotFound(text="Port {}/{} for {} not found".format(other_node["adapter_number"], other_node["port_number"], other_node["node"].name))
|
||||
raise ControllerNotFoundError("Port {}/{} for {} not found".format(other_node["adapter_number"], other_node["port_number"], other_node["node"].name))
|
||||
if port.link_type != other_port.link_type:
|
||||
raise aiohttp.web.HTTPConflict(text="Connecting a {} interface to a {} interface is not allowed".format(other_port.link_type, port.link_type))
|
||||
raise ControllerError("Connecting a {} interface to a {} interface is not allowed".format(other_port.link_type, port.link_type))
|
||||
|
||||
if label is None:
|
||||
label = {
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
# 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 html
|
||||
import copy
|
||||
@@ -23,6 +22,7 @@ import uuid
|
||||
import os
|
||||
|
||||
from .compute import ComputeConflict, ComputeError
|
||||
from .controller_error import ControllerError, ControllerTimeoutError
|
||||
from .ports.port_factory import PortFactory, StandardPortFactory, DynamipsPortFactory
|
||||
from ..utils.images import images_directories
|
||||
from ..utils.qt import qt_font_to_style
|
||||
@@ -420,7 +420,7 @@ class Node:
|
||||
compute_properties = kwargs[prop]
|
||||
else:
|
||||
if prop == "name" and self.status == "started" and self._node_type not in ("cloud", "nat", "ethernet_switch", "ethernet_hub", "frame_relay_switch", "atm_switch"):
|
||||
raise aiohttp.web.HTTPConflict(text="Sorry, it is not possible to rename a node that is already powered on")
|
||||
raise ControllerError("Sorry, it is not possible to rename a node that is already powered on")
|
||||
setattr(self, prop, kwargs[prop])
|
||||
|
||||
if compute_properties and "custom_adapters" in compute_properties:
|
||||
@@ -532,7 +532,7 @@ class Node:
|
||||
else:
|
||||
await self.post("/start", data=data, timeout=240)
|
||||
except asyncio.TimeoutError:
|
||||
raise aiohttp.web.HTTPRequestTimeout(text="Timeout when starting {}".format(self._name))
|
||||
raise ControllerTimeoutError("Timeout when starting {}".format(self._name))
|
||||
|
||||
async def stop(self):
|
||||
"""
|
||||
@@ -541,10 +541,10 @@ class Node:
|
||||
try:
|
||||
await self.post("/stop", timeout=240, dont_connect=True)
|
||||
# We don't care if a node is down at this step
|
||||
except (ComputeError, aiohttp.ClientError, aiohttp.web.HTTPError):
|
||||
except (ComputeError, ControllerError):
|
||||
pass
|
||||
except asyncio.TimeoutError:
|
||||
raise aiohttp.web.HTTPRequestTimeout(text="Timeout when stopping {}".format(self._name))
|
||||
raise ControllerTimeoutError("Timeout when stopping {}".format(self._name))
|
||||
|
||||
async def suspend(self):
|
||||
"""
|
||||
@@ -553,7 +553,7 @@ class Node:
|
||||
try:
|
||||
await self.post("/suspend", timeout=240)
|
||||
except asyncio.TimeoutError:
|
||||
raise aiohttp.web.HTTPRequestTimeout(text="Timeout when reloading {}".format(self._name))
|
||||
raise ControllerTimeoutError("Timeout when reloading {}".format(self._name))
|
||||
|
||||
async def reload(self):
|
||||
"""
|
||||
@@ -562,7 +562,7 @@ class Node:
|
||||
try:
|
||||
await self.post("/reload", timeout=240)
|
||||
except asyncio.TimeoutError:
|
||||
raise aiohttp.web.HTTPRequestTimeout(text="Timeout when reloading {}".format(self._name))
|
||||
raise ControllerTimeoutError("Timeout when reloading {}".format(self._name))
|
||||
|
||||
async def reset_console(self):
|
||||
"""
|
||||
@@ -573,7 +573,7 @@ class Node:
|
||||
try:
|
||||
await self.post("/console/reset", timeout=240)
|
||||
except asyncio.TimeoutError:
|
||||
raise aiohttp.web.HTTPRequestTimeout(text="Timeout when reset console {}".format(self._name))
|
||||
raise ControllerTimeoutError("Timeout when reset console {}".format(self._name))
|
||||
|
||||
async def post(self, path, data=None, **kwargs):
|
||||
"""
|
||||
@@ -602,15 +602,17 @@ class Node:
|
||||
HTTP post on the node
|
||||
"""
|
||||
if path is None:
|
||||
return (await self._compute.delete("/projects/{}/{}/nodes/{}".format(self._project.id, self._node_type, self._id), **kwargs))
|
||||
return await self._compute.delete("/projects/{}/{}/nodes/{}".format(self._project.id, self._node_type, self._id), **kwargs)
|
||||
else:
|
||||
return (await self._compute.delete("/projects/{}/{}/nodes/{}{}".format(self._project.id, self._node_type, self._id, path), **kwargs))
|
||||
return await self._compute.delete("/projects/{}/{}/nodes/{}{}".format(self._project.id, self._node_type, self._id, path), **kwargs)
|
||||
|
||||
async def _upload_missing_image(self, type, img):
|
||||
"""
|
||||
Search an image on local computer and upload it to remote compute
|
||||
if the image exists
|
||||
"""
|
||||
|
||||
print("UPLOAD MISSING IMAGE")
|
||||
for directory in images_directories(type):
|
||||
image = os.path.join(directory, img)
|
||||
if os.path.exists(image):
|
||||
@@ -619,7 +621,7 @@ class Node:
|
||||
with open(image, 'rb') as f:
|
||||
await self._compute.post("/{}/images/{}".format(self._node_type, os.path.basename(img)), data=f, timeout=None)
|
||||
except OSError as e:
|
||||
raise aiohttp.web.HTTPConflict(text="Can't upload {}: {}".format(image, str(e)))
|
||||
raise ControllerError("Can't upload {}: {}".format(image, str(e)))
|
||||
self.project.emit_notification("log.info", {"message": "Upload finished for {}".format(img)})
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -16,10 +16,10 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import aiohttp
|
||||
from contextlib import contextmanager
|
||||
|
||||
from ..notification_queue import NotificationQueue
|
||||
from .controller_error import ControllerError
|
||||
|
||||
|
||||
class Notification:
|
||||
@@ -106,9 +106,8 @@ class Notification:
|
||||
project = self._controller.get_project(event["project_id"])
|
||||
node = project.get_node(event["node_id"])
|
||||
await node.parse_node_response(event)
|
||||
|
||||
self.project_emit("node.updated", node.__json__())
|
||||
except (aiohttp.web.HTTPNotFound, aiohttp.web.HTTPForbidden): # Project closing
|
||||
except ControllerError: # Project closing
|
||||
return
|
||||
elif action == "ping":
|
||||
event["compute_id"] = compute_id
|
||||
|
||||
@@ -15,8 +15,7 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import aiohttp
|
||||
|
||||
from gns3server.controller.controller_error import ControllerError
|
||||
from gns3server.utils import macaddress_to_int, int_to_macaddress
|
||||
from .atm_port import ATMPort
|
||||
from .frame_relay_port import FrameRelayPort
|
||||
@@ -26,6 +25,7 @@ from .ethernet_port import EthernetPort
|
||||
from .serial_port import SerialPort
|
||||
from .pos_port import POSPort
|
||||
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -81,7 +81,7 @@ class StandardPortFactory:
|
||||
adapter=adapter_number,
|
||||
**cls._generate_replacement(interface_number, segment_number))
|
||||
except (IndexError, ValueError, KeyError) as e:
|
||||
raise aiohttp.web.HTTPConflict(text="Invalid port name format {}: {}".format(port_name_format, str(e)))
|
||||
raise ControllerError("Invalid port name format {}: {}".format(port_name_format, str(e)))
|
||||
|
||||
port_name = custom_adapter_settings.get("port_name", port_name)
|
||||
port = PortFactory(port_name, segment_number, adapter_number, port_number, "ethernet")
|
||||
|
||||
@@ -23,7 +23,6 @@ import copy
|
||||
import shutil
|
||||
import time
|
||||
import asyncio
|
||||
import aiohttp
|
||||
import aiofiles
|
||||
import tempfile
|
||||
import zipfile
|
||||
@@ -44,6 +43,7 @@ from ..utils.asyncio import locking
|
||||
from ..utils.asyncio import aiozipstream
|
||||
from .export_project import export_project
|
||||
from .import_project import import_project
|
||||
from .controller_error import ControllerError, ControllerForbiddenError, ControllerNotFoundError
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -56,7 +56,7 @@ def open_required(func):
|
||||
|
||||
def wrapper(self, *args, **kwargs):
|
||||
if self._status == "closed":
|
||||
raise aiohttp.web.HTTPForbidden(text="The project is not opened")
|
||||
raise ControllerForbiddenError("The project is not opened")
|
||||
return func(self, *args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
@@ -100,7 +100,7 @@ class Project:
|
||||
# Disallow overwrite of existing project
|
||||
if project_id is None and path is not None:
|
||||
if os.path.exists(path):
|
||||
raise aiohttp.web.HTTPForbidden(text="The path {} already exist.".format(path))
|
||||
raise ControllerForbiddenError("The path {} already exist.".format(path))
|
||||
|
||||
if project_id is None:
|
||||
self._id = str(uuid4())
|
||||
@@ -108,7 +108,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 ControllerError("{} is not a valid UUID".format(project_id))
|
||||
self._id = project_id
|
||||
|
||||
if path is None:
|
||||
@@ -404,10 +404,10 @@ 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 ControllerError("Could not create project directory: {}".format(e))
|
||||
|
||||
if '"' in path:
|
||||
raise aiohttp.web.HTTPForbidden(text="You are not allowed to use \" in the project directory path. Not supported by Dynamips.")
|
||||
raise ControllerForbiddenError("You are not allowed to use \" in the project directory path. Not supported by Dynamips.")
|
||||
|
||||
self._path = path
|
||||
|
||||
@@ -472,9 +472,9 @@ class Project:
|
||||
try:
|
||||
name = base_name.format(number, id=number, name="Node")
|
||||
except KeyError as e:
|
||||
raise aiohttp.web.HTTPConflict(text="{" + e.args[0] + "} is not a valid replacement string in the node name")
|
||||
raise ControllerError("{" + e.args[0] + "} is not a valid replacement string in the node name")
|
||||
except (ValueError, IndexError) as e:
|
||||
raise aiohttp.web.HTTPConflict(text="{} is not a valid replacement string in the node name".format(base_name))
|
||||
raise ControllerError("{} is not a valid replacement string in the node name".format(base_name))
|
||||
if name not in self._allocated_node_names:
|
||||
self._allocated_node_names.add(name)
|
||||
return name
|
||||
@@ -488,7 +488,7 @@ class Project:
|
||||
if name not in self._allocated_node_names:
|
||||
self._allocated_node_names.add(name)
|
||||
return name
|
||||
raise aiohttp.web.HTTPConflict(text="A node name could not be allocated (node limit reached?)")
|
||||
raise ControllerError("A node name could not be allocated (node limit reached?)")
|
||||
|
||||
def update_node_name(self, node, new_name):
|
||||
|
||||
@@ -507,7 +507,7 @@ class Project:
|
||||
except KeyError:
|
||||
msg = "Template {} doesn't exist".format(template_id)
|
||||
log.error(msg)
|
||||
raise aiohttp.web.HTTPNotFound(text=msg)
|
||||
raise ControllerNotFoundError(msg)
|
||||
template["x"] = x
|
||||
template["y"] = y
|
||||
node_type = template.pop("template_type")
|
||||
@@ -599,7 +599,7 @@ class Project:
|
||||
async def delete_node(self, node_id):
|
||||
node = self.get_node(node_id)
|
||||
if node.locked:
|
||||
raise aiohttp.web.HTTPConflict(text="Node {} cannot be deleted because it is locked".format(node.name))
|
||||
raise ControllerError("Node {} cannot be deleted because it is locked".format(node.name))
|
||||
await self.__delete_node_links(node)
|
||||
self.remove_allocated_node_name(node.name)
|
||||
del self._nodes[node.id]
|
||||
@@ -615,7 +615,7 @@ class Project:
|
||||
try:
|
||||
return self._nodes[node_id]
|
||||
except KeyError:
|
||||
raise aiohttp.web.HTTPNotFound(text="Node ID {} doesn't exist".format(node_id))
|
||||
raise ControllerNotFoundError("Node ID {} doesn't exist".format(node_id))
|
||||
|
||||
def _get_closed_data(self, section, id_key):
|
||||
"""
|
||||
@@ -631,7 +631,7 @@ class Project:
|
||||
with open(path, "r") as f:
|
||||
topology = json.load(f)
|
||||
except OSError as e:
|
||||
raise aiohttp.web.HTTPInternalServerError(text="Could not load topology: {}".format(e))
|
||||
raise ControllerError("Could not load topology: {}".format(e))
|
||||
|
||||
try:
|
||||
data = {}
|
||||
@@ -639,7 +639,7 @@ class Project:
|
||||
data[elem[id_key]] = elem
|
||||
return data
|
||||
except KeyError:
|
||||
raise aiohttp.web.HTTPNotFound(text="Section {} not found in the topology".format(section))
|
||||
raise ControllerNotFoundError("Section {} not found in the topology".format(section))
|
||||
|
||||
@property
|
||||
def nodes(self):
|
||||
@@ -684,13 +684,13 @@ class Project:
|
||||
try:
|
||||
return self._drawings[drawing_id]
|
||||
except KeyError:
|
||||
raise aiohttp.web.HTTPNotFound(text="Drawing ID {} doesn't exist".format(drawing_id))
|
||||
raise ControllerNotFoundError("Drawing ID {} doesn't exist".format(drawing_id))
|
||||
|
||||
@open_required
|
||||
async def delete_drawing(self, drawing_id):
|
||||
drawing = self.get_drawing(drawing_id)
|
||||
if drawing.locked:
|
||||
raise aiohttp.web.HTTPConflict(text="Drawing ID {} cannot be deleted because it is locked".format(drawing_id))
|
||||
raise ControllerError("Drawing ID {} cannot be deleted because it is locked".format(drawing_id))
|
||||
del self._drawings[drawing.id]
|
||||
self.dump()
|
||||
self.emit_notification("drawing.deleted", drawing.__json__())
|
||||
@@ -730,7 +730,7 @@ class Project:
|
||||
try:
|
||||
return self._links[link_id]
|
||||
except KeyError:
|
||||
raise aiohttp.web.HTTPNotFound(text="Link ID {} doesn't exist".format(link_id))
|
||||
raise ControllerNotFoundError("Link ID {} doesn't exist".format(link_id))
|
||||
|
||||
@property
|
||||
def links(self):
|
||||
@@ -756,7 +756,7 @@ class Project:
|
||||
try:
|
||||
return self._snapshots[snapshot_id]
|
||||
except KeyError:
|
||||
raise aiohttp.web.HTTPNotFound(text="Snapshot ID {} doesn't exist".format(snapshot_id))
|
||||
raise ControllerNotFoundError("Snapshot ID {} doesn't exist".format(snapshot_id))
|
||||
|
||||
@open_required
|
||||
async def snapshot(self, name):
|
||||
@@ -767,7 +767,7 @@ class Project:
|
||||
"""
|
||||
|
||||
if name in [snap.name for snap in self._snapshots.values()]:
|
||||
raise aiohttp.web.HTTPConflict(text="The snapshot name {} already exists".format(name))
|
||||
raise ControllerError("The snapshot name {} already exists".format(name))
|
||||
snapshot = Snapshot(self, name=name)
|
||||
await snapshot.create()
|
||||
self._snapshots[snapshot.id] = snapshot
|
||||
@@ -792,7 +792,7 @@ class Project:
|
||||
try:
|
||||
await compute.post("/projects/{}/close".format(self._id), dont_connect=True)
|
||||
# We don't care if a compute is down at this step
|
||||
except (ComputeError, aiohttp.web.HTTPError, aiohttp.ClientResponseError, TimeoutError):
|
||||
except (ComputeError, ControllerError, TimeoutError):
|
||||
pass
|
||||
self._clean_pictures()
|
||||
self._status = "closed"
|
||||
@@ -839,18 +839,18 @@ class Project:
|
||||
if self._status != "opened":
|
||||
try:
|
||||
await self.open()
|
||||
except aiohttp.web.HTTPConflict as e:
|
||||
except ControllerError as e:
|
||||
# ignore missing images or other conflicts when deleting a project
|
||||
log.warning("Conflict while deleting project: {}".format(e.text))
|
||||
log.warning("Conflict while deleting project: {}".format(e))
|
||||
await self.delete_on_computes()
|
||||
await self.close()
|
||||
try:
|
||||
project_directory = get_default_project_directory()
|
||||
if not os.path.commonprefix([project_directory, self.path]) == project_directory:
|
||||
raise aiohttp.web.HTTPConflict(text="Project '{}' cannot be deleted because it is not in the default project directory: '{}'".format(self._name, project_directory))
|
||||
raise ControllerError("Project '{}' cannot be deleted because it is not in the default project directory: '{}'".format(self._name, project_directory))
|
||||
shutil.rmtree(self.path)
|
||||
except OSError as e:
|
||||
raise aiohttp.web.HTTPConflict(text="Cannot delete project directory {}: {}".format(self.path, str(e)))
|
||||
raise ControllerError("Cannot delete project directory {}: {}".format(self.path, str(e)))
|
||||
|
||||
async def delete_on_computes(self):
|
||||
"""
|
||||
@@ -874,7 +874,7 @@ 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 ControllerError("Could not create project directory: {}".format(e))
|
||||
return path
|
||||
|
||||
def _topology_file(self):
|
||||
@@ -887,7 +887,7 @@ class Project:
|
||||
"""
|
||||
|
||||
if self._closing is True:
|
||||
raise aiohttp.web.HTTPConflict(text="Project is closing, please try again in a few seconds...")
|
||||
raise ControllerError("Project is closing, please try again in a few seconds...")
|
||||
|
||||
if self._status == "opened":
|
||||
return
|
||||
@@ -966,7 +966,7 @@ class Project:
|
||||
try:
|
||||
await compute.post("/projects/{}/close".format(self._id))
|
||||
# We don't care if a compute is down at this step
|
||||
except (ComputeError, aiohttp.web.HTTPNotFound, aiohttp.web.HTTPConflict, aiohttp.ServerDisconnectedError):
|
||||
except ComputeError:
|
||||
pass
|
||||
try:
|
||||
if os.path.exists(path + ".backup"):
|
||||
@@ -976,7 +976,7 @@ class Project:
|
||||
self._status = "closed"
|
||||
self._loading = False
|
||||
if isinstance(e, ComputeError):
|
||||
raise aiohttp.web.HTTPConflict(text=str(e))
|
||||
raise ControllerError(str(e))
|
||||
else:
|
||||
raise e
|
||||
try:
|
||||
@@ -1047,7 +1047,7 @@ class Project:
|
||||
|
||||
log.info("Project '{}' duplicated in {:.4f} seconds".format(project.name, time.time() - begin))
|
||||
except (ValueError, OSError, UnicodeEncodeError) as e:
|
||||
raise aiohttp.web.HTTPConflict(text="Cannot duplicate project: {}".format(str(e)))
|
||||
raise ControllerError("Cannot duplicate project: {}".format(str(e)))
|
||||
|
||||
if previous_status == "closed":
|
||||
await self.close()
|
||||
@@ -1076,7 +1076,7 @@ class Project:
|
||||
json.dump(topo, f, indent=4, sort_keys=True)
|
||||
shutil.move(path + ".tmp", path)
|
||||
except OSError as e:
|
||||
raise aiohttp.web.HTTPInternalServerError(text="Could not write topology: {}".format(e))
|
||||
raise ControllerError("Could not write topology: {}".format(e))
|
||||
|
||||
@open_required
|
||||
async def start_all(self):
|
||||
@@ -1131,7 +1131,7 @@ class Project:
|
||||
:returns: New node
|
||||
"""
|
||||
if node.status != "stopped" and not node.is_always_running():
|
||||
raise aiohttp.web.HTTPConflict(text="Cannot duplicate node data while the node is running")
|
||||
raise ControllerError("Cannot duplicate node data while the node is running")
|
||||
|
||||
data = copy.deepcopy(node.__json__(topology_dump=True))
|
||||
# Some properties like internal ID should not be duplicated
|
||||
@@ -1161,10 +1161,10 @@ class Project:
|
||||
await node.post("/duplicate", timeout=None, data={
|
||||
"destination_node_id": new_node_uuid
|
||||
})
|
||||
except aiohttp.web.HTTPNotFound as e:
|
||||
except ControllerNotFoundError:
|
||||
await self.delete_node(new_node_uuid)
|
||||
raise aiohttp.web.HTTPConflict(text="This node type cannot be duplicated")
|
||||
except aiohttp.web.HTTPConflict as e:
|
||||
raise ControllerError("This node type cannot be duplicated")
|
||||
except ControllerError as e:
|
||||
await self.delete_node(new_node_uuid)
|
||||
raise e
|
||||
return new_node
|
||||
|
||||
@@ -23,9 +23,9 @@ import tempfile
|
||||
import aiofiles
|
||||
import zipfile
|
||||
import time
|
||||
import aiohttp.web
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from .controller_error import ControllerError
|
||||
from ..utils.asyncio import wait_run_in_executor
|
||||
from ..utils.asyncio import aiozipstream
|
||||
from .export_project import export_project
|
||||
@@ -85,13 +85,13 @@ class Snapshot:
|
||||
"""
|
||||
|
||||
if os.path.exists(self.path):
|
||||
raise aiohttp.web.HTTPConflict(text="The snapshot file '{}' already exists".format(self.name))
|
||||
raise ControllerError("The snapshot file '{}' already exists".format(self.name))
|
||||
|
||||
snapshot_directory = os.path.join(self._project.path, "snapshots")
|
||||
try:
|
||||
os.makedirs(snapshot_directory, exist_ok=True)
|
||||
except OSError as e:
|
||||
raise aiohttp.web.HTTPInternalServerError(text="Could not create the snapshot directory '{}': {}".format(snapshot_directory, e))
|
||||
raise ControllerError("Could not create the snapshot directory '{}': {}".format(snapshot_directory, e))
|
||||
|
||||
try:
|
||||
begin = time.time()
|
||||
@@ -104,7 +104,7 @@ class Snapshot:
|
||||
await f.write(chunk)
|
||||
log.info("Snapshot '{}' created in {:.4f} seconds".format(self.name, time.time() - begin))
|
||||
except (ValueError, OSError, RuntimeError) as e:
|
||||
raise aiohttp.web.HTTPConflict(text="Could not create snapshot file '{}': {}".format(self.path, e))
|
||||
raise ControllerError("Could not create snapshot file '{}': {}".format(self.path, e))
|
||||
|
||||
async def restore(self):
|
||||
"""
|
||||
@@ -123,7 +123,7 @@ class Snapshot:
|
||||
with open(self._path, "rb") as f:
|
||||
project = await import_project(self._project.controller, self._project.id, f, location=self._project.path)
|
||||
except (OSError, PermissionError) as e:
|
||||
raise aiohttp.web.HTTPConflict(text=str(e))
|
||||
raise ControllerError(str(e))
|
||||
await project.open()
|
||||
self._project.emit_notification("snapshot.restored", self.__json__())
|
||||
return self._project
|
||||
|
||||
@@ -16,10 +16,10 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import aiohttp
|
||||
import posixpath
|
||||
|
||||
from .symbol_themes import BUILTIN_SYMBOL_THEMES
|
||||
from .controller_error import ControllerNotFoundError
|
||||
from ..utils.get_resource import get_resource
|
||||
from ..utils.picture import get_size
|
||||
from ..config import Config
|
||||
@@ -54,7 +54,7 @@ class Symbols:
|
||||
def theme(self, theme):
|
||||
|
||||
if not self._themes.get(theme):
|
||||
raise aiohttp.web.HTTPNotFound(text="Could not find symbol theme '{}'".format(theme))
|
||||
raise ControllerNotFoundError("Could not find symbol theme '{}'".format(theme))
|
||||
self._current_theme = theme
|
||||
|
||||
def default_symbols(self):
|
||||
@@ -65,7 +65,7 @@ class Symbols:
|
||||
|
||||
theme = self._themes.get(symbol_theme, None)
|
||||
if not theme:
|
||||
raise aiohttp.web.HTTPNotFound(text="Could not find symbol theme '{}'".format(symbol_theme))
|
||||
raise ControllerNotFoundError("Could not find symbol theme '{}'".format(symbol_theme))
|
||||
symbol_path = theme.get(symbol)
|
||||
if symbol_path not in self._symbols_path:
|
||||
log.warning("Default symbol {} was not found".format(symbol_path))
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
|
||||
import copy
|
||||
import uuid
|
||||
import aiohttp
|
||||
import jsonschema
|
||||
|
||||
from .controller_error import ControllerError, ControllerNotFoundError
|
||||
from .template import Template
|
||||
|
||||
import logging
|
||||
@@ -85,14 +85,14 @@ class TemplateManager:
|
||||
|
||||
template_id = settings.get("template_id", "")
|
||||
if template_id in self._templates:
|
||||
raise aiohttp.web.HTTPConflict(text="Template ID '{}' already exists".format(template_id))
|
||||
raise ControllerError("Template ID '{}' already exists".format(template_id))
|
||||
else:
|
||||
template_id = settings.setdefault("template_id", str(uuid.uuid4()))
|
||||
try:
|
||||
template = Template(template_id, settings)
|
||||
except jsonschema.ValidationError as e:
|
||||
message = "JSON schema error adding template with JSON data '{}': {}".format(settings, e.message)
|
||||
raise aiohttp.web.HTTPBadRequest(text=message)
|
||||
raise ControllerError(message)
|
||||
|
||||
from . import Controller
|
||||
Controller.instance().check_can_write_config()
|
||||
@@ -112,7 +112,7 @@ class TemplateManager:
|
||||
|
||||
template = self._templates.get(template_id)
|
||||
if not template:
|
||||
raise aiohttp.web.HTTPNotFound(text="Template ID {} doesn't exist".format(template_id))
|
||||
raise ControllerNotFoundError("Template ID {} doesn't exist".format(template_id))
|
||||
return template
|
||||
|
||||
def delete_template(self, template_id):
|
||||
@@ -124,7 +124,7 @@ class TemplateManager:
|
||||
|
||||
template = self.get_template(template_id)
|
||||
if template.builtin:
|
||||
raise aiohttp.web.HTTPConflict(text="Template ID {} cannot be deleted because it is a builtin".format(template_id))
|
||||
raise ControllerError("Template ID {} cannot be deleted because it is a builtin".format(template_id))
|
||||
from . import Controller
|
||||
Controller.instance().check_can_write_config()
|
||||
self._templates.pop(template_id)
|
||||
@@ -140,7 +140,7 @@ class TemplateManager:
|
||||
|
||||
template = self.get_template(template_id)
|
||||
if template.builtin:
|
||||
raise aiohttp.web.HTTPConflict(text="Template ID {} cannot be duplicated because it is a builtin".format(template_id))
|
||||
raise ControllerError("Template ID {} cannot be duplicated because it is a builtin".format(template_id))
|
||||
template_settings = copy.deepcopy(template.settings)
|
||||
del template_settings["template_id"]
|
||||
return self.add_template(template_settings)
|
||||
|
||||
@@ -23,7 +23,6 @@ import uuid
|
||||
import glob
|
||||
import shutil
|
||||
import zipfile
|
||||
import aiohttp
|
||||
import jsonschema
|
||||
|
||||
|
||||
@@ -32,6 +31,7 @@ from ..schemas.topology import TOPOLOGY_SCHEMA
|
||||
from ..schemas import dynamips_vm
|
||||
from ..utils.qt import qt_font_to_style
|
||||
from ..compute.dynamips import PLATFORMS_DEFAULT_RAM
|
||||
from .controller_error import ControllerError
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -64,7 +64,7 @@ def _check_topology_schema(topo):
|
||||
e.message,
|
||||
json.dumps(e.schema))
|
||||
log.critical(error)
|
||||
raise aiohttp.web.HTTPConflict(text=error)
|
||||
raise ControllerError(error)
|
||||
|
||||
|
||||
def project_to_topology(project):
|
||||
@@ -134,10 +134,10 @@ def load_topology(path):
|
||||
with open(path, encoding="utf-8") as f:
|
||||
topo = json.load(f)
|
||||
except (OSError, UnicodeDecodeError, ValueError) as e:
|
||||
raise aiohttp.web.HTTPConflict(text="Could not load topology {}: {}".format(path, str(e)))
|
||||
raise ControllerError("Could not load topology {}: {}".format(path, str(e)))
|
||||
|
||||
if topo.get("revision", 0) > GNS3_FILE_FORMAT_REVISION:
|
||||
raise aiohttp.web.HTTPConflict(text="This project was created with more recent version of GNS3 (file revision: {}). Please upgrade GNS3 to version {} or later".format(topo["revision"], topo["version"]))
|
||||
raise ControllerError("This project was created with more recent version of GNS3 (file revision: {}). Please upgrade GNS3 to version {} or later".format(topo["revision"], topo["version"]))
|
||||
|
||||
changed = False
|
||||
if "revision" not in topo or topo["revision"] < GNS3_FILE_FORMAT_REVISION:
|
||||
@@ -145,7 +145,7 @@ def load_topology(path):
|
||||
try:
|
||||
shutil.copy(path, path + ".backup{}".format(topo.get("revision", 0)))
|
||||
except OSError as e:
|
||||
raise aiohttp.web.HTTPConflict(text="Can't write backup of the topology {}: {}".format(path, str(e)))
|
||||
raise ControllerError("Can't write backup of the topology {}: {}".format(path, str(e)))
|
||||
changed = True
|
||||
# update the version because we converted the topology
|
||||
topo["version"] = __version__
|
||||
@@ -187,7 +187,7 @@ def load_topology(path):
|
||||
|
||||
try:
|
||||
_check_topology_schema(topo)
|
||||
except aiohttp.web.HTTPConflict as e:
|
||||
except ControllerError as e:
|
||||
log.error("Can't load the topology %s", path)
|
||||
raise e
|
||||
|
||||
@@ -196,7 +196,7 @@ def load_topology(path):
|
||||
with open(path, "w+", encoding="utf-8") as f:
|
||||
json.dump(topo, f, indent=4, sort_keys=True)
|
||||
except OSError as e:
|
||||
raise aiohttp.web.HTTPConflict(text="Can't write the topology {}: {}".format(path, str(e)))
|
||||
raise ControllerError("Can't write the topology {}: {}".format(path, str(e)))
|
||||
return topo
|
||||
|
||||
|
||||
@@ -284,7 +284,7 @@ def _convert_2_0_0_beta_2(topo, topo_path):
|
||||
for path in glob.glob(os.path.join(glob.escape(dynamips_dir), "configs", "i{}_*".format(dynamips_id))):
|
||||
shutil.move(path, os.path.join(node_dir, "configs", os.path.basename(path)))
|
||||
except OSError as e:
|
||||
raise aiohttp.web.HTTPConflict(text="Can't convert project {}: {}".format(topo_path, str(e)))
|
||||
raise ControllerError("Can't convert project {}: {}".format(topo_path, str(e)))
|
||||
return topo
|
||||
|
||||
|
||||
@@ -472,7 +472,7 @@ def _convert_1_3_later(topo, topo_path):
|
||||
symbol = old_node.get("symbol", ":/symbols/computer.svg")
|
||||
old_node["ports"] = _create_cloud(node, old_node, symbol)
|
||||
else:
|
||||
raise aiohttp.web.HTTPConflict(text="Conversion of {} is not supported".format(old_node["type"]))
|
||||
raise ControllerError("Conversion of {} is not supported".format(old_node["type"]))
|
||||
|
||||
for prop in old_node.get("properties", {}):
|
||||
if prop not in ["console", "name", "console_type", "console_host", "use_ubridge"]:
|
||||
@@ -671,13 +671,13 @@ def _create_cloud(node, old_node, icon):
|
||||
elif old_port["name"].startswith("nio_nat"):
|
||||
continue
|
||||
else:
|
||||
raise aiohttp.web.HTTPConflict(text="The conversion of cloud with {} is not supported".format(old_port["name"]))
|
||||
raise ControllerError("The conversion of cloud with {} is not supported".format(old_port["name"]))
|
||||
|
||||
if port_type == "udp":
|
||||
try:
|
||||
_, lport, rhost, rport = old_port["name"].split(":")
|
||||
except ValueError:
|
||||
raise aiohttp.web.HTTPConflict(text="UDP tunnel using IPV6 is not supported in cloud")
|
||||
raise ControllerError("UDP tunnel using IPV6 is not supported in cloud")
|
||||
port = {
|
||||
"name": "UDP tunnel {}".format(len(ports) + 1),
|
||||
"port_number": len(ports) + 1,
|
||||
|
||||
@@ -16,9 +16,7 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import aiohttp
|
||||
|
||||
|
||||
from .controller_error import ControllerError, ControllerNotFoundError
|
||||
from .link import Link
|
||||
|
||||
|
||||
@@ -52,7 +50,7 @@ class UDPLink(Link):
|
||||
try:
|
||||
(node1_host, node2_host) = await node1.compute.get_ip_on_same_subnet(node2.compute)
|
||||
except ValueError as e:
|
||||
raise aiohttp.web.HTTPConflict(text="Cannot get an IP address on same subnet: {}".format(e))
|
||||
raise ControllerError("Cannot get an IP address on same subnet: {}".format(e))
|
||||
|
||||
# Reserve a UDP port on both side
|
||||
response = await node1.compute.post("/projects/{}/ports/udp".format(self._project.id))
|
||||
@@ -142,7 +140,7 @@ class UDPLink(Link):
|
||||
try:
|
||||
await node1.delete("/adapters/{adapter_number}/ports/{port_number}/nio".format(adapter_number=adapter_number1, port_number=port_number1), timeout=120)
|
||||
# If the node is already deleted (user selected multiple element and delete all in the same time)
|
||||
except aiohttp.web.HTTPNotFound:
|
||||
except ControllerNotFoundError:
|
||||
pass
|
||||
|
||||
try:
|
||||
@@ -154,7 +152,7 @@ class UDPLink(Link):
|
||||
try:
|
||||
await node2.delete("/adapters/{adapter_number}/ports/{port_number}/nio".format(adapter_number=adapter_number2, port_number=port_number2), timeout=120)
|
||||
# If the node is already deleted (user selected multiple element and delete all in the same time)
|
||||
except aiohttp.web.HTTPNotFound:
|
||||
except ControllerNotFoundError:
|
||||
pass
|
||||
await super().delete()
|
||||
|
||||
@@ -216,7 +214,7 @@ class UDPLink(Link):
|
||||
if node["node"].node_type and node["node"].status == "started":
|
||||
return node
|
||||
|
||||
raise aiohttp.web.HTTPConflict(text="Cannot capture because there is no running device on this link")
|
||||
raise ControllerError("Cannot capture because there is no running device on this link")
|
||||
|
||||
async def node_updated(self, node):
|
||||
"""
|
||||
|
||||
Reference in new issue
Block a user