Skip to content

Commit

Permalink
Spinup multiple JupyterLab servers (#486)
Browse files Browse the repository at this point in the history
* Spinup multiple JupyterLab servers

* parametrize jupyterhub session

* add test for server sharing

* Skip sharing tests if jupyterhub<5
  • Loading branch information
aktech authored Sep 25, 2024
1 parent 704b539 commit d343900
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 25 deletions.
21 changes: 13 additions & 8 deletions jhub_apps/hub_client/hub_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

from jhub_apps.service.models import UserOptions, SharePermissions
from jhub_apps.hub_client.utils import is_jupyterhub_5
from jhub_apps.spawner.types import Framework

API_URL = os.environ.get("JUPYTERHUB_API_URL")
JUPYTERHUB_API_TOKEN = os.environ.get("JUPYTERHUB_API_TOKEN")
Expand Down Expand Up @@ -183,15 +184,19 @@ def _create_server(self, username: str, servername: str, user_options: UserOptio
logger.info("Creating new server", server_name=servername)
r = requests.post(API_URL + url, headers=self._headers(), json=data)
r.raise_for_status()
if is_jupyterhub_5():
logger.info("Sharing", share_with=user_options.share_with)
self._share_server_with_multiple_entities(
username,
servername,
share_with=user_options.share_with
)
if user_options.framework != Framework.jupyterlab.value:
if is_jupyterhub_5():
logger.info("Sharing", share_with=user_options.share_with)
self._share_server_with_multiple_entities(
username,
servername,
share_with=user_options.share_with
)
else:
logger.info("Not sharing server as JupyterHub < 5.x")
else:
logger.info("Not sharing server as JupyterHub < 5.x")
logger.info(f"Not sharing the server as Framework is {user_options.framework}, "
f"sharing JupyterLab servers is not allowed.")
return r.status_code, servername

def _share_server(
Expand Down
12 changes: 6 additions & 6 deletions jhub_apps/spawner/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,12 @@ def values(cls):
logo_path=STATIC_PATH.joinpath("gradio.png"),
logo=f"{LOGO_BASE_PATH}/gradio.png"
),
# FrameworkConf(
# name=Framework.jupyterlab.value,
# display_name="JupyterLab",
# logo_path="",
# logo="",
# ),
FrameworkConf(
name=Framework.jupyterlab.value,
display_name="JupyterLab",
logo_path=STATIC_PATH.joinpath("jupyter.png"),
logo=f"{LOGO_BASE_PATH}/jupyter.png",
),
FrameworkConf(
name=Framework.custom.value,
display_name="Custom Command",
Expand Down
47 changes: 44 additions & 3 deletions jhub_apps/tests/tests_e2e/test_api.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import hashlib
import uuid

import pytest

from jhub_apps.service.models import Repository, UserOptions, ServerCreation
from jhub_apps.service.models import Repository, UserOptions, ServerCreation, SharePermissions
from jhub_apps.spawner.types import Framework
from jhub_apps.tests.common.constants import JHUB_APPS_API_BASE_URL, JUPYTERHUB_HOSTNAME
from jhub_apps.tests.tests_e2e.utils import get_jhub_apps_session, fetch_url_until_title_found
from jhub_apps.tests.tests_e2e.utils import get_jhub_apps_session, fetch_url_until_title_found, \
skip_if_jupyterhub_less_than_5

EXAMPLE_TEST_REPO = "https://github.com/nebari-dev/jhub-apps-from-git-repo-example.git"

Expand Down Expand Up @@ -82,7 +85,7 @@ def test_create_server_with_git_repository():
jhub_app=True,
display_name="Test Application",
description="App description",
framework="panel",
framework=Framework.panel.value,
thumbnail="data:image/png;base64,ZHVtbXkgaW1hZ2UgZGF0YQ==",
filepath="panel_basic.py",
repository=Repository(
Expand All @@ -108,3 +111,41 @@ def test_create_server_with_git_repository():
fetch_url_until_title_found(
session, url=created_app_url, expected_title="Panel Test App from Git Repository"
)


@skip_if_jupyterhub_less_than_5()
@pytest.mark.parametrize("framework, response_status_code,", [
(Framework.panel.value, 200),
(Framework.jupyterlab.value, 403),
])
def test_server_sharing(framework, response_status_code):
share_with_user = f"share-username-{uuid.uuid4().hex[:6]}"
shared_user_session = get_jhub_apps_session(username=share_with_user)
user_options = UserOptions(
jhub_app=True,
display_name="Test Application",
description="App description",
framework=framework,
thumbnail="data:image/png;base64,ZHVtbXkgaW1hZ2UgZGF0YQ==",
filepath="",
share_with=SharePermissions(
users=[share_with_user],
groups=[]
)
)
server_data = ServerCreation(
servername="test server sharing",
user_options=user_options
)
data = {"data": server_data.model_dump_json()}
session = get_jhub_apps_session()
response = session.post(
f"{JHUB_APPS_API_BASE_URL}/server",
verify=False,
data=data,
)
assert response.status_code == 200
server_name = response.json()[-1]
created_app_url = f"http://{JUPYTERHUB_HOSTNAME}/user/admin/{server_name}/"
response = shared_user_session.get(created_app_url)
assert response.status_code == response_status_code
31 changes: 23 additions & 8 deletions jhub_apps/tests/tests_e2e/utils.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,36 @@
import time

import pytest
import requests

from jhub_apps.tests.common.constants import JUPYTERHUB_HOSTNAME, JUPYTERHUB_USERNAME, JUPYTERHUB_PASSWORD
from jhub_apps.hub_client.utils import is_jupyterhub_5
from jhub_apps.tests.common import constants


def get_jhub_apps_session():
def get_jhub_apps_session(username=None):
"""Get jhub-apps session with authenticated cookies to be able to call jhub-apps API"""
session = requests.Session()
session.cookies.clear()
if username:
# Since we're using jupyterhub.auth.DummyAuthenticator,
# any pair of username/password will work fine.
login_username = username
login_password = username
else:
login_username = constants.JUPYTERHUB_USERNAME
login_password = constants.JUPYTERHUB_PASSWORD
try:
response = session.get(
f"http://{JUPYTERHUB_HOSTNAME}/hub/login", verify=False
f"http://{constants.JUPYTERHUB_HOSTNAME}/hub/login", verify=False
)
response.raise_for_status()
auth_data = {
"_xsrf": session.cookies['_xsrf'],
"username": JUPYTERHUB_USERNAME,
"password": JUPYTERHUB_PASSWORD,
"username": login_username,
"password": login_password,
}
response = session.post(
f"http://{JUPYTERHUB_HOSTNAME}/hub/login?next=",
f"http://{constants.JUPYTERHUB_HOSTNAME}/hub/login?next=",
headers={"Content-Type": "application/x-www-form-urlencoded"},
data=auth_data,
verify=False,
Expand All @@ -31,11 +41,11 @@ def get_jhub_apps_session():
raise ValueError(f"An error occurred during authentication: {e}")

response_login = session.get(
f"http://{JUPYTERHUB_HOSTNAME}/services/japps/jhub-login",
f"http://{constants.JUPYTERHUB_HOSTNAME}/services/japps/jhub-login",
)
response_login.raise_for_status()
response_user = session.get(
f"http://{JUPYTERHUB_HOSTNAME}/services/japps/user",
f"http://{constants.JUPYTERHUB_HOSTNAME}/services/japps/user",
verify=False
)
response_user.raise_for_status()
Expand All @@ -58,3 +68,8 @@ def fetch_url_until_title_found(
if time_elapsed > timeout:
raise TimeoutError(f"Failed to get the title {expected_title} within {timeout} seconds") from e
time.sleep(interval)


def skip_if_jupyterhub_less_than_5():
"""Skip test if JupyterHub < 5"""
return pytest.mark.skipif(not is_jupyterhub_5(), reason="Skipping test because JupyterHub<5")

0 comments on commit d343900

Please sign in to comment.