Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add base_url capability to tljh-config #623

Merged
merged 9 commits into from
Nov 5, 2020
7 changes: 7 additions & 0 deletions docs/topic/tljh-config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@ file. If what you want is only to change the property's value, you should use

Some of the existing ``<property-path>`` are listed below by categories:

.. _tljh-base_url:

Base URL
--------

Use ``base_url`` to determine the base URL used by JupyterHub. This parameter will
be passed straight to ``c.JupyterHub.base_url``.

.. _tljh-set-auth:

Expand Down
201 changes: 115 additions & 86 deletions integration-tests/test_hub.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
# This catches issues with PATH
TLJH_CONFIG_PATH = ['sudo', 'tljh-config']


def test_hub_up():
r = requests.get('http://127.0.0.1')
r.raise_for_status()
Expand All @@ -37,13 +38,37 @@ async def test_user_code_execute():
assert 0 == await (await asyncio.create_subprocess_exec(*TLJH_CONFIG_PATH, 'reload')).wait()

async with User(username, hub_url, partial(login_dummy, password='')) as u:
await u.login()
await u.ensure_server_simulate()
await u.start_kernel()
await u.assert_code_output("5 * 4", "20", 5, 5)
await u.login()
await u.ensure_server_simulate()
await u.start_kernel()
await u.assert_code_output("5 * 4", "20", 5, 5)

# Assert that the user exists
assert pwd.getpwnam(f'jupyter-{username}') is not None
# Assert that the user exists
assert pwd.getpwnam(f'jupyter-{username}') is not None


@pytest.mark.asyncio
async def test_user_server_started_with_custom_base_url():
"""
User logs in, starts a server with a custom base_url & executes code
"""
# This *must* be localhost, not an IP
# aiohttp throws away cookies if we are connecting to an IP!
base_url = "/custom-base"
hub_url = f"http://localhost{base_url}"
username = secrets.token_hex(8)

assert 0 == await (await asyncio.create_subprocess_exec(*TLJH_CONFIG_PATH, 'set', 'auth.type', 'dummyauthenticator.DummyAuthenticator')).wait()
assert 0 == await (await asyncio.create_subprocess_exec(*TLJH_CONFIG_PATH, 'set', 'base_url', base_url)).wait()
assert 0 == await (await asyncio.create_subprocess_exec(*TLJH_CONFIG_PATH, 'reload')).wait()

async with User(username, hub_url, partial(login_dummy, password='')) as u:
await u.login()
await u.ensure_server_simulate()

# unset base_url to avoid problems with other tests
assert 0 == await (await asyncio.create_subprocess_exec(*TLJH_CONFIG_PATH, 'unset', 'base_url')).wait()
assert 0 == await (await asyncio.create_subprocess_exec(*TLJH_CONFIG_PATH, 'reload')).wait()


@pytest.mark.asyncio
Expand All @@ -61,14 +86,15 @@ async def test_user_admin_add():
assert 0 == await (await asyncio.create_subprocess_exec(*TLJH_CONFIG_PATH, 'reload')).wait()

async with User(username, hub_url, partial(login_dummy, password='')) as u:
await u.login()
await u.ensure_server_simulate()
await u.login()
await u.ensure_server_simulate()

# Assert that the user exists
assert pwd.getpwnam(f'jupyter-{username}') is not None
# Assert that the user exists
assert pwd.getpwnam(f'jupyter-{username}') is not None

# Assert that the user has admin rights
assert f'jupyter-{username}' in grp.getgrnam('jupyterhub-admins').gr_mem
# Assert that the user has admin rights
assert f'jupyter-{username}' in grp.getgrnam(
'jupyterhub-admins').gr_mem


# FIXME: Make this test pass
Expand All @@ -90,23 +116,25 @@ async def test_user_admin_remove():
assert 0 == await (await asyncio.create_subprocess_exec(*TLJH_CONFIG_PATH, 'reload')).wait()

async with User(username, hub_url, partial(login_dummy, password='')) as u:
await u.login()
await u.ensure_server_simulate()
await u.login()
await u.ensure_server_simulate()

# Assert that the user exists
assert pwd.getpwnam(f'jupyter-{username}') is not None
# Assert that the user exists
assert pwd.getpwnam(f'jupyter-{username}') is not None

# Assert that the user has admin rights
assert f'jupyter-{username}' in grp.getgrnam('jupyterhub-admins').gr_mem
# Assert that the user has admin rights
assert f'jupyter-{username}' in grp.getgrnam(
'jupyterhub-admins').gr_mem

assert 0 == await (await asyncio.create_subprocess_exec(*TLJH_CONFIG_PATH, 'remove-item', 'users.admin', username)).wait()
assert 0 == await (await asyncio.create_subprocess_exec(*TLJH_CONFIG_PATH, 'reload')).wait()
assert 0 == await (await asyncio.create_subprocess_exec(*TLJH_CONFIG_PATH, 'remove-item', 'users.admin', username)).wait()
assert 0 == await (await asyncio.create_subprocess_exec(*TLJH_CONFIG_PATH, 'reload')).wait()

await u.stop_server()
await u.ensure_server_simulate()
await u.stop_server()
await u.ensure_server_simulate()

# Assert that the user does *not* have admin rights
assert f'jupyter-{username}' not in grp.getgrnam('jupyterhub-admins').gr_mem
# Assert that the user does *not* have admin rights
assert f'jupyter-{username}' not in grp.getgrnam(
'jupyterhub-admins').gr_mem


@pytest.mark.asyncio
Expand All @@ -124,14 +152,14 @@ async def test_long_username():

try:
async with User(username, hub_url, partial(login_dummy, password='')) as u:
await u.login()
await u.ensure_server_simulate()
await u.login()
await u.ensure_server_simulate()

# Assert that the user exists
system_username = generate_system_username(f'jupyter-{username}')
assert pwd.getpwnam(system_username) is not None
# Assert that the user exists
system_username = generate_system_username(f'jupyter-{username}')
assert pwd.getpwnam(system_username) is not None

await u.stop_server()
await u.stop_server()
except:
# If we have any errors, print jupyterhub logs before exiting
subprocess.check_call([
Expand Down Expand Up @@ -161,19 +189,19 @@ async def test_user_group_adding():

try:
async with User(username, hub_url, partial(login_dummy, password='')) as u:
await u.login()
await u.ensure_server_simulate()
await u.login()
await u.ensure_server_simulate()

# Assert that the user exists
system_username = generate_system_username(f'jupyter-{username}')
assert pwd.getpwnam(system_username) is not None
# Assert that the user exists
system_username = generate_system_username(f'jupyter-{username}')
assert pwd.getpwnam(system_username) is not None

# Assert that the user was added to the specified group
assert f'jupyter-{username}' in grp.getgrnam('somegroup').gr_mem
# Assert that the user was added to the specified group
assert f'jupyter-{username}' in grp.getgrnam('somegroup').gr_mem

await u.stop_server()
# Delete the group
system('groupdel somegroup')
await u.stop_server()
# Delete the group
system('groupdel somegroup')
except:
# If we have any errors, print jupyterhub logs before exiting
subprocess.check_call([
Expand Down Expand Up @@ -205,29 +233,30 @@ async def test_idle_server_culled():
assert 0 == await (await asyncio.create_subprocess_exec(*TLJH_CONFIG_PATH, 'reload')).wait()

async with User(username, hub_url, partial(login_dummy, password='')) as u:
await u.login()
# Start user's server
await u.ensure_server_simulate()
# Assert that the user exists
assert pwd.getpwnam(f'jupyter-{username}') is not None

# Check that we can get to the user's server
await u.login()
# Start user's server
await u.ensure_server_simulate()
# Assert that the user exists
assert pwd.getpwnam(f'jupyter-{username}') is not None

# Check that we can get to the user's server
r = await u.session.get(u.hub_url / 'hub/api/users' / username,
headers={'Referer': str(u.hub_url / 'hub/')})
assert r.status == 200

async def _check_culling_done():
# Check that after 60s, the user and server have been culled and are not reacheable anymore
r = await u.session.get(u.hub_url / 'hub/api/users' / username,
headers={'Referer': str(u.hub_url / 'hub/')})
assert r.status == 200
headers={'Referer': str(u.hub_url / 'hub/')})
print(r.status)
return r.status == 403

async def _check_culling_done():
# Check that after 60s, the user and server have been culled and are not reacheable anymore
r = await u.session.get(u.hub_url / 'hub/api/users' / username,
headers={'Referer': str(u.hub_url / 'hub/')})
print(r.status)
return r.status == 403
await exponential_backoff(
_check_culling_done,
"Server culling failed!",
timeout=100,
)

await exponential_backoff(
_check_culling_done,
"Server culling failed!",
timeout=100,
)

@pytest.mark.asyncio
async def test_active_server_not_culled():
Expand All @@ -250,30 +279,30 @@ async def test_active_server_not_culled():
assert 0 == await (await asyncio.create_subprocess_exec(*TLJH_CONFIG_PATH, 'reload')).wait()

async with User(username, hub_url, partial(login_dummy, password='')) as u:
await u.login()
# Start user's server
await u.ensure_server_simulate()
# Assert that the user exists
assert pwd.getpwnam(f'jupyter-{username}') is not None

# Check that we can get to the user's server
await u.login()
# Start user's server
await u.ensure_server_simulate()
# Assert that the user exists
assert pwd.getpwnam(f'jupyter-{username}') is not None

# Check that we can get to the user's server
r = await u.session.get(u.hub_url / 'hub/api/users' / username,
headers={'Referer': str(u.hub_url / 'hub/')})
assert r.status == 200

async def _check_culling_done():
# Check that after 30s, we can still reach the user's server
r = await u.session.get(u.hub_url / 'hub/api/users' / username,
headers={'Referer': str(u.hub_url / 'hub/')})
assert r.status == 200

async def _check_culling_done():
# Check that after 30s, we can still reach the user's server
r = await u.session.get(u.hub_url / 'hub/api/users' / username,
headers={'Referer': str(u.hub_url / 'hub/')})
print(r.status)
return r.status != 200

try:
await exponential_backoff(
_check_culling_done,
"User's server is still reacheable!",
timeout=30,
)
except TimeoutError:
# During the 30s timeout the user's server wasn't culled, which is what we intended.
pass
headers={'Referer': str(u.hub_url / 'hub/')})
print(r.status)
return r.status != 200

try:
await exponential_backoff(
_check_culling_done,
"User's server is still reacheable!",
timeout=30,
)
except TimeoutError:
# During the 30s timeout the user's server wasn't culled, which is what we intended.
pass
38 changes: 27 additions & 11 deletions tests/test_configurer.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Test configurer
"""

from traitlets import Dict
import os
import sys

Expand Down Expand Up @@ -62,6 +63,22 @@ def apply_mock_config(overrides):
return c


def test_default_base_url():
"""
Test default JupyterHub base_url
"""
c = apply_mock_config({})
assert c.JupyterHub.base_url == '/'


def test_set_base_url():
"""
Test set JupyterHub base_url
"""
c = apply_mock_config({'base_url': '/custom-base'})
assert c.JupyterHub.base_url == '/custom-base'


def test_default_memory_limit():
"""
Test default per user memory limit
Expand Down Expand Up @@ -129,7 +146,7 @@ def test_auth_dummy():
assert c.JupyterHub.authenticator_class == 'dummyauthenticator.DummyAuthenticator'
assert c.DummyAuthenticator.password == 'test'

from traitlets import Dict

def test_user_groups():
"""
Test setting user groups
Expand All @@ -143,9 +160,9 @@ def test_user_groups():
}
})
assert c.UserCreatingSpawner.user_groups == {
"g1": ["u1", "u2"],
"g2": ["u3", "u4"]
}
"g1": ["u1", "u2"],
"g2": ["u3", "u4"]
}


def test_auth_firstuse():
Expand Down Expand Up @@ -213,9 +230,9 @@ def test_cull_service_default():
c = apply_mock_config({})

cull_cmd = [
sys.executable, '-m', 'jupyterhub_idle_culler',
'--timeout=600', '--cull-every=60', '--concurrency=5',
'--max-age=0'
sys.executable, '-m', 'jupyterhub_idle_culler',
'--timeout=600', '--cull-every=60', '--concurrency=5',
'--max-age=0'
]
assert c.JupyterHub.services == [{
'name': 'cull-idle',
Expand All @@ -238,9 +255,9 @@ def test_set_cull_service():
}
})
cull_cmd = [
sys.executable, '-m', 'jupyterhub_idle_culler',
'--timeout=600', '--cull-every=10', '--concurrency=5',
'--max-age=60', '--cull-users'
sys.executable, '-m', 'jupyterhub_idle_culler',
'--timeout=600', '--cull-every=10', '--concurrency=5',
'--max-age=60', '--cull-users'
]
assert c.JupyterHub.services == [{
'name': 'cull-idle',
Expand Down Expand Up @@ -276,4 +293,3 @@ def test_auth_native():
})
assert c.JupyterHub.authenticator_class == 'nativeauthenticator.NativeAuthenticator'
assert c.NativeAuthenticator.open_signup == True

Loading