mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-03 05:51:15 +02:00
Merge branch '3.1' into fix/node-update-while-running
This commit is contained in:
20 files changed
+590
-25
No files matched your search
@@ -201,10 +201,12 @@ class Controller:
|
||||
if computes:
|
||||
for c in computes:
|
||||
try:
|
||||
# FIXME: Task exception was never retrieved
|
||||
await self.add_compute(
|
||||
compute_id=str(c.compute_id),
|
||||
connect=False,
|
||||
# computes flagged auto_connect connect in the
|
||||
# background, one second after the controller started
|
||||
connect=c.auto_connect,
|
||||
wait_connection=False,
|
||||
**c.dict(exclude_unset=True, exclude={"compute_id", "created_at", "updated_at"}),
|
||||
ssl_context=self._ssl_context,
|
||||
)
|
||||
@@ -625,9 +627,12 @@ class Controller:
|
||||
if connect:
|
||||
if wait_connection:
|
||||
await compute.connect()
|
||||
else:
|
||||
# call compute.connect() later to give time to the controller to be fully started
|
||||
asyncio.get_event_loop().call_later(1, lambda: asyncio.ensure_future(compute.connect()))
|
||||
elif not hasattr(sys, "_called_from_test"):
|
||||
# connect in the background to give time to the controller to be fully
|
||||
# started; goes through _try_reconnect so connection errors don't die
|
||||
# as unretrieved task exceptions. Skipped during tests: the timer would
|
||||
# leak the connection attempt into another test's event loop.
|
||||
asyncio.get_event_loop().call_later(1, lambda: asyncio.ensure_future(compute._try_reconnect()))
|
||||
self.notification.controller_emit("compute.created", compute.asdict())
|
||||
return compute
|
||||
else:
|
||||
@@ -645,6 +650,17 @@ class Controller:
|
||||
if compute in project.computes:
|
||||
await project.close()
|
||||
|
||||
def get_compute_open_projects(self, compute):
|
||||
"""
|
||||
Return the opened projects using a compute.
|
||||
|
||||
:returns: List of opened projects
|
||||
"""
|
||||
|
||||
return [
|
||||
project for project in self._projects.values() if compute in project.computes and project.status == "opened"
|
||||
]
|
||||
|
||||
def compute_has_open_project(self, compute):
|
||||
"""
|
||||
Check is compute has a project opened.
|
||||
@@ -652,10 +668,7 @@ class Controller:
|
||||
:returns: True if a project is open
|
||||
"""
|
||||
|
||||
for project in self._projects.values():
|
||||
if compute in project.computes and project.status == "opened":
|
||||
return True
|
||||
return False
|
||||
return bool(self.get_compute_open_projects(compute))
|
||||
|
||||
async def delete_compute(self, compute_id):
|
||||
"""
|
||||
|
||||
@@ -61,6 +61,7 @@ class Compute:
|
||||
name=None,
|
||||
console_host=None,
|
||||
ssl_context=None,
|
||||
auto_connect=False,
|
||||
):
|
||||
self._http_session = None
|
||||
assert controller is not None
|
||||
@@ -80,6 +81,11 @@ class Compute:
|
||||
self._connected = False
|
||||
self._notifications = None
|
||||
self._closed = False # Close mean we are destroying the compute node
|
||||
# True when the user asked to disconnect: blocks every reconnection
|
||||
# path (retry backoff, notification stream reconnect, lazy connect)
|
||||
# until connect() is called again
|
||||
self._manual_disconnect = False
|
||||
self._auto_connect = auto_connect
|
||||
self._controller = controller
|
||||
self._set_auth(user, password)
|
||||
self._cpu_usage_percent = 0
|
||||
@@ -154,6 +160,11 @@ class Compute:
|
||||
|
||||
async def update(self, **kwargs):
|
||||
|
||||
if "auto_connect" in kwargs:
|
||||
# stored separately from the generic attributes below so that
|
||||
# asdict() reports it (it is persisted in the database, the
|
||||
# controller-side copy is only a mirror for notifications)
|
||||
self._auto_connect = kwargs.pop("auto_connect")
|
||||
for kw in kwargs:
|
||||
if kw not in ("user", "password"):
|
||||
setattr(self, kw, kwargs[kw])
|
||||
@@ -323,6 +334,7 @@ class Compute:
|
||||
"host": self._host,
|
||||
"port": self._port,
|
||||
"user": self._user,
|
||||
"auto_connect": self._auto_connect,
|
||||
"connected": self._connected,
|
||||
"cpu_usage_percent": self._cpu_usage_percent,
|
||||
"memory_usage_percent": self._memory_usage_percent,
|
||||
@@ -368,7 +380,9 @@ class Compute:
|
||||
:param params: Optional dict of query parameters to append to the URL
|
||||
"""
|
||||
|
||||
if not self._connected and not dont_connect:
|
||||
# a manually disconnected compute is not lazily reconnected either: the
|
||||
# request fails like for any unreachable compute
|
||||
if not self._connected and not self._manual_disconnect and not dont_connect:
|
||||
if self._id == "vm" and not self._controller.gns3vm.running:
|
||||
await self._controller.gns3vm.start()
|
||||
await self.connect()
|
||||
@@ -381,6 +395,9 @@ class Compute:
|
||||
"""
|
||||
We catch error during reconnect
|
||||
"""
|
||||
|
||||
if self._manual_disconnect:
|
||||
return # stop reconnecting, the compute was manually disconnected
|
||||
try:
|
||||
await self.connect()
|
||||
except ControllerError:
|
||||
@@ -413,6 +430,8 @@ class Compute:
|
||||
Check if remote server is accessible
|
||||
"""
|
||||
|
||||
# an explicit connection request cancels a manual disconnect
|
||||
self._manual_disconnect = False
|
||||
if not self._connected and not self._closed and self.host:
|
||||
try:
|
||||
log.info(f"Connecting to compute '{self._id}'")
|
||||
@@ -474,6 +493,31 @@ class Compute:
|
||||
self._last_error = None
|
||||
self._controller.notification.controller_emit("compute.updated", self.asdict())
|
||||
|
||||
async def disconnect(self):
|
||||
"""
|
||||
Manually disconnect from the compute. The controller stops reconnecting
|
||||
(retry backoff, notification stream reconnect and lazy connection are
|
||||
all blocked) until connect() is called again, or the controller
|
||||
restarts with auto_connect enabled for this compute.
|
||||
"""
|
||||
|
||||
self._manual_disconnect = True
|
||||
if self._notifications:
|
||||
# Cancelling the notification task runs its finally block: the
|
||||
# compute is marked disconnected and clients are notified. The
|
||||
# flag set above keeps it from scheduling a reconnection.
|
||||
self._notifications.cancel()
|
||||
try:
|
||||
await self._notifications
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
self._notifications = None
|
||||
if self._http_session and not self._http_session.closed:
|
||||
await self._http_session.close()
|
||||
if self._connected:
|
||||
self._connected = False
|
||||
self._controller.notification.controller_emit("compute.updated", self.asdict())
|
||||
|
||||
async def _connect_notification(self):
|
||||
"""
|
||||
Connect to the notification stream
|
||||
@@ -526,9 +570,12 @@ class Compute:
|
||||
# Try to reconnect after 1 second if server unavailable only if not during tests (otherwise we create a resources usage bomb)
|
||||
from gns3server.api.server import app
|
||||
|
||||
if not app.state.exiting and not hasattr(sys, "_called_from_test"):
|
||||
# The reconnection goes through _try_reconnect (not connect
|
||||
# directly) so that a manual disconnect landing within the 1
|
||||
# second window is still honoured by the time the timer fires
|
||||
if not self._manual_disconnect and not app.state.exiting and not hasattr(sys, "_called_from_test"):
|
||||
log.info(f"Reconnecting to compute '{self._id}' WebSocket '{ws_url}'")
|
||||
asyncio.get_event_loop().call_later(1, lambda: asyncio.ensure_future(self.connect()))
|
||||
asyncio.get_event_loop().call_later(1, lambda: asyncio.ensure_future(self._try_reconnect()))
|
||||
|
||||
def _getUrl(self, path):
|
||||
host = self._host
|
||||
|
||||
Reference in new issue
Block a user