various cleanup to get most tests passing (yay!)

This commit is contained in:
Min RK
2017-05-04 12:16:47 +02:00
parent be8f847309
commit 5e55753baa
13 changed files with 49 additions and 40 deletions

View File

@@ -4,9 +4,10 @@
# W: style warnings # W: style warnings
# C: complexity # C: complexity
# F401: module imported but unused # F401: module imported but unused
# F403: import *
# F811: redefinition of unused `name` from line `N` # F811: redefinition of unused `name` from line `N`
# F841: local variable assigned but never used # F841: local variable assigned but never used
ignore = E, C, W, F401, F811, F841 ignore = E, C, W, F401, F403, F811, F841
exclude = exclude =
.cache, .cache,

View File

@@ -224,7 +224,7 @@ class UserCreateNamedServerAPIHandler(APIHandler):
def post(self, name): def post(self, name):
user = self.find_user(name) user = self.find_user(name)
if user is None: if user is None:
raise HTTPError(404, "No such user %r" % name) raise web.HTTPError(404, "No such user %r" % name)
if user.running: if user.running:
# include notify, so that a server that died is noticed immediately # include notify, so that a server that died is noticed immediately
state = yield user.spawner.poll_and_notify() state = yield user.spawner.poll_and_notify()

View File

@@ -1107,10 +1107,11 @@ class JupyterHub(Application):
def init_proxy(self): def init_proxy(self):
"""Load the Proxy config""" """Load the Proxy config"""
# FIXME: handle deprecated config here # FIXME: handle deprecated config here
public_url = 'http{s}://{ip}:{port}'.format( public_url = 'http{s}://{ip}:{port}{base_url}'.format(
s='s' if self.ssl_cert else '', s='s' if self.ssl_cert else '',
ip=self.ip, ip=self.ip,
port=self.port, port=self.port,
base_url=self.base_url,
) )
self.proxy = self.proxy_class( self.proxy = self.proxy_class(
db=self.db, db=self.db,
@@ -1309,21 +1310,22 @@ class JupyterHub(Application):
users_count = 0 users_count = 0
active_users_count = 0 active_users_count = 0
for prefix, route in routes.items(): for prefix, route in routes.items():
if 'user' not in route['data']: route_data = route['data']
if 'user' not in route_data:
# not a user route, ignore it # not a user route, ignore it
continue continue
users_count += 1 users_count += 1
if 'last_activity' not in route['data']: if 'last_activity' not in route_data:
# no last activity data (possibly proxy other than CHP) # no last activity data (possibly proxy other than CHP)
continue continue
user = orm.User.find(self.db, route['user']) user = orm.User.find(self.db, route_data['user'])
if user is None: if user is None:
self.log.warning("Found no user for route: %s", route) self.log.warning("Found no user for route: %s", route)
continue continue
try: try:
dt = datetime.strptime(route['last_activity'], ISO8601_ms) dt = datetime.strptime(route_data['last_activity'], ISO8601_ms)
except Exception: except Exception:
dt = datetime.strptime(route['last_activity'], ISO8601_s) dt = datetime.strptime(route_data['last_activity'], ISO8601_s)
user.last_activity = max(user.last_activity, dt) user.last_activity = max(user.last_activity, dt)
# FIXME: Make this configurable duration. 30 minutes for now! # FIXME: Make this configurable duration. 30 minutes for now!
if (datetime.now() - user.last_activity).total_seconds() < 30 * 60: if (datetime.now() - user.last_activity).total_seconds() < 30 * 60:

View File

@@ -89,5 +89,4 @@ def _alembic(*args):
if __name__ == '__main__': if __name__ == '__main__':
import sys
_alembic(*sys.argv[1:]) _alembic(*sys.argv[1:])

View File

@@ -17,8 +17,9 @@ from tornado.web import RequestHandler
from tornado import gen, web from tornado import gen, web
from .. import orm from .. import orm
from ..user import User from ..objects import Server
from ..spawner import LocalProcessSpawner from ..spawner import LocalProcessSpawner
from ..user import User
from ..utils import url_path_join from ..utils import url_path_join
# pattern for the authentication token header # pattern for the authentication token header

View File

@@ -43,7 +43,7 @@ class Server(HasTraits):
port = 443 port = 443
else: else:
port = 80 port = 80
return cls(proto=proto, ip=ip, port=port) return cls(proto=proto, ip=ip, port=port, base_url=urlinfo.path)
@default('port') @default('port')
def _default_port(self): def _default_port(self):

View File

@@ -148,16 +148,6 @@ class User(Base):
# group mapping # group mapping
groups = relationship('Group', secondary='user_group_map', back_populates='users') groups = relationship('Group', secondary='user_group_map', back_populates='users')
@property
def server(self):
"""Returns the first element of servers.
Returns None if the list is empty.
"""
if len(self.servers) == 0:
return None
else:
return self.servers[0]
def __repr__(self): def __repr__(self):
if self.server: if self.server:
return "<{cls}({name}@{ip}:{port})>".format( return "<{cls}({name}@{ip}:{port})>".format(

View File

@@ -365,11 +365,11 @@ class ConfigurableHTTPProxy(Proxy):
def api_request(self, path, method='GET', body=None, client=None): def api_request(self, path, method='GET', body=None, client=None):
"""Make an authenticated API request of the proxy.""" """Make an authenticated API request of the proxy."""
client = client or AsyncHTTPClient() client = client or AsyncHTTPClient()
url = url_path_join(self.api_url, path) url = url_path_join(self.api_url, 'api/routes', path)
if isinstance(body, dict): if isinstance(body, dict):
body = json.dumps(body) body = json.dumps(body)
self.log.debug("Fetching %s %s", method, url) self.log.debug("Proxy: Fetching %s %s", method, url)
req = HTTPRequest(url, req = HTTPRequest(url,
method=method, method=method,
headers={'Authorization': 'token {}'.format( headers={'Authorization': 'token {}'.format(

View File

@@ -52,6 +52,7 @@ from traitlets import (
from traitlets.config import LoggingConfigurable from traitlets.config import LoggingConfigurable
from .. import orm from .. import orm
from ..objects import Server
from ..traitlets import Command from ..traitlets import Command
from ..spawner import LocalProcessSpawner, set_user_setuid from ..spawner import LocalProcessSpawner, set_user_setuid
from ..utils import url_path_join from ..utils import url_path_join
@@ -60,7 +61,7 @@ class _MockUser(HasTraits):
name = Unicode() name = Unicode()
server = Instance(orm.Server, allow_none=True) server = Instance(orm.Server, allow_none=True)
state = Dict() state = Dict()
service = Instance(__module__ + '.Service') service = Instance(__name__ + '.Service')
host = Unicode() host = Unicode()
@property @property
@@ -221,7 +222,10 @@ class Service(LoggingConfigurable):
@property @property
def server(self): def server(self):
return self.orm.server if self.orm.server:
return Server(orm_server=self.orm.server)
else:
return None
@property @property
def prefix(self): def prefix(self):

View File

@@ -4,7 +4,6 @@ import os
import sys import sys
from tempfile import NamedTemporaryFile from tempfile import NamedTemporaryFile
import threading import threading
from unittest import mock from unittest import mock
import requests import requests
@@ -18,6 +17,7 @@ from traitlets import default
from ..app import JupyterHub from ..app import JupyterHub
from ..auth import PAMAuthenticator from ..auth import PAMAuthenticator
from .. import orm from .. import orm
from ..objects import Server
from ..spawner import LocalProcessSpawner from ..spawner import LocalProcessSpawner
from ..singleuser import SingleUserNotebookApp from ..singleuser import SingleUserNotebookApp
from ..utils import random_port, url_path_join from ..utils import random_port, url_path_join
@@ -207,7 +207,7 @@ def public_host(app):
if app.subdomain_host: if app.subdomain_host:
return app.subdomain_host return app.subdomain_host
else: else:
return urlparse(app.proxy.public_url).host return Server.from_url(app.proxy.public_url).host
def public_url(app, user_or_service=None, path=''): def public_url(app, user_or_service=None, path=''):
@@ -220,7 +220,7 @@ def public_url(app, user_or_service=None, path=''):
prefix = user_or_service.server.base_url prefix = user_or_service.server.base_url
else: else:
host = public_host(app) host = public_host(app)
prefix = app.proxy.public_server.base_url prefix = Server.from_url(app.proxy.public_url).base_url
if path: if path:
return host + url_path_join(prefix, path) return host + url_path_join(prefix, path)
else: else:

View File

@@ -7,6 +7,7 @@ import pytest
from tornado import gen from tornado import gen
from .. import orm from .. import orm
from .. import objects
from ..user import User from ..user import User
from .mocking import MockSpawner from .mocking import MockSpawner
@@ -20,6 +21,9 @@ def test_server(db):
assert server.proto == 'http' assert server.proto == 'http'
assert isinstance(server.port, int) assert isinstance(server.port, int)
assert isinstance(server.cookie_name, str) assert isinstance(server.cookie_name, str)
# test wrapper
server = objects.Server(orm_server=server)
assert server.host == 'http://127.0.0.1:%i' % server.port assert server.host == 'http://127.0.0.1:%i' % server.port
assert server.url == server.host + '/' assert server.url == server.host + '/'
assert server.bind_url == 'http://*:%i/' % server.port assert server.bind_url == 'http://*:%i/' % server.port
@@ -29,9 +33,9 @@ def test_server(db):
def test_user(db): def test_user(db):
user = orm.User(name='kaylee', user = User(orm.User(name='kaylee',
state={'pid': 4234}, state={'pid': 4234},
) ))
server = orm.Server() server = orm.Server()
user.servers.append(server) user.servers.append(server)
db.add(user) db.add(user)

View File

@@ -17,6 +17,7 @@ import requests
from tornado import gen from tornado import gen
from ..user import User from ..user import User
from ..objects import Hub
from .. import spawner as spawnermod from .. import spawner as spawnermod
from ..spawner import LocalProcessSpawner from ..spawner import LocalProcessSpawner
from .. import orm from .. import orm
@@ -43,8 +44,8 @@ def setup():
def new_spawner(db, **kwargs): def new_spawner(db, **kwargs):
kwargs.setdefault('cmd', [sys.executable, '-c', _echo_sleep]) kwargs.setdefault('cmd', [sys.executable, '-c', _echo_sleep])
kwargs.setdefault('hub', Hub())
kwargs.setdefault('user', User(db.query(orm.User).first(), {})) kwargs.setdefault('user', User(db.query(orm.User).first(), {}))
kwargs.setdefault('hub', db.query(orm.Hub).first())
kwargs.setdefault('notebook_dir', os.getcwd()) kwargs.setdefault('notebook_dir', os.getcwd())
kwargs.setdefault('default_url', '/user/{username}/lab') kwargs.setdefault('default_url', '/user/{username}/lab')
kwargs.setdefault('INTERRUPT_TIMEOUT', 1) kwargs.setdefault('INTERRUPT_TIMEOUT', 1)

View File

@@ -9,9 +9,10 @@ from sqlalchemy import inspect
from tornado import gen from tornado import gen
from tornado.log import app_log from tornado.log import app_log
from .utils import url_path_join, default_server_name, new_token from .utils import url_path_join, default_server_name
from . import orm from . import orm
from .objects import Server
from traitlets import HasTraits, Any, Dict, observe, default from traitlets import HasTraits, Any, Dict, observe, default
from .spawner import LocalProcessSpawner from .spawner import LocalProcessSpawner
@@ -157,6 +158,13 @@ class User(HasTraits):
if self.server is None: if self.server is None:
return False return False
return True return True
@property
def server(self):
if len(self.servers) == 0:
return None
else:
return Server(orm_server=self.servers[0])
@property @property
def escaped_name(self): def escaped_name(self):
@@ -228,14 +236,13 @@ class User(HasTraits):
cookie_name=self.cookie_name, cookie_name=self.cookie_name,
base_url=base_url, base_url=base_url,
) )
db.add(orm_server) self.servers.append(orm_server)
db.commit()
server = Server(orm_server=orm_server)
self.servers.append(server)
api_token = self.new_api_token() api_token = self.new_api_token()
db.commit() db.commit()
server = Server(orm_server=orm_server)
spawner = self.spawner spawner = self.spawner
# Passing server_name to the spawner # Passing server_name to the spawner
spawner.server_name = server_name spawner.server_name = server_name
@@ -279,7 +286,7 @@ class User(HasTraits):
ip_port = yield gen.with_timeout(timedelta(seconds=spawner.start_timeout), f) ip_port = yield gen.with_timeout(timedelta(seconds=spawner.start_timeout), f)
if ip_port: if ip_port:
# get ip, port info from return value of start() # get ip, port info from return value of start()
self.server.ip, self.server.port = ip_port server.ip, server.port = ip_port
else: else:
# prior to 0.7, spawners had to store this info in user.server themselves. # prior to 0.7, spawners had to store this info in user.server themselves.
# Handle < 0.7 behavior with a warning, assuming info was stored in db by the Spawner. # Handle < 0.7 behavior with a warning, assuming info was stored in db by the Spawner.
@@ -317,14 +324,14 @@ class User(HasTraits):
db.commit() db.commit()
self.waiting_for_response = True self.waiting_for_response = True
try: try:
yield self.server.wait_up(http=True, timeout=spawner.http_timeout) yield server.wait_up(http=True, timeout=spawner.http_timeout)
except Exception as e: except Exception as e:
if isinstance(e, TimeoutError): if isinstance(e, TimeoutError):
self.log.warning( self.log.warning(
"{user}'s server never showed up at {url} " "{user}'s server never showed up at {url} "
"after {http_timeout} seconds. Giving up".format( "after {http_timeout} seconds. Giving up".format(
user=self.name, user=self.name,
url=self.server.url, url=server.url,
http_timeout=spawner.http_timeout, http_timeout=spawner.http_timeout,
) )
) )
@@ -332,7 +339,7 @@ class User(HasTraits):
else: else:
e.reason = 'error' e.reason = 'error'
self.log.error("Unhandled error waiting for {user}'s server to show up at {url}: {error}".format( self.log.error("Unhandled error waiting for {user}'s server to show up at {url}: {error}".format(
user=self.name, url=self.server.url, error=e, user=self.name, url=server.url, error=e,
)) ))
try: try:
yield self.stop() yield self.stop()