remove Hub, Proxy from database

These are in-memory-only objects, no need for a table with one row
This commit is contained in:
Min RK
2017-04-20 15:18:30 +02:00
parent a98bab8b5e
commit acc31b8441
15 changed files with 178 additions and 327 deletions

View File

@@ -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",

View File

@@ -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(
ip=self.hub_ip,
port=self.hub_port,
base_url=self.hub_prefix,
cookie_name='jupyter-hub-token',
)
)
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()
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,
)
@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:

View File

@@ -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

View File

@@ -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:

View File

@@ -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
View 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,
)

View File

@@ -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,

View File

@@ -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:

View File

@@ -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():

View File

@@ -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()

View File

@@ -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'

View File

@@ -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'

View File

@@ -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)

View File

@@ -117,10 +117,9 @@ class User(HasTraits):
super().__init__(**kwargs)
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.allow_named_servers = self.settings.get('allow_named_servers', False)
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)

View File

@@ -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: