Compare commits

...
16 Commits
15 changed files with 110 additions and 33 deletions

No files matched your search

+20
View File
@@ -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
+1 -1
View File
@@ -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
+6 -5
View File
@@ -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))
+1
View File
@@ -128,6 +128,7 @@ class Controller:
except (ComputeError, aiohttp.web.HTTPError, OSError):
pass
await self.gns3vm.exit_vm()
#self.save()
self._computes = {}
self._projects = {}
+37 -17
View File
@@ -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()))
+2
View File
@@ -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]
+1 -1
View File
@@ -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)
-1
View File
@@ -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")
+7 -4
View 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)
+1 -1
View File
@@ -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
+19
View File
@@ -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