diff --git a/jupyterhub/app.py b/jupyterhub/app.py index eab4e278..038daf95 100644 --- a/jupyterhub/app.py +++ b/jupyterhub/app.py @@ -593,14 +593,14 @@ class JupyterHub(Application): q = self.db.query(orm.Hub) assert q.count() <= 1 self._local.hub = q.first() - if self.use_subdomains: + if self.use_subdomains 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 self.use_subdomains: + if hub and self.use_subdomains: hub.host = self.subdomain_host @property diff --git a/jupyterhub/tests/mocking.py b/jupyterhub/tests/mocking.py index 0b77fc73..9998e5a0 100644 --- a/jupyterhub/tests/mocking.py +++ b/jupyterhub/tests/mocking.py @@ -1,7 +1,7 @@ """mock utilities for testing""" +import os import sys -from datetime import timedelta from tempfile import NamedTemporaryFile import threading @@ -13,10 +13,11 @@ from tornado import gen from tornado.concurrent import Future from tornado.ioloop import IOLoop -from ..spawner import LocalProcessSpawner from ..app import JupyterHub from ..auth import PAMAuthenticator from .. import orm +from ..spawner import LocalProcessSpawner +from ..utils import url_path_join from pamela import PAMError @@ -110,6 +111,12 @@ class MockHub(JupyterHub): db_file = None confirm_no_ssl = True + def _subdomain_host_default(self): + return os.environ.get('JUPYTERHUB_TEST_SUBDOMAIN_HOST', '') + + def _use_subdomains_default(self): + return bool(self.subdomain_host) + def _ip_default(self): return '127.0.0.1' @@ -161,7 +168,11 @@ class MockHub(JupyterHub): self.db_file.close() def login_user(self, name): - r = requests.post(self.proxy.public_server.url + 'hub/login', + if self.subdomain_host: + base_url = 'http://' + self.subdomain_host + self.proxy.public_server.base_url + else: + base_url = self.proxy.public_server.url + r = requests.post(base_url + 'hub/login', data={ 'username': name, 'password': name, @@ -171,3 +182,22 @@ class MockHub(JupyterHub): assert r.cookies return r.cookies + +def public_host(app): + if app.use_subdomains: + return app.subdomain_host + else: + return app.proxy.public_server.host + + +def public_url(app): + return 'http://%s%s' % (public_host(app), app.proxy.public_server.base_url) + + +def user_url(user, app): + print(user.host) + if app.use_subdomains: + host = user.host + else: + host = public_host(app) + return url_path_join('http://%s' % host, user.server.base_url) diff --git a/jupyterhub/tests/test_api.py b/jupyterhub/tests/test_api.py index 413ff913..021db510 100644 --- a/jupyterhub/tests/test_api.py +++ b/jupyterhub/tests/test_api.py @@ -2,7 +2,6 @@ import json import time -from datetime import timedelta from queue import Queue from urllib.parse import urlparse @@ -14,6 +13,7 @@ from .. import orm from ..user import User from ..utils import url_path_join as ujoin from . import mocking +from .mocking import public_url, user_url def check_db_locks(func): @@ -105,7 +105,7 @@ def test_auth_api(app): def test_referer_check(app, io_loop): - url = app.hub.server.url + url = ujoin(public_url(app), app.hub.server.base_url) host = urlparse(url).netloc user = find_user(app.db, 'admin') if user is None: @@ -352,15 +352,19 @@ def test_spawn(app, io_loop): assert status is None assert user.server.base_url == '/user/%s' % name - r = requests.get(ujoin(app.proxy.public_server.url, user.server.base_url)) + url = user_url(user, app) + print(url) + r = requests.get(url) assert r.status_code == 200 assert r.text == user.server.base_url - r = requests.get(ujoin(app.proxy.public_server.url, user.server.base_url, 'args')) + r = requests.get(ujoin(url, 'args')) assert r.status_code == 200 argv = r.json() for expected in ['--user=%s' % name, '--base-url=%s' % user.server.base_url]: assert expected in argv + if app.use_subdomains: + assert '--hub-host=//%s' % app.subdomain_host in argv r = api_request(app, 'users', name, 'server', method='delete') assert r.status_code == 204 diff --git a/jupyterhub/tests/test_pages.py b/jupyterhub/tests/test_pages.py index ba85ff26..d1bb8504 100644 --- a/jupyterhub/tests/test_pages.py +++ b/jupyterhub/tests/test_pages.py @@ -8,12 +8,11 @@ from ..utils import url_path_join as ujoin from .. import orm import mock -from .mocking import FormSpawner +from .mocking import FormSpawner, public_url, public_host, user_url from .test_api import api_request - def get_page(path, app, **kw): - base_url = ujoin(app.proxy.public_server.host, app.hub.server.base_url) + base_url = ujoin(public_url(app), app.hub.server.base_url) print(base_url) return requests.get(ujoin(base_url, path), **kw) @@ -22,15 +21,16 @@ def test_root_no_auth(app, io_loop): routes = io_loop.run_sync(app.proxy.get_routes) print(routes) print(app.hub.server) - r = requests.get(app.proxy.public_server.host) + url = public_url(app) + r = requests.get(url) r.raise_for_status() - assert r.url == ujoin(app.proxy.public_server.host, app.hub.server.base_url, 'login') + assert r.url == ujoin(url, app.hub.server.base_url, 'login') def test_root_auth(app): cookies = app.login_user('river') - r = requests.get(app.proxy.public_server.host, cookies=cookies) + r = requests.get(public_url(app), cookies=cookies) r.raise_for_status() - assert r.url == ujoin(app.proxy.public_server.host, '/user/river') + assert r.url == user_url(app.users['river'], app) def test_home_no_auth(app): r = get_page('home', app, allow_redirects=False) @@ -100,7 +100,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(app.proxy.public_server.host, app.hub.server.base_url) + base_url = ujoin(public_url(app), app.hub.server.base_url) cookies = app.login_user('jones') orm_u = orm.User.find(app.db, 'jones') u = app.users[orm_u] @@ -121,7 +121,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(app.proxy.public_server.host, app.hub.server.base_url) + base_url = ujoin(public_url(app), app.hub.server.base_url) cookies = app.login_user('jones') orm_u = orm.User.find(app.db, 'jones') u = app.users[orm_u] @@ -149,7 +149,7 @@ def test_spawn_form_with_file(app, io_loop): def test_static_files(app): - base_url = ujoin(app.proxy.public_server.url, app.hub.server.base_url) + base_url = ujoin(public_url(app), app.hub.server.base_url) print(base_url) r = requests.get(ujoin(base_url, 'logo')) r.raise_for_status() diff --git a/jupyterhub/tests/test_proxy.py b/jupyterhub/tests/test_proxy.py index 786c7c23..ef4cac6e 100644 --- a/jupyterhub/tests/test_proxy.py +++ b/jupyterhub/tests/test_proxy.py @@ -34,6 +34,8 @@ def test_external_proxy(request, io_loop): '--api-port', str(proxy_port), '--default-target', 'http://%s:%i' % (app.hub_ip, app.hub_port), ] + if app.use_subdomains: + cmd.append('--host-routing') proxy = Popen(cmd, env=env) def _cleanup_proxy(): if proxy.poll() is None: @@ -60,7 +62,11 @@ def test_external_proxy(request, io_loop): r.raise_for_status() routes = io_loop.run_sync(app.proxy.get_routes) - assert sorted(routes.keys()) == ['/', '/user/river'] + user_path = '/user/river' + if app.use_subdomains: + domain = app.subdomain_host.rsplit(':', 1)[0] + user_path = '/%s.%s' % (name, domain) + user_path + assert sorted(routes.keys()) == ['/', user_path] # teardown the proxy and start a new one in the same place proxy.terminate() @@ -76,7 +82,7 @@ def test_external_proxy(request, io_loop): # check that the routes are correct routes = io_loop.run_sync(app.proxy.get_routes) - assert sorted(routes.keys()) == ['/', '/user/river'] + assert sorted(routes.keys()) == ['/', user_path] # teardown the proxy again, and start a new one with different auth and port proxy.terminate() @@ -90,7 +96,8 @@ def test_external_proxy(request, io_loop): '--api-port', str(proxy_port), '--default-target', 'http://%s:%i' % (app.hub_ip, app.hub_port), ] - + if app.use_subdomains: + cmd.append('--host-routing') proxy = Popen(cmd, env=env) wait_for_proxy() @@ -113,7 +120,7 @@ def test_external_proxy(request, io_loop): # check that the routes are correct routes = io_loop.run_sync(app.proxy.get_routes) - assert sorted(routes.keys()) == ['/', '/user/river'] + assert sorted(routes.keys()) == ['/', user_path] def test_check_routes(app, io_loop): proxy = app.proxy @@ -125,12 +132,12 @@ def test_check_routes(app, io_loop): assert zoe is not None zoe = app.users[zoe] before = sorted(io_loop.run_sync(app.proxy.get_routes)) - assert '/user/zoe' in before + assert zoe.proxy_path in before io_loop.run_sync(lambda : app.proxy.check_routes(app.users)) io_loop.run_sync(lambda : proxy.delete_user(zoe)) during = sorted(io_loop.run_sync(app.proxy.get_routes)) - assert '/user/zoe' not in during + assert zoe.proxy_path not in during io_loop.run_sync(lambda : app.proxy.check_routes(app.users)) after = sorted(io_loop.run_sync(app.proxy.get_routes)) - assert '/user/zoe' in after + assert zoe.proxy_path in after assert before == after