mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-12 20:43:02 +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'
|
||||
elif user.spawners['']._stop_pending:
|
||||
model['pending'] = 'stop'
|
||||
return model
|
||||
# TODO: named servers
|
||||
servers = model['servers'] = {}
|
||||
for name, spawner in user.spawners.items():
|
||||
if user.running(name):
|
||||
servers[name] = s = {'name': name}
|
||||
if spawner._spawn_pending:
|
||||
s['pending'] = 'spawn'
|
||||
elif spawner._stop_pending:
|
||||
s['pending'] = 'stop'
|
||||
if spawner.server:
|
||||
s['url'] = spawner.server.url
|
||||
|
||||
if self.allow_named_servers:
|
||||
servers = model['servers'] = {}
|
||||
for name, spawner in user.spawners.items():
|
||||
if user.running(name):
|
||||
servers[name] = s = {'name': name}
|
||||
if spawner._spawn_pending:
|
||||
s['pending'] = 'spawn'
|
||||
elif spawner._stop_pending:
|
||||
s['pending'] = 'stop'
|
||||
if spawner.server:
|
||||
s['url'] = user.url + name
|
||||
return model
|
||||
|
||||
def group_model(self, group):
|
||||
|
@@ -221,7 +221,9 @@ class UserNamedServerAPIHandler(APIHandler):
|
||||
"""
|
||||
@gen.coroutine
|
||||
@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)
|
||||
if user is None:
|
||||
raise web.HTTPError(404, "No such user %r" % name)
|
||||
@@ -235,17 +237,23 @@ class UserNamedServerAPIHandler(APIHandler):
|
||||
@gen.coroutine
|
||||
@admin_or_self
|
||||
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)
|
||||
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]
|
||||
if spawner._stop_pending:
|
||||
self.set_status(202)
|
||||
return
|
||||
if not user.running(name):
|
||||
raise web.HTTPError(400, "%s's server is not running" % name)
|
||||
if not user.running(server_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
|
||||
status = yield spawner.poll_and_notify()
|
||||
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)
|
||||
status = 202 if spawner._stop_pending else 204
|
||||
self.set_status(status)
|
||||
|
@@ -400,11 +400,14 @@ class BaseHandler(RequestHandler):
|
||||
yield user.stop()
|
||||
|
||||
@gen.coroutine
|
||||
def stop_single_user(self, user):
|
||||
if user.spawner._stop_pending:
|
||||
raise RuntimeError("Stop already pending for: %s" % user.name)
|
||||
def stop_single_user(self, user, name=''):
|
||||
if name not in user.spawners:
|
||||
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()
|
||||
yield self.proxy.delete_user(user)
|
||||
yield self.proxy.delete_user(user, name)
|
||||
f = user.stop()
|
||||
@gen.coroutine
|
||||
def finish_stop(f=None):
|
||||
@@ -422,9 +425,9 @@ class BaseHandler(RequestHandler):
|
||||
try:
|
||||
yield gen.with_timeout(timedelta(seconds=self.slow_stop_timeout), f)
|
||||
except gen.TimeoutError:
|
||||
if user.spawner._stop_pending:
|
||||
if spawner._stop_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
|
||||
IOLoop.current().add_future(f, finish_stop)
|
||||
else:
|
||||
|
@@ -276,7 +276,7 @@ class Proxy(LoggingConfigurable):
|
||||
futures = []
|
||||
for orm_user in db.query(User):
|
||||
user = user_dict[orm_user]
|
||||
for name, spawner in user.spawners:
|
||||
for name, spawner in user.spawners.items():
|
||||
if user.running(name):
|
||||
futures.append(self.add_user(user, name))
|
||||
# wait after submitting them all
|
||||
|
@@ -229,7 +229,7 @@ def public_url(app, user_or_service=None, path=''):
|
||||
host = user_or_service.host
|
||||
else:
|
||||
host = public_host(app)
|
||||
prefix = user_or_service.server.base_url
|
||||
prefix = user_or_service.prefix
|
||||
else:
|
||||
host = public_host(app)
|
||||
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):
|
||||
return
|
||||
app.allow_named_servers = True
|
||||
username = 'user'
|
||||
servername = 'foo'
|
||||
from ..utils import url_path_join
|
||||
|
||||
from .test_api import api_request, add_user, find_user
|
||||
from .mocking import public_url
|
||||
|
||||
@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.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.base_url = url_path_join(
|
||||
self.base_url = self.prefix = url_path_join(
|
||||
self.settings.get('base_url', '/'), 'user', self.escaped_name) + '/'
|
||||
|
||||
self.spawners = _SpawnerDict(self._new_spawner)
|
||||
|
Reference in New Issue
Block a user