mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-13 04:53:01 +00:00
support named servers in REST API
and exercise them in tests
This commit is contained in:
@@ -108,18 +108,18 @@ class APIHandler(BaseHandler):
|
|||||||
model['pending'] = 'spawn'
|
model['pending'] = 'spawn'
|
||||||
elif user.spawners['']._stop_pending:
|
elif user.spawners['']._stop_pending:
|
||||||
model['pending'] = 'stop'
|
model['pending'] = 'stop'
|
||||||
return model
|
|
||||||
# TODO: named servers
|
if self.allow_named_servers:
|
||||||
servers = model['servers'] = {}
|
servers = model['servers'] = {}
|
||||||
for name, spawner in user.spawners.items():
|
for name, spawner in user.spawners.items():
|
||||||
if user.running(name):
|
if user.running(name):
|
||||||
servers[name] = s = {'name': name}
|
servers[name] = s = {'name': name}
|
||||||
if spawner._spawn_pending:
|
if spawner._spawn_pending:
|
||||||
s['pending'] = 'spawn'
|
s['pending'] = 'spawn'
|
||||||
elif spawner._stop_pending:
|
elif spawner._stop_pending:
|
||||||
s['pending'] = 'stop'
|
s['pending'] = 'stop'
|
||||||
if spawner.server:
|
if spawner.server:
|
||||||
s['url'] = spawner.server.url
|
s['url'] = user.url + name
|
||||||
return model
|
return model
|
||||||
|
|
||||||
def group_model(self, group):
|
def group_model(self, group):
|
||||||
|
@@ -221,7 +221,9 @@ class UserNamedServerAPIHandler(APIHandler):
|
|||||||
"""
|
"""
|
||||||
@gen.coroutine
|
@gen.coroutine
|
||||||
@admin_or_self
|
@admin_or_self
|
||||||
def post(self, name, server_name=''):
|
def post(self, name, server_name):
|
||||||
|
if not self.allow_named_servers:
|
||||||
|
raise web.HTTPError(400, "Named servers are not enabled.")
|
||||||
user = self.find_user(name)
|
user = self.find_user(name)
|
||||||
if user is None:
|
if user is None:
|
||||||
raise web.HTTPError(404, "No such user %r" % name)
|
raise web.HTTPError(404, "No such user %r" % name)
|
||||||
@@ -235,17 +237,23 @@ class UserNamedServerAPIHandler(APIHandler):
|
|||||||
@gen.coroutine
|
@gen.coroutine
|
||||||
@admin_or_self
|
@admin_or_self
|
||||||
def delete(self, name, server_name):
|
def delete(self, name, server_name):
|
||||||
|
if not self.allow_named_servers:
|
||||||
|
raise web.HTTPError(400, "Named servers are not enabled.")
|
||||||
user = self.find_user(name)
|
user = self.find_user(name)
|
||||||
|
if user is None:
|
||||||
|
raise web.HTTPError(404, "No such user %r" % name)
|
||||||
|
if server_name not in user.spawners:
|
||||||
|
raise web.HTTPError(404, "%s has no server named %r" % (name, server_name))
|
||||||
spawner = user.spawners[server_name]
|
spawner = user.spawners[server_name]
|
||||||
if spawner._stop_pending:
|
if spawner._stop_pending:
|
||||||
self.set_status(202)
|
self.set_status(202)
|
||||||
return
|
return
|
||||||
if not user.running(name):
|
if not user.running(server_name):
|
||||||
raise web.HTTPError(400, "%s's server is not running" % name)
|
raise web.HTTPError(400, "%s's server %r is not running" % (name, server_name))
|
||||||
# include notify, so that a server that died is noticed immediately
|
# include notify, so that a server that died is noticed immediately
|
||||||
status = yield spawner.poll_and_notify()
|
status = yield spawner.poll_and_notify()
|
||||||
if status is not None:
|
if status is not None:
|
||||||
raise web.HTTPError(400, "%s's server is not running" % name)
|
raise web.HTTPError(400, "%s's server %r is not running" % (name, server_name))
|
||||||
yield self.stop_single_user(user, server_name)
|
yield self.stop_single_user(user, server_name)
|
||||||
status = 202 if spawner._stop_pending else 204
|
status = 202 if spawner._stop_pending else 204
|
||||||
self.set_status(status)
|
self.set_status(status)
|
||||||
|
@@ -400,11 +400,14 @@ class BaseHandler(RequestHandler):
|
|||||||
yield user.stop()
|
yield user.stop()
|
||||||
|
|
||||||
@gen.coroutine
|
@gen.coroutine
|
||||||
def stop_single_user(self, user):
|
def stop_single_user(self, user, name=''):
|
||||||
if user.spawner._stop_pending:
|
if name not in user.spawners:
|
||||||
raise RuntimeError("Stop already pending for: %s" % user.name)
|
raise KeyError("User %s has no such spawner %r", user.name, name)
|
||||||
|
spawner = user.spawners[name]
|
||||||
|
if spawner._stop_pending:
|
||||||
|
raise RuntimeError("Stop already pending for: %s:%s" % (user.name, name))
|
||||||
tic = IOLoop.current().time()
|
tic = IOLoop.current().time()
|
||||||
yield self.proxy.delete_user(user)
|
yield self.proxy.delete_user(user, name)
|
||||||
f = user.stop()
|
f = user.stop()
|
||||||
@gen.coroutine
|
@gen.coroutine
|
||||||
def finish_stop(f=None):
|
def finish_stop(f=None):
|
||||||
@@ -422,9 +425,9 @@ class BaseHandler(RequestHandler):
|
|||||||
try:
|
try:
|
||||||
yield gen.with_timeout(timedelta(seconds=self.slow_stop_timeout), f)
|
yield gen.with_timeout(timedelta(seconds=self.slow_stop_timeout), f)
|
||||||
except gen.TimeoutError:
|
except gen.TimeoutError:
|
||||||
if user.spawner._stop_pending:
|
if spawner._stop_pending:
|
||||||
# hit timeout, but stop is still pending
|
# hit timeout, but stop is still pending
|
||||||
self.log.warning("User %s server is slow to stop", user.name)
|
self.log.warning("User %s:%s server is slow to stop", user.name, name)
|
||||||
# schedule finish for when the server finishes stopping
|
# schedule finish for when the server finishes stopping
|
||||||
IOLoop.current().add_future(f, finish_stop)
|
IOLoop.current().add_future(f, finish_stop)
|
||||||
else:
|
else:
|
||||||
|
@@ -276,7 +276,7 @@ class Proxy(LoggingConfigurable):
|
|||||||
futures = []
|
futures = []
|
||||||
for orm_user in db.query(User):
|
for orm_user in db.query(User):
|
||||||
user = user_dict[orm_user]
|
user = user_dict[orm_user]
|
||||||
for name, spawner in user.spawners:
|
for name, spawner in user.spawners.items():
|
||||||
if user.running(name):
|
if user.running(name):
|
||||||
futures.append(self.add_user(user, name))
|
futures.append(self.add_user(user, name))
|
||||||
# wait after submitting them all
|
# wait after submitting them all
|
||||||
|
@@ -229,7 +229,7 @@ def public_url(app, user_or_service=None, path=''):
|
|||||||
host = user_or_service.host
|
host = user_or_service.host
|
||||||
else:
|
else:
|
||||||
host = public_host(app)
|
host = public_host(app)
|
||||||
prefix = user_or_service.server.base_url
|
prefix = user_or_service.prefix
|
||||||
else:
|
else:
|
||||||
host = public_host(app)
|
host = public_host(app)
|
||||||
prefix = Server.from_url(app.proxy.public_url).base_url
|
prefix = Server.from_url(app.proxy.public_url).base_url
|
||||||
|
@@ -1,10 +1,82 @@
|
|||||||
from .test_api import api_request, add_user
|
"""Tests for named servers"""
|
||||||
|
import pytest
|
||||||
|
import requests
|
||||||
|
|
||||||
def test_create_named_server(app):
|
from ..utils import url_path_join
|
||||||
return
|
|
||||||
app.allow_named_servers = True
|
from .test_api import api_request, add_user, find_user
|
||||||
username = 'user'
|
from .mocking import public_url
|
||||||
servername = 'foo'
|
|
||||||
|
@pytest.fixture
|
||||||
|
def named_servers(app):
|
||||||
|
key = 'allow_named_servers'
|
||||||
|
app.tornado_application.settings[key] = app.tornado_settings[key] = True
|
||||||
|
try:
|
||||||
|
yield True
|
||||||
|
finally:
|
||||||
|
app.tornado_application.settings[key] = app.tornado_settings[key] = False
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_named_server(app, named_servers):
|
||||||
|
username = 'walnut'
|
||||||
|
user = add_user(app.db, app, name=username)
|
||||||
|
# assert user.allow_named_servers == True
|
||||||
|
cookies = app.login_user(username)
|
||||||
|
servername = 'trevor'
|
||||||
r = api_request(app, 'users', username, 'servers', servername, method='post')
|
r = api_request(app, 'users', username, 'servers', servername, method='post')
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
|
assert r.status_code == 201
|
||||||
|
assert r.text == ''
|
||||||
|
|
||||||
|
url = url_path_join(public_url(app, user), servername, 'env')
|
||||||
|
r = requests.get(url, cookies=cookies)
|
||||||
|
r.raise_for_status()
|
||||||
|
assert r.url == url
|
||||||
|
env = r.json()
|
||||||
|
prefix = env.get('JUPYTERHUB_SERVICE_PREFIX')
|
||||||
|
assert prefix == user.spawners[servername].server.base_url
|
||||||
|
assert prefix.endswith('/user/%s/%s/' % (username, servername))
|
||||||
|
|
||||||
|
|
||||||
|
def test_delete_named_server(app, named_servers):
|
||||||
|
username = 'donaar'
|
||||||
|
user = add_user(app.db, app, name=username)
|
||||||
|
assert user.allow_named_servers
|
||||||
|
cookies = app.login_user(username)
|
||||||
|
servername = 'splugoth'
|
||||||
|
r = api_request(app, 'users', username, 'servers', servername, method='post')
|
||||||
|
r.raise_for_status()
|
||||||
|
assert r.status_code == 201
|
||||||
|
|
||||||
|
r = api_request(app, 'users', username, 'servers', servername, method='delete')
|
||||||
|
r.raise_for_status()
|
||||||
|
assert r.status_code == 204
|
||||||
|
|
||||||
|
r = api_request(app, 'users', username)
|
||||||
|
r.raise_for_status()
|
||||||
|
|
||||||
|
user_model = r.json()
|
||||||
|
user_model.pop('last_activity')
|
||||||
|
assert user_model == {
|
||||||
|
'name': username,
|
||||||
|
'groups': [],
|
||||||
|
'kind': 'user',
|
||||||
|
'admin': False,
|
||||||
|
'pending': None,
|
||||||
|
'server': None,
|
||||||
|
'servers': {
|
||||||
|
name: {
|
||||||
|
'name': name,
|
||||||
|
'url': url_path_join(user.url, name),
|
||||||
|
}
|
||||||
|
for name in ['1', servername]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def test_named_server_disabled(app):
|
||||||
|
username = 'user'
|
||||||
|
servername = 'okay'
|
||||||
|
r = api_request(app, 'users', username, 'servers', servername, method='post')
|
||||||
|
assert r.status_code == 400
|
||||||
|
r = api_request(app, 'users', username, 'servers', servername, method='delete')
|
||||||
|
assert r.status_code == 400
|
||||||
|
@@ -134,7 +134,7 @@ class User(HasTraits):
|
|||||||
|
|
||||||
self.allow_named_servers = self.settings.get('allow_named_servers', False)
|
self.allow_named_servers = self.settings.get('allow_named_servers', False)
|
||||||
|
|
||||||
self.base_url = url_path_join(
|
self.base_url = self.prefix = url_path_join(
|
||||||
self.settings.get('base_url', '/'), 'user', self.escaped_name) + '/'
|
self.settings.get('base_url', '/'), 'user', self.escaped_name) + '/'
|
||||||
|
|
||||||
self.spawners = _SpawnerDict(self._new_spawner)
|
self.spawners = _SpawnerDict(self._new_spawner)
|
||||||
|
Reference in New Issue
Block a user