mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-14 21:43:01 +00:00
further clear up named servers
- use spawner.server instead of user.server - user.running, proxy_spec are methods that take spawner names
This commit is contained in:
@@ -9,6 +9,7 @@ import binascii
|
||||
from datetime import datetime
|
||||
from getpass import getuser
|
||||
import logging
|
||||
from operator import itemgetter
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
@@ -1116,8 +1117,9 @@ class JupyterHub(Application):
|
||||
parts = ['{0: >8}'.format(user.name)]
|
||||
if user.admin:
|
||||
parts.append('admin')
|
||||
if user.server:
|
||||
parts.append('running at %s' % user.server)
|
||||
for name, spawner in sorted(user.spawners.items(), key=itemgetter(0)):
|
||||
if spawner.server:
|
||||
parts.append('%r running at %s' % (name, spawner.server))
|
||||
return ' '.join(parts)
|
||||
|
||||
@gen.coroutine
|
||||
@@ -1149,7 +1151,7 @@ class JupyterHub(Application):
|
||||
else:
|
||||
# user not running. This is expected if server is None,
|
||||
# but indicates the user's server died while the Hub wasn't running
|
||||
# if user.server is defined.
|
||||
# if spawner.server is defined.
|
||||
log = self.log.warning if spawner.server else self.log.debug
|
||||
log("%s not running.", user.name)
|
||||
# remove all server or servers entry from db related to the user
|
||||
@@ -1319,6 +1321,7 @@ class JupyterHub(Application):
|
||||
self.log.info("Cleaning up single-user servers...")
|
||||
# request (async) process termination
|
||||
for uid, user in self.users.items():
|
||||
user.db = self.db
|
||||
if user.spawner is not None:
|
||||
futures.append(user.stop())
|
||||
else:
|
||||
|
@@ -57,6 +57,10 @@ class BaseHandler(RequestHandler):
|
||||
def subdomain_host(self):
|
||||
return self.settings.get('subdomain_host', '')
|
||||
|
||||
@property
|
||||
def allow_named_servers(self):
|
||||
return self.settings.get('allow_named_servers', False)
|
||||
|
||||
@property
|
||||
def domain(self):
|
||||
return self.settings['domain']
|
||||
@@ -360,7 +364,7 @@ class BaseHandler(RequestHandler):
|
||||
# though it's possible that it started at the wrong URL
|
||||
self.log.warning("User %s's server is slow to become responsive (timeout=%s)",
|
||||
user.name, self.slow_spawn_timeout)
|
||||
self.log.debug("Expecting server for %s at: %s", user.name, user.server.url)
|
||||
self.log.debug("Expecting server for %s at: %s", user.name, spawner.server.url)
|
||||
# schedule finish for when the user finishes spawning
|
||||
IOLoop.current().add_future(f, finish_user_spawn)
|
||||
else:
|
||||
|
@@ -182,7 +182,7 @@ class AdminHandler(BaseHandler):
|
||||
|
||||
users = self.db.query(orm.User).order_by(*ordered)
|
||||
users = [ self._user_from_orm(u) for u in users ]
|
||||
running = [ u for u in users if u.running ]
|
||||
running = [ u for u in users if u.running('') ]
|
||||
|
||||
html = self.render_template('admin.html',
|
||||
user=self.get_current_user(),
|
||||
|
@@ -26,6 +26,7 @@ from traitlets import (
|
||||
validate,
|
||||
)
|
||||
|
||||
from .objects import Server
|
||||
from .traitlets import Command, ByteSpecification
|
||||
from .utils import random_port, url_path_join
|
||||
|
||||
@@ -52,11 +53,19 @@ class Spawner(LoggingConfigurable):
|
||||
_waiting_for_response = False
|
||||
|
||||
orm_spawner = Any()
|
||||
db = Any()
|
||||
user = Any()
|
||||
hub = Any()
|
||||
authenticator = Any()
|
||||
server = Any()
|
||||
orm_spawner = Any()
|
||||
@property
|
||||
def server(self):
|
||||
if self.orm_spawner and self.orm_spawner.server:
|
||||
return Server(orm_server=self.orm_spawner.server)
|
||||
@property
|
||||
def name(self):
|
||||
if self.orm_spawner:
|
||||
return self.orm_spawner.name
|
||||
return ''
|
||||
admin_access = Bool(False)
|
||||
api_token = Unicode()
|
||||
oauth_client_id = Unicode()
|
||||
@@ -436,7 +445,7 @@ class Spawner(LoggingConfigurable):
|
||||
env['JUPYTERHUB_CLIENT_ID'] = self.oauth_client_id
|
||||
env['JUPYTERHUB_HOST'] = self.hub.public_host
|
||||
env['JUPYTERHUB_OAUTH_CALLBACK_URL'] = \
|
||||
url_path_join(self.user.url, 'oauth_callback')
|
||||
url_path_join(self.user.url, self.name, 'oauth_callback')
|
||||
|
||||
# Info previously passed on args
|
||||
env['JUPYTERHUB_USER'] = self.user.name
|
||||
|
@@ -157,6 +157,9 @@ class MockHub(JupyterHub):
|
||||
def init_signal(self):
|
||||
pass
|
||||
|
||||
def load_config_file(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def start(self, argv=None):
|
||||
self.db_file = NamedTemporaryFile()
|
||||
self.pid_file = NamedTemporaryFile(delete=False).name
|
||||
@@ -172,8 +175,6 @@ class MockHub(JupyterHub):
|
||||
# add an initial user
|
||||
user = orm.User(name='user')
|
||||
self.db.add(user)
|
||||
admin = orm.User(name='admin', admin=True)
|
||||
self.db.add(admin)
|
||||
self.db.commit()
|
||||
yield super(MockHub, self).start()
|
||||
yield self.hub.wait_up(http=True)
|
||||
|
@@ -408,16 +408,17 @@ def test_spawn(app, io_loop):
|
||||
assert 'pid' in user.orm_spawners[''].state
|
||||
app_user = get_app_user(app, name)
|
||||
assert app_user.spawner is not None
|
||||
spawner = app_user.spawner
|
||||
assert app_user.spawner.user_options == options
|
||||
assert not app_user.spawner._spawn_pending
|
||||
status = io_loop.run_sync(app_user.spawner.poll)
|
||||
assert status is None
|
||||
|
||||
assert user.server.base_url == ujoin(app.base_url, 'user/%s' % name) + '/'
|
||||
assert spawner.server.base_url == ujoin(app.base_url, 'user/%s' % name) + '/'
|
||||
url = public_url(app, user)
|
||||
r = requests.get(url)
|
||||
assert r.status_code == 200
|
||||
assert r.text == user.server.base_url
|
||||
assert r.text == spawner.server.base_url
|
||||
|
||||
r = requests.get(ujoin(url, 'args'))
|
||||
assert r.status_code == 200
|
||||
@@ -438,7 +439,7 @@ def test_spawn(app, io_loop):
|
||||
assert status == 0
|
||||
|
||||
# check that we cleaned up after ourselves
|
||||
assert user.server is None
|
||||
assert spawner.server is None
|
||||
after_servers = sorted(db.query(orm.Server), key=lambda s: s.url)
|
||||
assert before_servers == after_servers
|
||||
tokens = list(db.query(orm.APIToken).filter(orm.APIToken.user_id == user.id))
|
||||
|
10
jupyterhub/tests/test_named_servers.py
Normal file
10
jupyterhub/tests/test_named_servers.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from .test_api import api_request, add_user
|
||||
|
||||
def test_create_named_server(app):
|
||||
return
|
||||
app.allow_named_servers = True
|
||||
username = 'user'
|
||||
servername = 'foo'
|
||||
r = api_request(app, 'users', username, 'servers', servername, method='post')
|
||||
r.raise_for_status()
|
||||
|
@@ -39,16 +39,13 @@ def test_server(db):
|
||||
|
||||
|
||||
def test_user(db):
|
||||
user = User(orm.User(name='kaylee',
|
||||
state={'pid': 4234},
|
||||
))
|
||||
server = orm.Server()
|
||||
user.servers.append(server)
|
||||
user = User(orm.User(name='kaylee'))
|
||||
db.add(user)
|
||||
db.commit()
|
||||
spawner = user.spawners['']
|
||||
spawner.orm_spawner.state = {'pid': 4234}
|
||||
assert user.name == 'kaylee'
|
||||
assert user.server.ip == ''
|
||||
assert user.state == {'pid': 4234}
|
||||
assert user.orm_spawners[''].state == {'pid': 4234}
|
||||
|
||||
found = orm.User.find(db, 'kaylee')
|
||||
assert found.name == user.name
|
||||
@@ -160,7 +157,7 @@ def test_spawn_fails(db, io_loop):
|
||||
|
||||
with pytest.raises(RuntimeError) as exc:
|
||||
io_loop.run_sync(user.spawn)
|
||||
assert user.server is None
|
||||
assert user.spawners[''].server is None
|
||||
assert not user.running('')
|
||||
|
||||
|
||||
|
@@ -158,18 +158,18 @@ def test_check_routes(app, io_loop, username, endpoints):
|
||||
# check a valid route exists for user
|
||||
test_user = app.users[username]
|
||||
before = sorted(io_loop.run_sync(app.proxy.get_all_routes))
|
||||
assert test_user.proxy_spec in before
|
||||
assert test_user.proxy_spec() in before
|
||||
|
||||
# check if a route is removed when user deleted
|
||||
io_loop.run_sync(lambda: app.proxy.check_routes(app.users, app._service_map))
|
||||
io_loop.run_sync(lambda: proxy.delete_user(test_user))
|
||||
during = sorted(io_loop.run_sync(app.proxy.get_all_routes))
|
||||
assert test_user.proxy_spec not in during
|
||||
assert test_user.proxy_spec() not in during
|
||||
|
||||
# check if a route exists for user
|
||||
io_loop.run_sync(lambda: app.proxy.check_routes(app.users, app._service_map))
|
||||
after = sorted(io_loop.run_sync(app.proxy.get_all_routes))
|
||||
assert test_user.proxy_spec in after
|
||||
assert test_user.proxy_spec() in after
|
||||
|
||||
# check that before and after state are the same
|
||||
assert before == after
|
||||
|
@@ -43,9 +43,9 @@ def setup():
|
||||
|
||||
|
||||
def new_spawner(db, **kwargs):
|
||||
user = kwargs.setdefault('user', User(db.query(orm.User).first(), {}))
|
||||
kwargs.setdefault('cmd', [sys.executable, '-c', _echo_sleep])
|
||||
kwargs.setdefault('hub', Hub())
|
||||
kwargs.setdefault('user', User(db.query(orm.User).first(), {}))
|
||||
kwargs.setdefault('notebook_dir', os.getcwd())
|
||||
kwargs.setdefault('default_url', '/user/{username}/lab')
|
||||
kwargs.setdefault('oauth_client_id', 'mock-client-id')
|
||||
@@ -53,7 +53,7 @@ def new_spawner(db, **kwargs):
|
||||
kwargs.setdefault('TERM_TIMEOUT', 1)
|
||||
kwargs.setdefault('KILL_TIMEOUT', 1)
|
||||
kwargs.setdefault('poll_interval', 1)
|
||||
return LocalProcessSpawner(db=db, **kwargs)
|
||||
return user._new_spawner('', spawner_class=LocalProcessSpawner, **kwargs)
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
@@ -63,8 +63,6 @@ def test_spawner(db, request):
|
||||
assert ip == '127.0.0.1'
|
||||
assert isinstance(port, int)
|
||||
assert port > 0
|
||||
spawner.user.server.ip = ip
|
||||
spawner.user.server.port = port
|
||||
db.commit()
|
||||
|
||||
# wait for the process to get to the while True: loop
|
||||
@@ -85,7 +83,7 @@ def wait_for_spawner(spawner, timeout=10):
|
||||
"""
|
||||
deadline = time.monotonic() + timeout
|
||||
def wait():
|
||||
return spawner.user.server.wait_up(timeout=1, http=True)
|
||||
return spawner.server.wait_up(timeout=1, http=True)
|
||||
while time.monotonic() < deadline:
|
||||
status = yield spawner.poll()
|
||||
assert status is None
|
||||
@@ -104,8 +102,8 @@ def test_single_user_spawner(app, request):
|
||||
spawner = user.spawner
|
||||
spawner.cmd = ['jupyterhub-singleuser']
|
||||
yield user.spawn()
|
||||
assert user.server.ip == '127.0.0.1'
|
||||
assert user.server.port > 0
|
||||
assert spawner.server.ip == '127.0.0.1'
|
||||
assert spawner.server.port > 0
|
||||
yield wait_for_spawner(spawner)
|
||||
status = yield spawner.poll()
|
||||
assert status is None
|
||||
@@ -233,10 +231,12 @@ def test_shell_cmd(db, tmpdir, request):
|
||||
cmd=[sys.executable, '-m', 'jupyterhub.tests.mocksu'],
|
||||
shell_cmd=['bash', '--rcfile', str(f), '-i', '-c'],
|
||||
)
|
||||
s.orm_spawner.server = orm.Server()
|
||||
db.commit()
|
||||
(ip, port) = yield s.start()
|
||||
request.addfinalizer(s.stop)
|
||||
s.user.server.ip = ip
|
||||
s.user.server.port = port
|
||||
s.server.ip = ip
|
||||
s.server.port = port
|
||||
db.commit()
|
||||
yield wait_for_spawner(s)
|
||||
r = requests.get('http://%s:%i/env' % (ip, port))
|
||||
|
@@ -13,7 +13,6 @@ from .utils import url_path_join, default_server_name
|
||||
|
||||
from . import orm
|
||||
from ._version import _check_version, __version__
|
||||
from .objects import Server
|
||||
from traitlets import HasTraits, Any, Dict, observe, default
|
||||
from .spawner import LocalProcessSpawner
|
||||
|
||||
@@ -105,12 +104,8 @@ class User(HasTraits):
|
||||
db = change.new
|
||||
if self._user_id is not None:
|
||||
self.orm_user = db.query(orm.User).filter(orm.User.id == self._user_id).first()
|
||||
for spawner in self.spawners.values():
|
||||
spawner.db = db
|
||||
if (spawner.server):
|
||||
orm_server = spawner.server.orm_server
|
||||
inspect(orm_server).session.expunge(orm_server)
|
||||
db.add(orm_server)
|
||||
for name, spawner in self.spawners.items():
|
||||
spawner.orm_spawner = self.orm_user.orm_spawners[name]
|
||||
|
||||
_user_id = None
|
||||
orm_user = Any(allow_none=True)
|
||||
@@ -151,9 +146,11 @@ class User(HasTraits):
|
||||
for name in self.orm_spawners:
|
||||
self.spawners[name] = self._new_spawner(name)
|
||||
|
||||
def _new_spawner(self, name):
|
||||
def _new_spawner(self, name, spawner_class=None, **kwargs):
|
||||
"""Create a new spawner"""
|
||||
self.log.debug("Creating Spawner for %s:%s", self.name, name)
|
||||
if spawner_class is None:
|
||||
spawner_class = self.spawner_class
|
||||
self.log.debug("Creating %s for %s:%s", spawner_class, self.name, name)
|
||||
|
||||
orm_spawner = self.orm_spawners.get(name)
|
||||
if orm_spawner is None:
|
||||
@@ -165,16 +162,17 @@ class User(HasTraits):
|
||||
# migrate user.state to spawner.state
|
||||
orm_spawner.state = self.state
|
||||
self.state = None
|
||||
spawner = self.spawner_class(
|
||||
|
||||
spawn_kwargs = dict(
|
||||
user=self,
|
||||
db=self.db,
|
||||
orm_spawner=orm_spawner,
|
||||
hub=self.settings.get('hub'),
|
||||
authenticator=self.authenticator,
|
||||
config=self.settings.get('config'),
|
||||
)
|
||||
if orm_spawner.server:
|
||||
spawner.server = Server(orm_spawner.server)
|
||||
# update with kwargs. Mainly for testing.
|
||||
spawn_kwargs.update(kwargs)
|
||||
spawner = spawner_class(**spawn_kwargs)
|
||||
spawner.load_state(orm_spawner.state or {})
|
||||
return spawner
|
||||
|
||||
@@ -226,9 +224,9 @@ class User(HasTraits):
|
||||
|
||||
def proxy_spec(self, name=''):
|
||||
if self.settings.get('subdomain_host'):
|
||||
return url_path_join(self.domain, self.base_url, name)
|
||||
return url_path_join(self.domain, self.base_url, name, '/')
|
||||
else:
|
||||
return url_path_join(self.base_url, name)
|
||||
return url_path_join(self.base_url, name, '/')
|
||||
|
||||
@property
|
||||
def domain(self):
|
||||
@@ -286,13 +284,12 @@ class User(HasTraits):
|
||||
api_token = self.new_api_token()
|
||||
db.commit()
|
||||
|
||||
server = Server(orm_server=orm_server)
|
||||
|
||||
spawner = self.spawners[server_name]
|
||||
spawner.orm_spawner.server = orm_server
|
||||
server = spawner.server
|
||||
|
||||
# Passing server, server_name and options to the spawner
|
||||
spawner.server = server
|
||||
# Passing user_options to the spawner
|
||||
spawner.user_options = options or {}
|
||||
# we are starting a new server, make sure it doesn't restore state
|
||||
spawner.clear_state()
|
||||
@@ -426,8 +423,8 @@ class User(HasTraits):
|
||||
self.last_activity = datetime.utcnow()
|
||||
# remove server entry from db
|
||||
if spawner.server is not None:
|
||||
self.db.delete(spawner.server.orm_server)
|
||||
spawner.server = None
|
||||
self.db.delete(spawner.orm_spawner.server)
|
||||
spawner.orm_spawner.server = None
|
||||
if not spawner.will_resume:
|
||||
# find and remove the API token if the spawner isn't
|
||||
# going to re-use it next time
|
||||
|
Reference in New Issue
Block a user