mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-13 04:53:01 +00:00
remove Hub, Proxy from database
These are in-memory-only objects, no need for a table with one row
This commit is contained in:
@@ -32,7 +32,7 @@ class APIHandler(BaseHandler):
|
||||
self.log.warning("Blocking API request with no referer")
|
||||
return False
|
||||
|
||||
host_path = url_path_join(host, self.hub.server.base_url)
|
||||
host_path = url_path_join(host, self.hub.base_url)
|
||||
referer_path = referer.split('://', 1)[-1]
|
||||
if not (referer_path + '/').startswith(host_path):
|
||||
self.log.warning("Blocking Cross Origin API request. Referer: %s, Host: %s",
|
||||
|
@@ -62,6 +62,7 @@ from .utils import (
|
||||
# classes for config
|
||||
from .auth import Authenticator, PAMAuthenticator
|
||||
from .spawner import Spawner, LocalProcessSpawner
|
||||
from .objects import Server, Hub
|
||||
|
||||
# For faking stats
|
||||
from .emptyclass import EmptyClass
|
||||
@@ -805,36 +806,6 @@ class JupyterHub(Application):
|
||||
self._local.db = scoped_session(self.session_factory)()
|
||||
return self._local.db
|
||||
|
||||
@property
|
||||
def hub(self):
|
||||
if not getattr(self._local, 'hub', None):
|
||||
q = self.db.query(orm.Hub)
|
||||
assert q.count() <= 1
|
||||
self._local.hub = q.first()
|
||||
if self.subdomain_host and self._local.hub:
|
||||
self._local.hub.host = self.subdomain_host
|
||||
return self._local.hub
|
||||
|
||||
@hub.setter
|
||||
def hub(self, hub):
|
||||
self._local.hub = hub
|
||||
if hub and self.subdomain_host:
|
||||
hub.host = self.subdomain_host
|
||||
|
||||
@property
|
||||
def proxy(self):
|
||||
if not getattr(self._local, 'proxy', None):
|
||||
q = self.db.query(orm.Proxy)
|
||||
assert q.count() <= 1
|
||||
p = self._local.proxy = q.first()
|
||||
if p:
|
||||
p.auth_token = self.proxy_auth_token
|
||||
return self._local.proxy
|
||||
|
||||
@proxy.setter
|
||||
def proxy(self, proxy):
|
||||
self._local.proxy = proxy
|
||||
|
||||
def init_db(self):
|
||||
"""Create the database connection"""
|
||||
self.log.debug("Connecting to db: %s", self.db_url)
|
||||
@@ -861,28 +832,13 @@ class JupyterHub(Application):
|
||||
|
||||
def init_hub(self):
|
||||
"""Load the Hub config into the database"""
|
||||
self.hub = self.db.query(orm.Hub).first()
|
||||
if self.hub is None:
|
||||
self.hub = orm.Hub(
|
||||
server=orm.Server(
|
||||
self.hub = Hub(
|
||||
ip=self.hub_ip,
|
||||
port=self.hub_port,
|
||||
base_url=self.hub_prefix,
|
||||
cookie_name='jupyter-hub-token',
|
||||
public_host=self.subdomain_host,
|
||||
)
|
||||
)
|
||||
self.db.add(self.hub)
|
||||
else:
|
||||
server = self.hub.server
|
||||
server.ip = self.hub_ip
|
||||
server.port = self.hub_port
|
||||
server.base_url = self.hub_prefix
|
||||
if self.subdomain_host:
|
||||
if not self.subdomain_host:
|
||||
raise ValueError("Must specify subdomain_host when using subdomains."
|
||||
" This should be the public domain[:port] of the Hub.")
|
||||
|
||||
self.db.commit()
|
||||
|
||||
@gen.coroutine
|
||||
def init_users(self):
|
||||
@@ -1231,8 +1187,8 @@ class JupyterHub(Application):
|
||||
'--port', str(self.proxy.public_server.port),
|
||||
'--api-ip', self.proxy.api_server.ip,
|
||||
'--api-port', str(self.proxy.api_server.port),
|
||||
'--default-target', self.hub.server.host,
|
||||
'--error-target', url_path_join(self.hub.server.url, 'error'),
|
||||
'--default-target', self.hub.host,
|
||||
'--error-target', url_path_join(self.hub.url, 'error'),
|
||||
]
|
||||
if self.subdomain_host:
|
||||
cmd.append('--host-routing')
|
||||
@@ -1299,7 +1255,7 @@ class JupyterHub(Application):
|
||||
|
||||
def init_tornado_settings(self):
|
||||
"""Set up the tornado settings dict."""
|
||||
base_url = self.hub.server.base_url
|
||||
base_url = self.hub.base_url
|
||||
jinja_options = dict(
|
||||
autoescape=True,
|
||||
)
|
||||
@@ -1337,7 +1293,7 @@ class JupyterHub(Application):
|
||||
login_url=login_url,
|
||||
logout_url=logout_url,
|
||||
static_path=os.path.join(self.data_files_path, 'static'),
|
||||
static_url_prefix=url_path_join(self.hub.server.base_url, 'static/'),
|
||||
static_url_prefix=url_path_join(self.hub.base_url, 'static/'),
|
||||
static_handler_class=CacheControlStaticFilesHandler,
|
||||
template_path=self.template_paths,
|
||||
jinja2_env=jinja_env,
|
||||
@@ -1530,10 +1486,10 @@ class JupyterHub(Application):
|
||||
try:
|
||||
self.http_server.listen(self.hub_port, address=self.hub_ip)
|
||||
except Exception:
|
||||
self.log.error("Failed to bind hub to %s", self.hub.server.bind_url)
|
||||
self.log.error("Failed to bind hub to %s", self.hub.bind_url)
|
||||
raise
|
||||
else:
|
||||
self.log.info("Hub API listening on %s", self.hub.server.bind_url)
|
||||
self.log.info("Hub API listening on %s", self.hub.bind_url)
|
||||
|
||||
# start the proxy
|
||||
try:
|
||||
|
@@ -103,7 +103,7 @@ class BaseHandler(RequestHandler):
|
||||
@property
|
||||
def csp_report_uri(self):
|
||||
return self.settings.get('csp_report_uri',
|
||||
url_path_join(self.hub.server.base_url, 'security/csp-report')
|
||||
url_path_join(self.hub.base_url, 'security/csp-report')
|
||||
)
|
||||
|
||||
@property
|
||||
@@ -184,7 +184,7 @@ class BaseHandler(RequestHandler):
|
||||
max_age_days=self.cookie_max_age_days,
|
||||
)
|
||||
def clear():
|
||||
self.clear_cookie(cookie_name, path=self.hub.server.base_url)
|
||||
self.clear_cookie(cookie_name, path=self.hub.base_url)
|
||||
|
||||
if cookie_id is None:
|
||||
if self.get_cookie(cookie_name):
|
||||
@@ -208,7 +208,7 @@ class BaseHandler(RequestHandler):
|
||||
|
||||
def get_current_user_cookie(self):
|
||||
"""get_current_user from a cookie token"""
|
||||
return self._user_for_cookie(self.hub.server.cookie_name)
|
||||
return self._user_for_cookie(self.hub.cookie_name)
|
||||
|
||||
def get_current_user(self):
|
||||
"""get current username"""
|
||||
@@ -247,7 +247,7 @@ class BaseHandler(RequestHandler):
|
||||
kwargs['domain'] = self.domain
|
||||
if user and user.server:
|
||||
self.clear_cookie(user.server.cookie_name, path=user.server.base_url, **kwargs)
|
||||
self.clear_cookie(self.hub.server.cookie_name, path=self.hub.server.base_url, **kwargs)
|
||||
self.clear_cookie(self.hub.cookie_name, path=self.hub.base_url, **kwargs)
|
||||
self.clear_cookie('jupyterhub-services', path=url_path_join(self.base_url, 'services'))
|
||||
|
||||
def _set_user_cookie(self, user, server):
|
||||
@@ -436,7 +436,7 @@ class BaseHandler(RequestHandler):
|
||||
def template_namespace(self):
|
||||
user = self.get_current_user()
|
||||
return dict(
|
||||
base_url=self.hub.server.base_url,
|
||||
base_url=self.hub.base_url,
|
||||
prefix=self.base_url,
|
||||
user=user,
|
||||
login_url=self.settings['login_url'],
|
||||
@@ -502,7 +502,7 @@ class PrefixRedirectHandler(BaseHandler):
|
||||
else:
|
||||
path = self.request.path
|
||||
self.redirect(url_path_join(
|
||||
self.hub.server.base_url, path,
|
||||
self.hub.base_url, path,
|
||||
), permanent=False)
|
||||
|
||||
|
||||
@@ -528,7 +528,7 @@ class UserSpawnHandler(BaseHandler):
|
||||
port = host_info.port
|
||||
if not port:
|
||||
port = 443 if host_info.scheme == 'https' else 80
|
||||
if port != self.proxy.public_server.port and port == self.hub.server.port:
|
||||
if port != self.proxy.public_server.port and port == self.hub.port:
|
||||
self.log.warning("""
|
||||
Detected possible direct connection to Hub's private ip: %s, bypassing proxy.
|
||||
This will result in a redirect loop.
|
||||
@@ -548,14 +548,14 @@ class UserSpawnHandler(BaseHandler):
|
||||
status = yield current_user.spawner.poll()
|
||||
if status is not None:
|
||||
if current_user.spawner.options_form:
|
||||
self.redirect(url_concat(url_path_join(self.hub.server.base_url, 'spawn'),
|
||||
self.redirect(url_concat(url_path_join(self.hub.base_url, 'spawn'),
|
||||
{'next': self.request.uri}))
|
||||
return
|
||||
else:
|
||||
yield self.spawn_single_user(current_user)
|
||||
# set login cookie anew
|
||||
self.set_login_cookie(current_user)
|
||||
without_prefix = self.request.uri[len(self.hub.server.base_url):]
|
||||
without_prefix = self.request.uri[len(self.hub.base_url):]
|
||||
target = url_path_join(self.base_url, without_prefix)
|
||||
if self.subdomain_host:
|
||||
target = current_user.host + target
|
||||
|
@@ -54,7 +54,7 @@ class LoginHandler(BaseHandler):
|
||||
if user.running:
|
||||
next_url = user.url
|
||||
else:
|
||||
next_url = self.hub.server.base_url
|
||||
next_url = self.hub.base_url
|
||||
# set new login cookie
|
||||
# because single-user cookie may have been cleared or incorrect
|
||||
self.set_login_cookie(self.get_current_user())
|
||||
@@ -98,7 +98,7 @@ class LoginHandler(BaseHandler):
|
||||
next_url = self.get_argument('next', default='')
|
||||
if not next_url.startswith('/'):
|
||||
next_url = ''
|
||||
next_url = next_url or self.hub.server.base_url
|
||||
next_url = next_url or self.hub.base_url
|
||||
self.redirect(next_url)
|
||||
self.log.info("User logged in: %s", username)
|
||||
else:
|
||||
|
@@ -37,7 +37,7 @@ class RootHandler(BaseHandler):
|
||||
# The next request will be handled by UserSpawnHandler,
|
||||
# ultimately redirecting to the logged-in user's server.
|
||||
without_prefix = next_url[len(self.base_url):]
|
||||
next_url = url_path_join(self.hub.server.base_url, without_prefix)
|
||||
next_url = url_path_join(self.hub.base_url, without_prefix)
|
||||
self.log.warning("Redirecting %s to %s. For sharing public links, use /user-redirect/",
|
||||
self.request.uri, next_url,
|
||||
)
|
||||
@@ -50,7 +50,7 @@ class RootHandler(BaseHandler):
|
||||
self.log.debug("User is running: %s", url)
|
||||
self.set_login_cookie(user) # set cookie
|
||||
else:
|
||||
url = url_path_join(self.hub.server.base_url, 'home')
|
||||
url = url_path_join(self.hub.base_url, 'home')
|
||||
self.log.debug("User is not running: %s", url)
|
||||
else:
|
||||
url = self.settings['login_url']
|
||||
@@ -215,7 +215,7 @@ class ProxyErrorHandler(BaseHandler):
|
||||
status_message = responses.get(status_code, 'Unknown HTTP Error')
|
||||
# build template namespace
|
||||
|
||||
hub_home = url_path_join(self.hub.server.base_url, 'home')
|
||||
hub_home = url_path_join(self.hub.base_url, 'home')
|
||||
message_html = ''
|
||||
if status_code == 503:
|
||||
message_html = ' '.join([
|
||||
|
126
jupyterhub/objects.py
Normal file
126
jupyterhub/objects.py
Normal file
@@ -0,0 +1,126 @@
|
||||
"""Some general objects for use in JupyterHub"""
|
||||
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from traitlets import HasTraits
|
||||
from . import orm
|
||||
|
||||
|
||||
class Server(HasTraits):
|
||||
"""An object representing an HTTP endpoint.
|
||||
|
||||
*Some* of these reside in the database (user servers),
|
||||
but others (Hub, proxy) are in-memory only.
|
||||
"""
|
||||
orm_server = Instance(orm.Server, allow_none=True)
|
||||
|
||||
ip = Unicode()
|
||||
proto = Unicode('http')
|
||||
port = Integer()
|
||||
base_url = Unicode('/')
|
||||
cookie_name = Unicode('')
|
||||
|
||||
@classmethod
|
||||
def from_url(cls, url):
|
||||
"""Create a Server from a given URL"""
|
||||
urlinfo = urlparse(url)
|
||||
proto = urlinfo.scheme
|
||||
ip = urlinfo.hostname
|
||||
port = urlinfo.port
|
||||
if not port:
|
||||
if proto == 'https':
|
||||
port = 443
|
||||
else:
|
||||
port = 80
|
||||
return cls(proto=proto, ip=ip, port=port)
|
||||
|
||||
@default('port')
|
||||
def _default_port(self):
|
||||
return random_port()
|
||||
|
||||
@observe('orm_server')
|
||||
def _orm_server_changed(self, change):
|
||||
"""When we get an orm_server, get attributes from there."""
|
||||
obj = change.new
|
||||
self.proto = obj.proto
|
||||
self.ip = obj.ip
|
||||
self.port = obj.port
|
||||
self.base_url = obj.base_url
|
||||
self.cookie_name = obj.cookie_name
|
||||
|
||||
# setter to pass through to the database
|
||||
@observe('ip', 'proto', 'port', 'base_url', 'cookie_name')
|
||||
def _change(self, change):
|
||||
if self.orm_server:
|
||||
setattr(self.orm_server, change.name, change.new)
|
||||
|
||||
@property
|
||||
def host(self):
|
||||
ip = self.ip
|
||||
if ip in {'', '0.0.0.0'}:
|
||||
# when listening on all interfaces, connect to localhost
|
||||
ip = '127.0.0.1'
|
||||
return "{proto}://{ip}:{port}".format(
|
||||
proto=self.proto,
|
||||
ip=ip,
|
||||
port=self.port,
|
||||
)
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
return "{host}{uri}".format(
|
||||
host=self.host,
|
||||
uri=self.base_url,
|
||||
)
|
||||
|
||||
@property
|
||||
def bind_url(self):
|
||||
"""representation of URL used for binding
|
||||
|
||||
Never used in APIs, only logging,
|
||||
since it can be non-connectable value, such as '', meaning all interfaces.
|
||||
"""
|
||||
if self.ip in {'', '0.0.0.0'}:
|
||||
return self.url.replace('127.0.0.1', self.ip or '*', 1)
|
||||
return self.url
|
||||
|
||||
@gen.coroutine
|
||||
def wait_up(self, timeout=10, http=False):
|
||||
"""Wait for this server to come up"""
|
||||
if http:
|
||||
yield wait_for_http_server(self.url, timeout=timeout)
|
||||
else:
|
||||
yield wait_for_server(self.ip or '127.0.0.1', self.port, timeout=timeout)
|
||||
|
||||
def is_up(self):
|
||||
"""Is the server accepting connections?"""
|
||||
return can_connect(self.ip or '127.0.0.1', self.port)
|
||||
|
||||
|
||||
class Hub(Server):
|
||||
"""Bring it all together at the hub.
|
||||
|
||||
The Hub is a server, plus its API path suffix
|
||||
|
||||
the api_url is the full URL plus the api_path suffix on the end
|
||||
of the server base_url.
|
||||
"""
|
||||
|
||||
@property
|
||||
def server(self):
|
||||
"""backward-compat"""
|
||||
return self
|
||||
host = Unicode()
|
||||
|
||||
@property
|
||||
def api_url(self):
|
||||
"""return the full API url (with proto://host...)"""
|
||||
return url_path_join(self.server.url, 'api')
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s %s:%s>" % (
|
||||
self.__class__.__name__, self.server.ip, self.server.port,
|
||||
)
|
@@ -79,240 +79,6 @@ class Server(Base):
|
||||
def __repr__(self):
|
||||
return "<Server(%s:%s)>" % (self.ip, self.port)
|
||||
|
||||
@property
|
||||
def host(self):
|
||||
ip = self.ip
|
||||
if ip in {'', '0.0.0.0'}:
|
||||
# when listening on all interfaces, connect to localhost
|
||||
ip = '127.0.0.1'
|
||||
return "{proto}://{ip}:{port}".format(
|
||||
proto=self.proto,
|
||||
ip=ip,
|
||||
port=self.port,
|
||||
)
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
return "{host}{uri}".format(
|
||||
host=self.host,
|
||||
uri=self.base_url,
|
||||
)
|
||||
|
||||
@property
|
||||
def bind_url(self):
|
||||
"""representation of URL used for binding
|
||||
|
||||
Never used in APIs, only logging,
|
||||
since it can be non-connectable value, such as '', meaning all interfaces.
|
||||
"""
|
||||
if self.ip in {'', '0.0.0.0'}:
|
||||
return self.url.replace('127.0.0.1', self.ip or '*', 1)
|
||||
return self.url
|
||||
|
||||
@gen.coroutine
|
||||
def wait_up(self, timeout=10, http=False):
|
||||
"""Wait for this server to come up"""
|
||||
if http:
|
||||
yield wait_for_http_server(self.url, timeout=timeout)
|
||||
else:
|
||||
yield wait_for_server(self.ip or '127.0.0.1', self.port, timeout=timeout)
|
||||
|
||||
def is_up(self):
|
||||
"""Is the server accepting connections?"""
|
||||
return can_connect(self.ip or '127.0.0.1', self.port)
|
||||
|
||||
|
||||
class Proxy(Base):
|
||||
"""A configurable-http-proxy instance.
|
||||
|
||||
A proxy consists of the API server info and the public-facing server info,
|
||||
plus an auth token for configuring the proxy table.
|
||||
"""
|
||||
__tablename__ = 'proxies'
|
||||
id = Column(Integer, primary_key=True)
|
||||
auth_token = None
|
||||
_public_server_id = Column(Integer, ForeignKey('servers.id'))
|
||||
public_server = relationship(Server, primaryjoin=_public_server_id == Server.id)
|
||||
_api_server_id = Column(Integer, ForeignKey('servers.id'))
|
||||
api_server = relationship(Server, primaryjoin=_api_server_id == Server.id)
|
||||
|
||||
def __repr__(self):
|
||||
if self.public_server:
|
||||
return "<%s %s:%s>" % (
|
||||
self.__class__.__name__, self.public_server.ip, self.public_server.port,
|
||||
)
|
||||
else:
|
||||
return "<%s [unconfigured]>" % self.__class__.__name__
|
||||
|
||||
def api_request(self, path, method='GET', body=None, client=None):
|
||||
"""Make an authenticated API request of the proxy"""
|
||||
client = client or AsyncHTTPClient()
|
||||
url = url_path_join(self.api_server.url, path)
|
||||
|
||||
if isinstance(body, dict):
|
||||
body = json.dumps(body)
|
||||
self.log.debug("Fetching %s %s", method, url)
|
||||
req = HTTPRequest(url,
|
||||
method=method,
|
||||
headers={'Authorization': 'token {}'.format(self.auth_token)},
|
||||
body=body,
|
||||
)
|
||||
|
||||
return client.fetch(req)
|
||||
|
||||
@gen.coroutine
|
||||
def add_service(self, service, client=None):
|
||||
"""Add a service's server to the proxy table."""
|
||||
if not service.server:
|
||||
raise RuntimeError(
|
||||
"Service %s does not have an http endpoint to add to the proxy.", service.name)
|
||||
|
||||
self.log.info("Adding service %s to proxy %s => %s",
|
||||
service.name, service.proxy_path, service.server.host,
|
||||
)
|
||||
|
||||
yield self.proxy.add_route(
|
||||
service.proxy_path,
|
||||
service.server.host,
|
||||
{'service': service.name}
|
||||
)
|
||||
|
||||
@gen.coroutine
|
||||
def delete_service(self, service, client=None):
|
||||
"""Remove a service's server from the proxy table."""
|
||||
self.log.info("Removing service %s from proxy", service.name)
|
||||
yield self.proxy.delete_route(service.proxy_path)
|
||||
|
||||
# FIX-ME
|
||||
# we need to add a reference to a specific server
|
||||
@gen.coroutine
|
||||
def add_user(self, user, client=None):
|
||||
"""Add a user's server to the proxy table."""
|
||||
self.log.info("Adding user %s to proxy %s => %s",
|
||||
user.name, user.proxy_path, user.server.host,
|
||||
)
|
||||
|
||||
if user.spawn_pending:
|
||||
raise RuntimeError(
|
||||
"User %s's spawn is pending, shouldn't be added to the proxy yet!", user.name)
|
||||
|
||||
yield self.proxy.add_route(
|
||||
user.proxy_path,
|
||||
user.server.host,
|
||||
{'user': user.name}
|
||||
)
|
||||
|
||||
@gen.coroutine
|
||||
def delete_user(self, user, client=None):
|
||||
"""Remove a user's server from the proxy table."""
|
||||
self.log.info("Removing user %s from proxy", user.name)
|
||||
yield self.proxy.delete_route(
|
||||
user.proxy_path
|
||||
)
|
||||
|
||||
@gen.coroutine
|
||||
def add_all_services(self, service_dict):
|
||||
"""Update the proxy table from the database.
|
||||
|
||||
Used when loading up a new proxy.
|
||||
"""
|
||||
db = inspect(self).session
|
||||
futures = []
|
||||
for orm_service in db.query(Service):
|
||||
service = service_dict[orm_service.name]
|
||||
if service.server:
|
||||
futures.append(self.add_service(service))
|
||||
# wait after submitting them all
|
||||
for f in futures:
|
||||
yield f
|
||||
|
||||
@gen.coroutine
|
||||
def add_all_users(self, user_dict):
|
||||
"""Update the proxy table from the database.
|
||||
|
||||
Used when loading up a new proxy.
|
||||
"""
|
||||
db = inspect(self).session
|
||||
futures = []
|
||||
for orm_user in db.query(User):
|
||||
user = user_dict[orm_user]
|
||||
if user.running:
|
||||
futures.append(self.add_user(user))
|
||||
# wait after submitting them all
|
||||
for f in futures:
|
||||
yield f
|
||||
|
||||
@gen.coroutine
|
||||
def get_routes(self, client=None):
|
||||
"""Fetch the proxy's routes"""
|
||||
return (yield self.proxy.get_all_routes())
|
||||
|
||||
# FIX-ME
|
||||
# we need to add a reference to a specific server
|
||||
@gen.coroutine
|
||||
def check_routes(self, user_dict, service_dict, routes=None):
|
||||
"""Check that all users are properly routed on the proxy"""
|
||||
if not routes:
|
||||
routes = yield self.get_routes()
|
||||
|
||||
user_routes = { r['user'] for r in routes.values() if 'user' in r }
|
||||
futures = []
|
||||
db = inspect(self).session
|
||||
for orm_user in db.query(User):
|
||||
user = user_dict[orm_user]
|
||||
if user.running:
|
||||
if user.name not in user_routes:
|
||||
self.log.warning("Adding missing route for %s (%s)", user.name, user.server)
|
||||
futures.append(self.add_user(user))
|
||||
else:
|
||||
# User not running, make sure it's not in the table
|
||||
if user.name in user_routes:
|
||||
self.log.warning("Removing route for not running %s", user.name)
|
||||
futures.append(self.delete_user(user))
|
||||
|
||||
# check service routes
|
||||
service_routes = { r['service'] for r in routes.values() if 'service' in r }
|
||||
for orm_service in db.query(Service).filter(Service.server != None):
|
||||
service = service_dict[orm_service.name]
|
||||
if service.server is None:
|
||||
# This should never be True, but seems to be on rare occasion.
|
||||
# catch filter bug, either in sqlalchemy or my understanding of its behavior
|
||||
self.log.error("Service %s has no server, but wasn't filtered out.", service)
|
||||
continue
|
||||
if service.name not in service_routes:
|
||||
self.log.warning("Adding missing route for %s (%s)", service.name, service.server)
|
||||
futures.append(self.add_service(service))
|
||||
for f in futures:
|
||||
yield f
|
||||
|
||||
|
||||
class Hub(Base):
|
||||
"""Bring it all together at the hub.
|
||||
|
||||
The Hub is a server, plus its API path suffix
|
||||
|
||||
the api_url is the full URL plus the api_path suffix on the end
|
||||
of the server base_url.
|
||||
"""
|
||||
__tablename__ = 'hubs'
|
||||
id = Column(Integer, primary_key=True)
|
||||
_server_id = Column(Integer, ForeignKey('servers.id'))
|
||||
server = relationship(Server, primaryjoin=_server_id == Server.id)
|
||||
host = ''
|
||||
|
||||
@property
|
||||
def api_url(self):
|
||||
"""return the full API url (with proto://host...)"""
|
||||
return url_path_join(self.server.url, 'api')
|
||||
|
||||
def __repr__(self):
|
||||
if self.server:
|
||||
return "<%s %s:%s>" % (
|
||||
self.__class__.__name__, self.server.ip, self.server.port,
|
||||
)
|
||||
else:
|
||||
return "<%s [unconfigured]>" % self.__class__.__name__
|
||||
|
||||
|
||||
# user:group many:many mapping table
|
||||
user_group_map = Table('user_group_map', Base.metadata,
|
||||
|
@@ -496,8 +496,8 @@ class Spawner(LoggingConfigurable):
|
||||
args = [
|
||||
'--user="%s"' % self.user.name,
|
||||
'--base-url="%s"' % self.user.server.base_url,
|
||||
'--hub-host="%s"' % self.hub.host,
|
||||
'--hub-prefix="%s"' % self.hub.server.base_url,
|
||||
'--hub-host="%s"' % self.hub.public_host,
|
||||
'--hub-prefix="%s"' % self.hub.base_url,
|
||||
'--hub-api-url="%s"' % self.hub.api_url,
|
||||
]
|
||||
if self.ip:
|
||||
|
@@ -165,7 +165,7 @@ class MockHub(JupyterHub):
|
||||
self.db.add(user)
|
||||
self.db.commit()
|
||||
yield super(MockHub, self).start()
|
||||
yield self.hub.server.wait_up(http=True)
|
||||
yield self.hub.wait_up(http=True)
|
||||
self.io_loop.add_callback(evt.set)
|
||||
|
||||
def _start():
|
||||
|
@@ -83,7 +83,7 @@ def auth_header(db, name):
|
||||
@check_db_locks
|
||||
def api_request(app, *api_path, **kwargs):
|
||||
"""Make an API request"""
|
||||
base_url = app.hub.server.url
|
||||
base_url = app.hub.url
|
||||
headers = kwargs.setdefault('headers', {})
|
||||
|
||||
if 'Authorization' not in headers:
|
||||
@@ -94,7 +94,7 @@ def api_request(app, *api_path, **kwargs):
|
||||
f = getattr(requests, method)
|
||||
resp = f(url, **kwargs)
|
||||
assert "frame-ancestors 'self'" in resp.headers['Content-Security-Policy']
|
||||
assert ujoin(app.hub.server.base_url, "security/csp-report") in resp.headers['Content-Security-Policy']
|
||||
assert ujoin(app.hub.base_url, "security/csp-report") in resp.headers['Content-Security-Policy']
|
||||
assert 'http' not in resp.headers['Content-Security-Policy']
|
||||
return resp
|
||||
|
||||
@@ -132,7 +132,7 @@ def test_auth_api(app):
|
||||
|
||||
|
||||
def test_referer_check(app, io_loop):
|
||||
url = ujoin(public_host(app), app.hub.server.base_url)
|
||||
url = ujoin(public_host(app), app.hub.base_url)
|
||||
host = urlparse(url).netloc
|
||||
user = find_user(app.db, 'admin')
|
||||
if user is None:
|
||||
@@ -779,7 +779,7 @@ def test_get_service(app, mockservice_url):
|
||||
|
||||
|
||||
def test_root_api(app):
|
||||
base_url = app.hub.server.url
|
||||
base_url = app.hub.url
|
||||
url = ujoin(base_url, 'api')
|
||||
r = requests.get(url)
|
||||
r.raise_for_status()
|
||||
|
@@ -58,8 +58,8 @@ def test_hub(db):
|
||||
)
|
||||
db.add(hub)
|
||||
db.commit()
|
||||
assert hub.server.ip == '1.2.3.4'
|
||||
assert hub.server.port == 1234
|
||||
assert hub.ip == '1.2.3.4'
|
||||
assert hub.port == 1234
|
||||
assert hub.api_url == 'http://1.2.3.4:1234/hubtest/api'
|
||||
|
||||
|
||||
|
@@ -16,7 +16,7 @@ from .test_api import api_request
|
||||
|
||||
def get_page(path, app, hub=True, **kw):
|
||||
if hub:
|
||||
prefix = app.hub.server.base_url
|
||||
prefix = app.hub.base_url
|
||||
else:
|
||||
prefix = app.base_url
|
||||
base_url = ujoin(public_host(app), prefix)
|
||||
@@ -24,11 +24,11 @@ def get_page(path, app, hub=True, **kw):
|
||||
return requests.get(ujoin(base_url, path), **kw)
|
||||
|
||||
def test_root_no_auth(app, io_loop):
|
||||
print(app.hub.server.is_up())
|
||||
print(app.hub.is_up())
|
||||
routes = io_loop.run_sync(app.proxy.get_routes)
|
||||
print(routes)
|
||||
print(app.hub.server)
|
||||
url = ujoin(public_host(app), app.hub.server.base_url)
|
||||
url = ujoin(public_host(app), app.hub.base_url)
|
||||
print(url)
|
||||
r = requests.get(url)
|
||||
r.raise_for_status()
|
||||
@@ -123,7 +123,7 @@ def test_spawn_page(app):
|
||||
|
||||
def test_spawn_form(app, io_loop):
|
||||
with mock.patch.dict(app.users.settings, {'spawner_class': FormSpawner}):
|
||||
base_url = ujoin(public_host(app), app.hub.server.base_url)
|
||||
base_url = ujoin(public_host(app), app.hub.base_url)
|
||||
cookies = app.login_user('jones')
|
||||
orm_u = orm.User.find(app.db, 'jones')
|
||||
u = app.users[orm_u]
|
||||
@@ -145,7 +145,7 @@ def test_spawn_form(app, io_loop):
|
||||
|
||||
def test_spawn_form_with_file(app, io_loop):
|
||||
with mock.patch.dict(app.users.settings, {'spawner_class': FormSpawner}):
|
||||
base_url = ujoin(public_host(app), app.hub.server.base_url)
|
||||
base_url = ujoin(public_host(app), app.hub.base_url)
|
||||
cookies = app.login_user('jones')
|
||||
orm_u = orm.User.find(app.db, 'jones')
|
||||
u = app.users[orm_u]
|
||||
@@ -181,7 +181,7 @@ def test_user_redirect(app):
|
||||
assert path == ujoin(app.base_url, '/hub/login')
|
||||
query = urlparse(r.url).query
|
||||
assert query == urlencode({
|
||||
'next': ujoin(app.hub.server.base_url, '/user-redirect/tree/top/')
|
||||
'next': ujoin(app.hub.base_url, '/user-redirect/tree/top/')
|
||||
})
|
||||
|
||||
r = get_page('/user-redirect/notebooks/test.ipynb', app, cookies=cookies)
|
||||
@@ -320,7 +320,7 @@ def test_login_no_whitelist_adds_user(app):
|
||||
|
||||
|
||||
def test_static_files(app):
|
||||
base_url = ujoin(public_host(app), app.hub.server.base_url)
|
||||
base_url = ujoin(public_host(app), app.hub.base_url)
|
||||
r = requests.get(ujoin(base_url, 'logo'))
|
||||
r.raise_for_status()
|
||||
assert r.headers['content-type'] == 'image/png'
|
||||
|
@@ -25,7 +25,7 @@ def external_service(app, name='mockservice'):
|
||||
env = {
|
||||
'JUPYTERHUB_API_TOKEN': hexlify(os.urandom(5)),
|
||||
'JUPYTERHUB_SERVICE_NAME': name,
|
||||
'JUPYTERHUB_API_URL': url_path_join(app.hub.server.url, 'api/'),
|
||||
'JUPYTERHUB_API_URL': url_path_join(app.hub.url, 'api/'),
|
||||
'JUPYTERHUB_SERVICE_URL': 'http://127.0.0.1:%i' % random_port(),
|
||||
}
|
||||
proc = Popen(mockservice_cmd, env=env)
|
||||
|
@@ -119,8 +119,7 @@ class User(HasTraits):
|
||||
hub = self.db.query(orm.Hub).first()
|
||||
|
||||
self.allow_named_servers = self.settings.get('allow_named_servers', False)
|
||||
|
||||
self.cookie_name = '%s-%s' % (hub.server.cookie_name, quote(self.name, safe=''))
|
||||
self.cookie_name = '%s-%s' % (hub.cookie_name, quote(self.name, safe=''))
|
||||
self.base_url = url_path_join(
|
||||
self.settings.get('base_url', '/'), 'user', self.escaped_name)
|
||||
|
||||
|
@@ -37,6 +37,8 @@ def can_connect(ip, port):
|
||||
|
||||
Return True if we can connect, False otherwise.
|
||||
"""
|
||||
if ip in {'', '0.0.0.0'}:
|
||||
ip = '127.0.0.1'
|
||||
try:
|
||||
socket.create_connection((ip, port))
|
||||
except socket.error as e:
|
||||
@@ -50,6 +52,8 @@ def can_connect(ip, port):
|
||||
@gen.coroutine
|
||||
def wait_for_server(ip, port, timeout=10):
|
||||
"""Wait for any server to show up at ip:port."""
|
||||
if ip in {'', '0.0.0.0'}:
|
||||
ip = '127.0.0.1'
|
||||
loop = ioloop.IOLoop.current()
|
||||
tic = loop.time()
|
||||
while loop.time() - tic < timeout:
|
||||
|
Reference in New Issue
Block a user