mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-10 03:23:04 +00:00
support removing named servers
This commit is contained in:
@@ -217,6 +217,13 @@ paths:
|
|||||||
in: path
|
in: path
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
|
- name: remove
|
||||||
|
description: |
|
||||||
|
Whether to fully remove the server, rather than just stop it.
|
||||||
|
Removing a server deletes things like the state of the stopped server.
|
||||||
|
in: body
|
||||||
|
required: false
|
||||||
|
type: boolean
|
||||||
responses:
|
responses:
|
||||||
'201':
|
'201':
|
||||||
description: The user's notebook named-server has started
|
description: The user's notebook named-server has started
|
||||||
|
@@ -378,29 +378,52 @@ class UserServerAPIHandler(APIHandler):
|
|||||||
@admin_or_self
|
@admin_or_self
|
||||||
async def delete(self, name, server_name=''):
|
async def delete(self, name, server_name=''):
|
||||||
user = self.find_user(name)
|
user = self.find_user(name)
|
||||||
|
options = self.get_json_body()
|
||||||
|
remove = (options or {}).get('remove', False)
|
||||||
|
|
||||||
|
|
||||||
|
def _remove_spawner(f=None):
|
||||||
|
if f and f.exception():
|
||||||
|
return
|
||||||
|
self.log.info("Deleting spawner %s", spawner._log_name)
|
||||||
|
self.db.delete(spawner.orm_spawner)
|
||||||
|
self.db.commit()
|
||||||
|
|
||||||
if server_name:
|
if server_name:
|
||||||
if not self.allow_named_servers:
|
if not self.allow_named_servers:
|
||||||
raise web.HTTPError(400, "Named servers are not enabled.")
|
raise web.HTTPError(400, "Named servers are not enabled.")
|
||||||
if server_name not in user.spawners:
|
if server_name not in user.orm_spawners:
|
||||||
raise web.HTTPError(404, "%s has no server named '%s'" % (name, server_name))
|
raise web.HTTPError(404, "%s has no server named '%s'" % (name, server_name))
|
||||||
|
elif remove:
|
||||||
|
raise web.HTTPError(400, "Cannot delete the default server")
|
||||||
|
|
||||||
spawner = user.spawners[server_name]
|
spawner = user.spawners[server_name]
|
||||||
if spawner.pending == 'stop':
|
if spawner.pending == 'stop':
|
||||||
self.log.debug("%s already stopping", spawner._log_name)
|
self.log.debug("%s already stopping", spawner._log_name)
|
||||||
self.set_header('Content-Type', 'text/plain')
|
self.set_header('Content-Type', 'text/plain')
|
||||||
self.set_status(202)
|
self.set_status(202)
|
||||||
|
if remove:
|
||||||
|
spawner._stop_future.add_done_callback(_remove_spawner)
|
||||||
return
|
return
|
||||||
|
|
||||||
if not spawner.ready:
|
if spawner.pending:
|
||||||
raise web.HTTPError(
|
raise web.HTTPError(
|
||||||
400, "%s is not running %s" %
|
400, "%s is pending %s, please wait" % (spawner._log_name, spawner.pending)
|
||||||
(spawner._log_name, '(pending: %s)' % spawner.pending if spawner.pending else '')
|
|
||||||
)
|
)
|
||||||
# include notify, so that a server that died is noticed immediately
|
|
||||||
status = await spawner.poll_and_notify()
|
stop_future = None
|
||||||
if status is not None:
|
if spawner.ready:
|
||||||
raise web.HTTPError(400, "%s is not running" % spawner._log_name)
|
# include notify, so that a server that died is noticed immediately
|
||||||
await self.stop_single_user(user, server_name)
|
status = await spawner.poll_and_notify()
|
||||||
|
if status is None:
|
||||||
|
stop_future = await self.stop_single_user(user, server_name)
|
||||||
|
|
||||||
|
if remove:
|
||||||
|
if stop_future:
|
||||||
|
stop_future.add_done_callback(_remove_spawner)
|
||||||
|
else:
|
||||||
|
_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')
|
||||||
self.set_status(status)
|
self.set_status(status)
|
||||||
|
@@ -677,7 +677,8 @@ def test_slow_spawn(app, no_patience, slow_spawn):
|
|||||||
assert not app_user.spawner._stop_pending
|
assert not app_user.spawner._stop_pending
|
||||||
assert app_user.spawner is not None
|
assert app_user.spawner is not None
|
||||||
r = yield api_request(app, 'users', name, 'server', method='delete')
|
r = yield api_request(app, 'users', name, 'server', method='delete')
|
||||||
assert r.status_code == 400
|
# 204 deleted if there's no such server
|
||||||
|
assert r.status_code == 204
|
||||||
assert app.users.count_active_users()['pending'] == 0
|
assert app.users.count_active_users()['pending'] == 0
|
||||||
assert app.users.count_active_users()['active'] == 0
|
assert app.users.count_active_users()['active'] == 0
|
||||||
|
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
"""Tests for named servers"""
|
"""Tests for named servers"""
|
||||||
|
import json
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@@ -134,6 +135,21 @@ def test_delete_named_server(app, named_servers):
|
|||||||
'auth_state': None,
|
'auth_state': None,
|
||||||
'servers': {},
|
'servers': {},
|
||||||
})
|
})
|
||||||
|
# wrapper Spawner is gone
|
||||||
|
assert servername not in user.spawners
|
||||||
|
# low-level record still exists
|
||||||
|
assert servername in user.orm_spawners
|
||||||
|
|
||||||
|
r = yield api_request(
|
||||||
|
app, 'users', username, 'servers', servername,
|
||||||
|
method='delete',
|
||||||
|
data=json.dumps({'remove': True}),
|
||||||
|
)
|
||||||
|
r.raise_for_status()
|
||||||
|
assert r.status_code == 204
|
||||||
|
# low-level record is now removes
|
||||||
|
assert servername not in user.orm_spawners
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.gen_test
|
@pytest.mark.gen_test
|
||||||
def test_named_server_disabled(app):
|
def test_named_server_disabled(app):
|
||||||
|
Reference in New Issue
Block a user