mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-12 12:33:02 +00:00
define Spawner.delete_forever on base Spawner
instead of on the test class and fix the logic for when it is called a bit: - call on *all* Spawners, not just the default - call on named server deletion when remove=True
This commit is contained in:
@@ -242,11 +242,7 @@ class UserAPIHandler(APIHandler):
|
|||||||
|
|
||||||
await maybe_future(self.authenticator.delete_user(user))
|
await maybe_future(self.authenticator.delete_user(user))
|
||||||
|
|
||||||
# allow the spawner to cleanup any persistent resources associated with the user
|
await user.delete_spawners()
|
||||||
try:
|
|
||||||
await user.spawner.delete_forever()
|
|
||||||
except Exception as e:
|
|
||||||
self.log.error("Error cleaning up persistent resources: %s" % e)
|
|
||||||
|
|
||||||
# remove from registry
|
# remove from registry
|
||||||
self.users.delete(user)
|
self.users.delete(user)
|
||||||
@@ -488,10 +484,18 @@ class UserServerAPIHandler(APIHandler):
|
|||||||
options = self.get_json_body()
|
options = self.get_json_body()
|
||||||
remove = (options or {}).get('remove', False)
|
remove = (options or {}).get('remove', False)
|
||||||
|
|
||||||
def _remove_spawner(f=None):
|
async def _remove_spawner(f=None):
|
||||||
if f and f.exception():
|
"""Remove the spawner object
|
||||||
return
|
|
||||||
|
only called after it stops successfully
|
||||||
|
"""
|
||||||
|
if f:
|
||||||
|
# await f, stop on error,
|
||||||
|
# leaving resources in the db in case of failure to stop
|
||||||
|
await f
|
||||||
self.log.info("Deleting spawner %s", spawner._log_name)
|
self.log.info("Deleting spawner %s", spawner._log_name)
|
||||||
|
await maybe_future(user._delete_spawner(spawner))
|
||||||
|
|
||||||
self.db.delete(spawner.orm_spawner)
|
self.db.delete(spawner.orm_spawner)
|
||||||
user.spawners.pop(server_name, None)
|
user.spawners.pop(server_name, None)
|
||||||
self.db.commit()
|
self.db.commit()
|
||||||
@@ -512,7 +516,8 @@ class UserServerAPIHandler(APIHandler):
|
|||||||
self.set_header('Content-Type', 'text/plain')
|
self.set_header('Content-Type', 'text/plain')
|
||||||
self.set_status(202)
|
self.set_status(202)
|
||||||
if remove:
|
if remove:
|
||||||
spawner._stop_future.add_done_callback(_remove_spawner)
|
# schedule remove when stop completes
|
||||||
|
asyncio.ensure_future(_remove_spawner(spawner._stop_future))
|
||||||
return
|
return
|
||||||
|
|
||||||
if spawner.pending:
|
if spawner.pending:
|
||||||
@@ -530,9 +535,10 @@ class UserServerAPIHandler(APIHandler):
|
|||||||
|
|
||||||
if remove:
|
if remove:
|
||||||
if stop_future:
|
if stop_future:
|
||||||
stop_future.add_done_callback(_remove_spawner)
|
# schedule remove when stop completes
|
||||||
|
asyncio.ensure_future(_remove_spawner(spawner._stop_future))
|
||||||
else:
|
else:
|
||||||
_remove_spawner()
|
await _remove_spawner()
|
||||||
|
|
||||||
status = 202 if spawner._stop_pending else 204
|
status = 202 if spawner._stop_pending else 204
|
||||||
self.set_header('Content-Type', 'text/plain')
|
self.set_header('Content-Type', 'text/plain')
|
||||||
|
@@ -1141,6 +1141,18 @@ class Spawner(LoggingConfigurable):
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError("Override in subclass. Must be a coroutine.")
|
raise NotImplementedError("Override in subclass. Must be a coroutine.")
|
||||||
|
|
||||||
|
def delete_forever(self):
|
||||||
|
"""Called when a user or server is deleted.
|
||||||
|
|
||||||
|
This can do things like request removal of resources such as persistent storage.
|
||||||
|
Only called on stopped spawners, and is usually the last action ever taken for the user.
|
||||||
|
|
||||||
|
Will only be called once on each Spawner, immediately prior to removal.
|
||||||
|
|
||||||
|
Stopping a server does *not* call this method.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
def add_poll_callback(self, callback, *args, **kwargs):
|
def add_poll_callback(self, callback, *args, **kwargs):
|
||||||
"""Add a callback to fire when the single-user server stops"""
|
"""Add a callback to fire when the single-user server stops"""
|
||||||
if args or kwargs:
|
if args or kwargs:
|
||||||
|
@@ -93,16 +93,6 @@ class MockSpawner(SimpleLocalProcessSpawner):
|
|||||||
def _cmd_default(self):
|
def _cmd_default(self):
|
||||||
return [sys.executable, '-m', 'jupyterhub.tests.mocksu']
|
return [sys.executable, '-m', 'jupyterhub.tests.mocksu']
|
||||||
|
|
||||||
async def delete_forever(self):
|
|
||||||
"""Called when a user is deleted.
|
|
||||||
|
|
||||||
This can do things like request removal of resources such as persistent storage.
|
|
||||||
Only called on stopped spawners, and is likely the last action ever taken for the user.
|
|
||||||
|
|
||||||
Will only be called once on the user's default Spawner.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
use_this_api_token = None
|
use_this_api_token = None
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
|
@@ -252,6 +252,35 @@ class User:
|
|||||||
await self.save_auth_state(auth_state)
|
await self.save_auth_state(auth_state)
|
||||||
return auth_state
|
return auth_state
|
||||||
|
|
||||||
|
async def delete_spawners(self):
|
||||||
|
"""Call spawner cleanup methods
|
||||||
|
|
||||||
|
Allows the spawner to cleanup persistent resources
|
||||||
|
"""
|
||||||
|
for name in self.orm_user.orm_spawners.keys():
|
||||||
|
await self._delete_spawner(name)
|
||||||
|
|
||||||
|
async def _delete_spawner(self, name_or_spawner):
|
||||||
|
"""Delete a single spawner"""
|
||||||
|
# always ensure full Spawner
|
||||||
|
# this may instantiate the Spawner if it wasn't already running,
|
||||||
|
# just to delete it
|
||||||
|
if isinstance(name_or_spawner, str):
|
||||||
|
spawner = self.spawners[name_or_spawner]
|
||||||
|
else:
|
||||||
|
spawner = name_or_spawner
|
||||||
|
|
||||||
|
if spawner.active:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Spawner {spawner._log_name} is active and cannot be deleted."
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
await maybe_future(spawner.delete_forever())
|
||||||
|
except Exception as e:
|
||||||
|
self.log.exception(
|
||||||
|
f"Error cleaning up persistent resources on {spawner._log_name}"
|
||||||
|
)
|
||||||
|
|
||||||
def all_spawners(self, include_default=True):
|
def all_spawners(self, include_default=True):
|
||||||
"""Generator yielding all my spawners
|
"""Generator yielding all my spawners
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user