mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-13 18:35:15 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7543d7587c | ||
|
|
f5ebc6a29a | ||
|
|
f41158173b | ||
|
|
cb5dc36987 | ||
|
|
fb470dd4b5 | ||
|
|
6dbb992513 | ||
|
|
e962717448 | ||
|
|
6dc58b28fd | ||
|
|
6db8cecda5 | ||
|
|
cc1980ac1c | ||
|
|
2af9c9c8ec | ||
|
|
8cd66cfc02 | ||
|
|
4d222ddc0a | ||
|
|
5c884702e5 | ||
|
|
645031ee15 | ||
|
|
c70b344133 |
No files matched your search
@@ -1,5 +1,20 @@
|
||||
# Change Log
|
||||
|
||||
## 2.2.0a5 15/04/2019
|
||||
|
||||
* Back to the major.minor version for config files. Ref https://github.com/GNS3/gns3-gui/issues/2756
|
||||
* Fix templates missing after server restart. Fixes https://github.com/GNS3/gns3-gui/issues/2769
|
||||
* Fix bug when GNS3 VM were not saved. Fix tests.
|
||||
* Some adjustments with compute WebSocket handling. Ref https://github.com/GNS3/gns3-server/issues/1564
|
||||
* Fix broken embedded console for Ethernet switch. Fixes #1574
|
||||
* Prevent locked nodes to be deleted. Fixes https://github.com/GNS3/gns3-gui/issues/2764
|
||||
* Remove old unused argument option. Fixes #1569
|
||||
|
||||
## 2.1.16 15/04/2019
|
||||
|
||||
* Fix broken embedded console for Ethernet switch. Fixes #1574
|
||||
* Remove old unused argument option. Fixes #1569
|
||||
|
||||
## 2.2.0a4 05/04/2019
|
||||
|
||||
* Use the full version number for path to config files. Ref https://github.com/GNS3/gns3-gui/issues/2756
|
||||
@@ -28,6 +43,11 @@
|
||||
* Save the GNS3 VM settings even if the GNS3 VM cannot be stopped.
|
||||
* Fix exception when emitting event from controller. Ref https://github.com/GNS3/gns3-gui/issues/2737
|
||||
|
||||
## 2.1.15 21/03/2019
|
||||
|
||||
* Fix IOU symlink issue on remote servers.
|
||||
* Fix vcpus configuration for GNS3 VM on VMware. Ref #2738.
|
||||
|
||||
## 2.2.0a2 14/03/2019
|
||||
|
||||
* Web-UI v2019.1.0-alpha.1
|
||||
|
||||
@@ -746,7 +746,7 @@ class QemuVM(BaseNode):
|
||||
log.info('QEMU VM "{name}" [{id}] has set the QEMU initrd path to {initrd}'.format(name=self._name,
|
||||
id=self._id,
|
||||
initrd=initrd))
|
||||
if "asa" in initrd:
|
||||
if "asa" in initrd and self._initrd != initrd:
|
||||
self.project.emit("log.warning", {"message": "Warning ASA 8 is not supported by GNS3 and Cisco, please use ASAv instead. Depending of your hardware and OS this could not work or you could be limited to one instance. If ASA 8 is not booting their is no GNS3 solution, you must to upgrade to ASAv."})
|
||||
self._initrd = initrd
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ class Config:
|
||||
self._watch_callback = []
|
||||
|
||||
appname = "GNS3"
|
||||
version = "{}.{}.{}".format(__version_info__[0], __version_info__[1], __version_info__[2])
|
||||
version = "{}.{}".format(__version_info__[0], __version_info__[1])
|
||||
|
||||
if sys.platform.startswith("win"):
|
||||
|
||||
@@ -131,14 +131,15 @@ class Config:
|
||||
try:
|
||||
# migrate the server config file
|
||||
old_server_config = os.path.join(legacy_user_dir, server_filename)
|
||||
if os.path.exists(old_server_config):
|
||||
new_server_config = os.path.join(versioned_user_dir, server_filename)
|
||||
new_server_config = os.path.join(versioned_user_dir, server_filename)
|
||||
if not os.path.exists(new_server_config) and os.path.exists(old_server_config):
|
||||
shutil.copyfile(old_server_config, new_server_config)
|
||||
|
||||
# migrate the controller config file
|
||||
old_controller_config = os.path.join(legacy_user_dir, controller_filename)
|
||||
if os.path.exists(old_controller_config):
|
||||
shutil.copyfile(old_controller_config, os.path.join(versioned_user_dir, controller_filename))
|
||||
new_controller_config = os.path.join(versioned_user_dir, controller_filename)
|
||||
if not os.path.exists(new_controller_config) and os.path.exists(old_controller_config):
|
||||
shutil.copyfile(old_controller_config, os.path.join(versioned_user_dir, new_controller_config))
|
||||
except OSError as e:
|
||||
log.error("Cannot migrate old config files: {}".format(e))
|
||||
|
||||
|
||||
@@ -128,6 +128,7 @@ class Controller:
|
||||
except (ComputeError, aiohttp.web.HTTPError, OSError):
|
||||
pass
|
||||
await self.gns3vm.exit_vm()
|
||||
#self.save()
|
||||
self._computes = {}
|
||||
self._projects = {}
|
||||
|
||||
|
||||
@@ -74,6 +74,7 @@ class Compute:
|
||||
self._user = None
|
||||
self._password = None
|
||||
self._connected = False
|
||||
self._notifications = None
|
||||
self._closed = False # Close mean we are destroying the compute node
|
||||
self._controller = controller
|
||||
self._set_auth(user, password)
|
||||
@@ -150,9 +151,15 @@ class Compute:
|
||||
self._controller.save()
|
||||
|
||||
async def close(self):
|
||||
|
||||
self._connected = False
|
||||
if self._http_session and not self._http_session.closed:
|
||||
await self._http_session.close()
|
||||
try:
|
||||
if self._notifications:
|
||||
await self._notifications
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
self._closed = True
|
||||
|
||||
@property
|
||||
@@ -417,25 +424,38 @@ class Compute:
|
||||
Connect to the notification stream
|
||||
"""
|
||||
|
||||
async with self._session().ws_connect(self._getUrl("/notifications/ws"), auth=self._auth) as ws:
|
||||
async for response in ws:
|
||||
if response.type == aiohttp.WSMsgType.TEXT and response.data:
|
||||
msg = json.loads(response.data)
|
||||
action = msg.pop("action")
|
||||
event = msg.pop("event")
|
||||
project_id = msg.pop("project_id", None)
|
||||
if action == "ping":
|
||||
self._cpu_usage_percent = event["cpu_usage_percent"]
|
||||
self._memory_usage_percent = event["memory_usage_percent"]
|
||||
#FIXME: slow down number of compute events
|
||||
self._controller.notification.controller_emit("compute.updated", self.__json__())
|
||||
ws_url = self._getUrl("/notifications/ws")
|
||||
try:
|
||||
async with self._session().ws_connect(ws_url, auth=self._auth, heartbeat=10) as ws:
|
||||
log.info("Connected to compute WebSocket '{}'".format(ws_url))
|
||||
async for response in ws:
|
||||
if response.type == aiohttp.WSMsgType.TEXT:
|
||||
msg = json.loads(response.data)
|
||||
action = msg.pop("action")
|
||||
event = msg.pop("event")
|
||||
project_id = msg.pop("project_id", None)
|
||||
if action == "ping":
|
||||
self._cpu_usage_percent = event["cpu_usage_percent"]
|
||||
self._memory_usage_percent = event["memory_usage_percent"]
|
||||
#FIXME: slow down number of compute events
|
||||
self._controller.notification.controller_emit("compute.updated", self.__json__())
|
||||
else:
|
||||
await self._controller.notification.dispatch(action, event, project_id=project_id, compute_id=self.id)
|
||||
else:
|
||||
await self._controller.notification.dispatch(action, event, project_id=project_id, compute_id=self.id)
|
||||
elif response.type == aiohttp.WSMsgType.CLOSED or response.type == aiohttp.WSMsgType.ERROR or response.data is None:
|
||||
self._connected = False
|
||||
break
|
||||
if response.type == aiohttp.WSMsgType.CLOSE:
|
||||
await ws.close()
|
||||
elif response.type == aiohttp.WSMsgType.ERROR:
|
||||
log.error("Error received on compute WebSocket '{}': {}".format(ws_url, ws.exception()))
|
||||
elif response.type == aiohttp.WSMsgType.CLOSED:
|
||||
pass
|
||||
self._connected = False
|
||||
break
|
||||
except aiohttp.client_exceptions.ClientResponseError as e:
|
||||
log.error("Client response error received on compute WebSocket '{}': {}".format(ws_url,e))
|
||||
finally:
|
||||
log.info("Connection closed to compute WebSocket '{}'".format(ws_url))
|
||||
|
||||
# Try to reconnect after 1 seconds if server unavailable only if not during tests (otherwise we create a ressources usage bomb)
|
||||
# Try to reconnect after 1 second if server unavailable only if not during tests (otherwise we create a ressources usage bomb)
|
||||
if not hasattr(sys, "_called_from_test") or not sys._called_from_test:
|
||||
asyncio.get_event_loop().call_later(1, lambda: asyncio.ensure_future(self.connect()))
|
||||
|
||||
|
||||
@@ -567,6 +567,8 @@ class Project:
|
||||
@open_required
|
||||
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))
|
||||
await self.__delete_node_links(node)
|
||||
self.remove_allocated_node_name(node.name)
|
||||
del self._nodes[node.id]
|
||||
|
||||
@@ -58,7 +58,7 @@ class CrashReport:
|
||||
Report crash to a third party service
|
||||
"""
|
||||
|
||||
DSN = "https://eb305634570047afb80a73d634baa5f2:453d676bad2b4d068326e6cb22af7ae5@sentry.io/38482"
|
||||
DSN = "https://7beef06951ab456bb9e17c0ffad7f494:2277ec5146db4649abde8c25c233e8b3@sentry.io/38482"
|
||||
if hasattr(sys, "frozen"):
|
||||
cacert = get_resource("cacert.pem")
|
||||
if cacert is not None and os.path.isfile(cacert):
|
||||
|
||||
@@ -21,6 +21,9 @@ from aiohttp.web import WebSocketResponse
|
||||
from gns3server.web.route import Route
|
||||
from gns3server.compute.notification_manager import NotificationManager
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def process_websocket(ws):
|
||||
"""
|
||||
@@ -44,7 +47,7 @@ class NotificationHandler:
|
||||
|
||||
request.app['websockets'].add(ws)
|
||||
asyncio.ensure_future(process_websocket(ws))
|
||||
|
||||
log.info("New client has connected to compute WebSocket")
|
||||
try:
|
||||
with notifications.queue() as queue:
|
||||
while True:
|
||||
@@ -53,6 +56,7 @@ class NotificationHandler:
|
||||
break
|
||||
await ws.send_str(notification)
|
||||
finally:
|
||||
log.info("Client has disconnected from compute WebSocket")
|
||||
if not ws.closed:
|
||||
await ws.close()
|
||||
request.app['websockets'].discard(ws)
|
||||
|
||||
@@ -72,7 +72,9 @@ class GNS3VMHandler:
|
||||
})
|
||||
async def update(request, response):
|
||||
|
||||
gns3_vm = Controller().instance().gns3vm
|
||||
controller = Controller().instance()
|
||||
gns3_vm = controller.gns3vm
|
||||
await gns3_vm.update_settings(request.json)
|
||||
controller.save()
|
||||
response.json(gns3_vm)
|
||||
response.set_status(201)
|
||||
@@ -189,7 +189,8 @@ class NodeHandler:
|
||||
status_codes={
|
||||
204: "Instance deleted",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
404: "Instance doesn't exist",
|
||||
409: "Cannot delete locked node"
|
||||
},
|
||||
description="Delete a node instance")
|
||||
async def delete(request, response):
|
||||
|
||||
@@ -21,6 +21,9 @@ from aiohttp.web import WebSocketResponse
|
||||
from gns3server.web.route import Route
|
||||
from gns3server.controller import Controller
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def process_websocket(ws):
|
||||
"""
|
||||
@@ -67,6 +70,7 @@ class NotificationHandler:
|
||||
|
||||
request.app['websockets'].add(ws)
|
||||
asyncio.ensure_future(process_websocket(ws))
|
||||
log.info("New client has connected to controller WebSocket")
|
||||
try:
|
||||
with controller.notification.controller_queue() as queue:
|
||||
while True:
|
||||
@@ -75,6 +79,7 @@ class NotificationHandler:
|
||||
break
|
||||
await ws.send_str(notification)
|
||||
finally:
|
||||
log.info("Client has disconnected from controller WebSocket")
|
||||
if not ws.closed:
|
||||
await ws.close()
|
||||
request.app['websockets'].discard(ws)
|
||||
|
||||
@@ -91,7 +91,6 @@ def parse_arguments(argv):
|
||||
parser.add_argument("--host", help="run on the given host/IP address")
|
||||
parser.add_argument("--port", help="run on the given port", type=int)
|
||||
parser.add_argument("--ssl", action="store_true", help="run in SSL mode")
|
||||
parser.add_argument("--no-ubridge", action="store_false", help="do not use ubridge to handle node connections")
|
||||
parser.add_argument("--config", help="Configuration file")
|
||||
parser.add_argument("--certfile", help="SSL cert file")
|
||||
parser.add_argument("--certkey", help="SSL key file")
|
||||
|
||||
@@ -195,8 +195,8 @@ class UnstoppableEventLoop(EventLoop):
|
||||
|
||||
|
||||
class ShellConnection(TelnetConnection):
|
||||
def __init__(self, reader, writer, shell, loop):
|
||||
super(ShellConnection, self).__init__(reader, writer)
|
||||
def __init__(self, reader, writer, shell, window_size_changed_callback, loop):
|
||||
super(ShellConnection, self).__init__(reader, writer, window_size_changed_callback)
|
||||
self._shell = shell
|
||||
self._loop = loop
|
||||
self._cli = None
|
||||
@@ -234,9 +234,12 @@ class ShellConnection(TelnetConnection):
|
||||
async def disconnected(self):
|
||||
pass
|
||||
|
||||
@asyncio.coroutine
|
||||
def window_size_changed(self, columns, rows):
|
||||
self._size = Size(rows=rows, columns=columns)
|
||||
self._cb.terminal_size_changed()
|
||||
if self._window_size_changed_callback:
|
||||
yield from self._window_size_changed_callback(columns, rows)
|
||||
|
||||
async def feed(self, data):
|
||||
data = data.decode()
|
||||
@@ -285,8 +288,8 @@ def create_telnet_shell(shell, loop=None):
|
||||
if loop is None:
|
||||
loop = asyncio.get_event_loop()
|
||||
|
||||
def factory(reader, writer):
|
||||
return ShellConnection(reader, writer, shell, loop)
|
||||
def factory(reader, writer, window_size_changed_callback):
|
||||
return ShellConnection(reader, writer, shell, window_size_changed_callback, loop)
|
||||
|
||||
return AsyncioTelnetServer(binary=True, echo=True, naws=True, connection_factory=factory)
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
# or negative for a release candidate or beta (after the base version
|
||||
# number has been incremented)
|
||||
|
||||
__version__ = "2.2.0a4"
|
||||
__version__ = "2.2.0a5"
|
||||
__version_info__ = (2, 2, 0, -99)
|
||||
|
||||
# If it's a git checkout try to add the commit
|
||||
|
||||
@@ -257,6 +257,25 @@ def test_delete_node(async_run, controller):
|
||||
project.emit_notification.assert_any_call("node.deleted", node.__json__())
|
||||
|
||||
|
||||
def test_delete_locked_node(async_run, controller):
|
||||
"""
|
||||
For a local server we send the project path
|
||||
"""
|
||||
compute = MagicMock()
|
||||
project = Project(controller=controller, name="Test")
|
||||
project.emit_notification = MagicMock()
|
||||
|
||||
response = MagicMock()
|
||||
response.json = {"console": 2048}
|
||||
compute.post = AsyncioMagicMock(return_value=response)
|
||||
|
||||
node = async_run(project.add_node(compute, "test", None, node_type="vpcs", properties={"startup_config": "test.cfg"}))
|
||||
assert node.id in project._nodes
|
||||
node.locked = True
|
||||
with pytest.raises(aiohttp.web_exceptions.HTTPConflict):
|
||||
async_run(project.delete_node(node.id))
|
||||
|
||||
|
||||
def test_delete_node_delete_link(async_run, controller):
|
||||
"""
|
||||
Delete a node delete all the node connected
|
||||
|
||||
Reference in new issue
Block a user