Skip to content

Commit fd2e56c

Browse files
committed
feat: perf improvements for amalthea sessions (#411)
1 parent ae87e85 commit fd2e56c

File tree

19 files changed

+251
-117
lines changed

19 files changed

+251
-117
lines changed

components/renku_data_services/app_config/config.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
from renku_data_services.message_queue.interface import IMessageQueue
5353
from renku_data_services.message_queue.redis_queue import RedisQueue
5454
from renku_data_services.namespace.db import GroupRepository
55-
from renku_data_services.notebooks.config import _NotebooksConfig
55+
from renku_data_services.notebooks.config import NotebooksConfig
5656
from renku_data_services.platform.db import PlatformRepository
5757
from renku_data_services.project.db import ProjectMemberRepository, ProjectRepository
5858
from renku_data_services.repositories.db import GitRepositoriesRepository
@@ -145,7 +145,7 @@ class Config:
145145
kc_api: IKeycloakAPI
146146
message_queue: IMessageQueue
147147
gitlab_url: str | None
148-
nb_config: _NotebooksConfig
148+
nb_config: NotebooksConfig
149149

150150
secrets_service_public_key: rsa.RSAPublicKey
151151
"""The public key of the secrets service, used to encrypt user secrets that only it can decrypt."""
@@ -511,7 +511,7 @@ def from_env(cls, prefix: str = "") -> "Config":
511511
sentry = SentryConfig.from_env(prefix)
512512
trusted_proxies = TrustedProxiesConfig.from_env(prefix)
513513
message_queue = RedisQueue(redis)
514-
nb_config = _NotebooksConfig.from_env(db)
514+
nb_config = NotebooksConfig.from_env(db)
515515

516516
return cls(
517517
version=version,

components/renku_data_services/base_models/core.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ def is_authenticated(self) -> bool:
3131
"""Indicates whether the user has successfully logged in."""
3232
return self.id is not None
3333

34+
@property
35+
def is_anonymous(self) -> bool:
36+
"""Indicates whether the user is anonymous."""
37+
return isinstance(self, AnonymousAPIUser)
38+
3439
def get_full_name(self) -> str | None:
3540
"""Generate the closest thing to a full name if the full name field is not set."""
3641
full_name = self.full_name or " ".join(filter(None, (self.first_name, self.last_name)))

components/renku_data_services/crc/db.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,13 @@ async def get_classes(
292292
orms = res.scalars().all()
293293
return [orm.dump() for orm in orms]
294294

295+
async def get_resource_class(self, api_user: base_models.APIUser, id: int) -> models.ResourceClass:
296+
"""Get a specific resource class by its ID."""
297+
classes = await self.get_classes(api_user, id)
298+
if len(classes) == 0:
299+
raise errors.MissingResourceError(message=f"The resource class with ID {id} cannot be found", quiet=True)
300+
return classes[0]
301+
295302
@_only_admins
296303
async def insert_resource_class(
297304
self,

components/renku_data_services/notebooks/api/amalthea_patches/git_proxy.py

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,48 +6,53 @@
66

77
from kubernetes import client
88

9+
from renku_data_services.base_models.core import AnonymousAPIUser, AuthenticatedAPIUser
910
from renku_data_services.notebooks.api.amalthea_patches.utils import get_certificates_volume_mounts
11+
from renku_data_services.notebooks.api.classes.repository import GitProvider, Repository
12+
from renku_data_services.notebooks.config import NotebooksConfig
1013

1114
if TYPE_CHECKING:
1215
# NOTE: If these are directly imported then you get circular imports.
1316
from renku_data_services.notebooks.api.classes.server import UserServer
1417

1518

16-
async def main_container(server: "UserServer") -> client.V1Container | None:
19+
async def main_container(
20+
user: AnonymousAPIUser | AuthenticatedAPIUser,
21+
config: NotebooksConfig,
22+
repositories: list[Repository],
23+
git_providers: list[GitProvider],
24+
) -> client.V1Container | None:
1725
"""The patch that adds the git proxy container to a session statefulset."""
18-
repositories = await server.repositories()
19-
if not server.user.is_authenticated or not repositories:
26+
if not user.is_authenticated or not repositories or user.access_token is None or user.refresh_token is None:
2027
return None
2128

2229
etc_cert_volume_mount = get_certificates_volume_mounts(
23-
server.config,
30+
config,
2431
custom_certs=False,
2532
etc_certs=True,
2633
read_only_etc_certs=True,
2734
)
2835

2936
prefix = "GIT_PROXY_"
30-
git_providers = await server.git_providers()
31-
repositories = await server.repositories()
3237
env = [
33-
client.V1EnvVar(name=f"{prefix}PORT", value=str(server.config.sessions.git_proxy.port)),
34-
client.V1EnvVar(name=f"{prefix}HEALTH_PORT", value=str(server.config.sessions.git_proxy.health_port)),
38+
client.V1EnvVar(name=f"{prefix}PORT", value=str(config.sessions.git_proxy.port)),
39+
client.V1EnvVar(name=f"{prefix}HEALTH_PORT", value=str(config.sessions.git_proxy.health_port)),
3540
client.V1EnvVar(
3641
name=f"{prefix}ANONYMOUS_SESSION",
37-
value="false" if server.user.is_authenticated else "true",
42+
value="false" if user.is_authenticated else "true",
3843
),
39-
client.V1EnvVar(name=f"{prefix}RENKU_ACCESS_TOKEN", value=str(server.user.access_token)),
40-
client.V1EnvVar(name=f"{prefix}RENKU_REFRESH_TOKEN", value=str(server.user.refresh_token)),
41-
client.V1EnvVar(name=f"{prefix}RENKU_REALM", value=server.config.keycloak_realm),
44+
client.V1EnvVar(name=f"{prefix}RENKU_ACCESS_TOKEN", value=str(user.access_token)),
45+
client.V1EnvVar(name=f"{prefix}RENKU_REFRESH_TOKEN", value=str(user.refresh_token)),
46+
client.V1EnvVar(name=f"{prefix}RENKU_REALM", value=config.keycloak_realm),
4247
client.V1EnvVar(
4348
name=f"{prefix}RENKU_CLIENT_ID",
44-
value=str(server.config.sessions.git_proxy.renku_client_id),
49+
value=str(config.sessions.git_proxy.renku_client_id),
4550
),
4651
client.V1EnvVar(
4752
name=f"{prefix}RENKU_CLIENT_SECRET",
48-
value=str(server.config.sessions.git_proxy.renku_client_secret),
53+
value=str(config.sessions.git_proxy.renku_client_secret),
4954
),
50-
client.V1EnvVar(name=f"{prefix}RENKU_URL", value="https://" + server.config.sessions.ingress.host),
55+
client.V1EnvVar(name=f"{prefix}RENKU_URL", value="https://" + config.sessions.ingress.host),
5156
client.V1EnvVar(
5257
name=f"{prefix}REPOSITORIES",
5358
value=json.dumps([asdict(repo) for repo in repositories]),
@@ -60,7 +65,7 @@ async def main_container(server: "UserServer") -> client.V1Container | None:
6065
),
6166
]
6267
container = client.V1Container(
63-
image=server.config.sessions.git_proxy.image,
68+
image=config.sessions.git_proxy.image,
6469
security_context={
6570
"fsGroup": 100,
6671
"runAsGroup": 1000,
@@ -73,14 +78,14 @@ async def main_container(server: "UserServer") -> client.V1Container | None:
7378
liveness_probe={
7479
"httpGet": {
7580
"path": "/health",
76-
"port": server.config.sessions.git_proxy.health_port,
81+
"port": config.sessions.git_proxy.health_port,
7782
},
7883
"initialDelaySeconds": 3,
7984
},
8085
readiness_probe={
8186
"httpGet": {
8287
"path": "/health",
83-
"port": server.config.sessions.git_proxy.health_port,
88+
"port": config.sessions.git_proxy.health_port,
8489
},
8590
"initialDelaySeconds": 3,
8691
},
@@ -98,7 +103,8 @@ async def main(server: "UserServer") -> list[dict[str, Any]]:
98103
if not server.user.is_authenticated or not repositories:
99104
return []
100105

101-
container = await main_container(server)
106+
git_providers = await server.git_providers()
107+
container = await main_container(server.user, server.config, repositories, git_providers)
102108
if not container:
103109
return []
104110

components/renku_data_services/notebooks/api/amalthea_patches/init_containers.py

Lines changed: 35 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,72 +3,77 @@
33
import json
44
import os
55
from dataclasses import asdict
6-
from pathlib import Path
6+
from pathlib import Path, PurePosixPath
77
from typing import TYPE_CHECKING, Any
88

99
from kubernetes import client
1010

11+
from renku_data_services.base_models.core import AnonymousAPIUser, AuthenticatedAPIUser
1112
from renku_data_services.notebooks.api.amalthea_patches.utils import get_certificates_volume_mounts
12-
from renku_data_services.notebooks.config import _NotebooksConfig
13+
from renku_data_services.notebooks.api.classes.repository import GitProvider, Repository
14+
from renku_data_services.notebooks.config import NotebooksConfig
1315

1416
if TYPE_CHECKING:
1517
# NOTE: If these are directly imported then you get circular imports.
1618
from renku_data_services.notebooks.api.classes.server import UserServer
1719

1820

19-
async def git_clone_container_v2(server: "UserServer") -> dict[str, Any] | None:
21+
async def git_clone_container_v2(
22+
user: AuthenticatedAPIUser | AnonymousAPIUser,
23+
config: NotebooksConfig,
24+
repositories: list[Repository],
25+
git_providers: list[GitProvider],
26+
workspace_mount_path: PurePosixPath,
27+
work_dir: PurePosixPath,
28+
lfs_auto_fetch: bool = False,
29+
) -> dict[str, Any] | None:
2030
"""Returns the specification for the container that clones the user's repositories for new operator."""
2131
amalthea_session_work_volume: str = "amalthea-volume"
22-
repositories = await server.repositories()
2332
if not repositories:
2433
return None
2534

2635
etc_cert_volume_mount = get_certificates_volume_mounts(
27-
server.config,
36+
config,
2837
custom_certs=False,
2938
etc_certs=True,
3039
read_only_etc_certs=True,
3140
)
3241

33-
user_is_anonymous = not server.user.is_authenticated
3442
prefix = "GIT_CLONE_"
3543
env = [
36-
{
37-
"name": f"{prefix}WORKSPACE_MOUNT_PATH",
38-
"value": server.workspace_mount_path.as_posix(),
39-
},
44+
{"name": f"{prefix}WORKSPACE_MOUNT_PATH", "value": workspace_mount_path.as_posix()},
4045
{
4146
"name": f"{prefix}MOUNT_PATH",
42-
"value": server.work_dir.as_posix(),
47+
"value": work_dir.as_posix(),
4348
},
4449
{
4550
"name": f"{prefix}LFS_AUTO_FETCH",
46-
"value": "1" if server.server_options.lfs_auto_fetch else "0",
51+
"value": "1" if lfs_auto_fetch else "0",
4752
},
4853
{
4954
"name": f"{prefix}USER__USERNAME",
50-
"value": server.user.email,
55+
"value": user.email,
5156
},
5257
{
5358
"name": f"{prefix}USER__RENKU_TOKEN",
54-
"value": str(server.user.access_token),
59+
"value": str(user.access_token),
5560
},
56-
{"name": f"{prefix}IS_GIT_PROXY_ENABLED", "value": "0" if user_is_anonymous else "1"},
61+
{"name": f"{prefix}IS_GIT_PROXY_ENABLED", "value": "0" if user.is_anonymous else "1"},
5762
{
5863
"name": f"{prefix}SENTRY__ENABLED",
59-
"value": str(server.config.sessions.git_clone.sentry.enabled).lower(),
64+
"value": str(config.sessions.git_clone.sentry.enabled).lower(),
6065
},
6166
{
6267
"name": f"{prefix}SENTRY__DSN",
63-
"value": server.config.sessions.git_clone.sentry.dsn,
68+
"value": config.sessions.git_clone.sentry.dsn,
6469
},
6570
{
6671
"name": f"{prefix}SENTRY__ENVIRONMENT",
67-
"value": server.config.sessions.git_clone.sentry.env,
72+
"value": config.sessions.git_clone.sentry.env,
6873
},
6974
{
7075
"name": f"{prefix}SENTRY__SAMPLE_RATE",
71-
"value": str(server.config.sessions.git_clone.sentry.sample_rate),
76+
"value": str(config.sessions.git_clone.sentry.sample_rate),
7277
},
7378
{"name": "SENTRY_RELEASE", "value": os.environ.get("SENTRY_RELEASE")},
7479
{
@@ -80,12 +85,12 @@ async def git_clone_container_v2(server: "UserServer") -> dict[str, Any] | None:
8085
"value": str(Path(etc_cert_volume_mount[0]["mountPath"]) / "ca-certificates.crt"),
8186
},
8287
]
83-
if server.user.is_authenticated:
84-
if server.user.email:
88+
if user.is_authenticated:
89+
if user.email:
8590
env.append(
86-
{"name": f"{prefix}USER__EMAIL", "value": server.user.email},
91+
{"name": f"{prefix}USER__EMAIL", "value": user.email},
8792
)
88-
full_name = server.user.get_full_name()
93+
full_name = user.get_full_name()
8994
if full_name:
9095
env.append(
9196
{
@@ -105,7 +110,8 @@ async def git_clone_container_v2(server: "UserServer") -> dict[str, Any] | None:
105110
)
106111

107112
# Set up git providers
108-
required_git_providers = await server.required_git_providers()
113+
required_provider_ids: set[str] = {r.provider for r in repositories if r.provider}
114+
required_git_providers = [p for p in git_providers if p.id in required_provider_ids]
109115
for idx, provider in enumerate(required_git_providers):
110116
obj_env = f"{prefix}GIT_PROVIDERS_{idx}_"
111117
data = dict(id=provider.id, access_token_url=provider.access_token_url)
@@ -117,7 +123,7 @@ async def git_clone_container_v2(server: "UserServer") -> dict[str, Any] | None:
117123
)
118124

119125
return {
120-
"image": server.config.sessions.git_clone.image,
126+
"image": config.sessions.git_clone.image,
121127
"name": "git-clone",
122128
"resources": {
123129
"requests": {
@@ -134,7 +140,7 @@ async def git_clone_container_v2(server: "UserServer") -> dict[str, Any] | None:
134140
},
135141
"volumeMounts": [
136142
{
137-
"mountPath": server.workspace_mount_path.as_posix(),
143+
"mountPath": workspace_mount_path.as_posix(),
138144
"name": amalthea_session_work_volume,
139145
},
140146
*etc_cert_volume_mount,
@@ -156,7 +162,6 @@ async def git_clone_container(server: "UserServer") -> dict[str, Any] | None:
156162
read_only_etc_certs=True,
157163
)
158164

159-
user_is_anonymous = not server.user.is_authenticated
160165
prefix = "GIT_CLONE_"
161166
env = [
162167
{
@@ -179,7 +184,7 @@ async def git_clone_container(server: "UserServer") -> dict[str, Any] | None:
179184
"name": f"{prefix}USER__RENKU_TOKEN",
180185
"value": str(server.user.access_token),
181186
},
182-
{"name": f"{prefix}IS_GIT_PROXY_ENABLED", "value": "0" if user_is_anonymous else "1"},
187+
{"name": f"{prefix}IS_GIT_PROXY_ENABLED", "value": "0" if server.user.is_anonymous else "1"},
183188
{
184189
"name": f"{prefix}SENTRY__ENABLED",
185190
"value": str(server.config.sessions.git_clone.sentry.enabled).lower(),
@@ -288,7 +293,7 @@ async def git_clone(server: "UserServer") -> list[dict[str, Any]]:
288293
]
289294

290295

291-
def certificates_container(config: _NotebooksConfig) -> tuple[client.V1Container, list[client.V1Volume]]:
296+
def certificates_container(config: NotebooksConfig) -> tuple[client.V1Container, list[client.V1Volume]]:
292297
"""The specification for the container that setups self signed CAs."""
293298
init_container = client.V1Container(
294299
name="init-certificates",
@@ -321,7 +326,7 @@ def certificates_container(config: _NotebooksConfig) -> tuple[client.V1Container
321326
return (init_container, [volume_etc_certs, volume_custom_certs])
322327

323328

324-
def certificates(config: _NotebooksConfig) -> list[dict[str, Any]]:
329+
def certificates(config: NotebooksConfig) -> list[dict[str, Any]]:
325330
"""Add a container that initializes custom certificate authorities for a session."""
326331
container, vols = certificates_container(config)
327332
api_client = client.ApiClient()

components/renku_data_services/notebooks/api/amalthea_patches/ssh.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
from typing import Any
44

5-
from renku_data_services.notebooks.config import _NotebooksConfig
5+
from renku_data_services.notebooks.config import NotebooksConfig
66

77

8-
def main(config: _NotebooksConfig) -> list[dict[str, Any]]:
8+
def main(config: NotebooksConfig) -> list[dict[str, Any]]:
99
"""Adds the required configuration to the session statefulset for SSH access."""
1010
if not config.sessions.ssh.enabled:
1111
return []

components/renku_data_services/notebooks/api/amalthea_patches/utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44

55
from kubernetes import client
66

7-
from renku_data_services.notebooks.config import _NotebooksConfig
7+
from renku_data_services.notebooks.config import NotebooksConfig
88

99

1010
def get_certificates_volume_mounts(
11-
config: _NotebooksConfig,
11+
config: NotebooksConfig,
1212
etc_certs: bool = True,
1313
custom_certs: bool = True,
1414
read_only_etc_certs: bool = False,

components/renku_data_services/notebooks/api/classes/server.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from renku_data_services.notebooks.api.classes.repository import GitProvider, Repository
2626
from renku_data_services.notebooks.api.schemas.secrets import K8sUserSecrets
2727
from renku_data_services.notebooks.api.schemas.server_options import ServerOptions
28-
from renku_data_services.notebooks.config import _NotebooksConfig
28+
from renku_data_services.notebooks.config import NotebooksConfig
2929
from renku_data_services.notebooks.crs import JupyterServerV1Alpha1
3030
from renku_data_services.notebooks.errors.programming import DuplicateEnvironmentVariableError
3131
from renku_data_services.notebooks.errors.user import MissingResourceError
@@ -46,7 +46,7 @@ def __init__(
4646
k8s_client: K8sClient,
4747
workspace_mount_path: PurePosixPath,
4848
work_dir: PurePosixPath,
49-
config: _NotebooksConfig,
49+
config: NotebooksConfig,
5050
internal_gitlab_user: APIUser,
5151
using_default_image: bool = False,
5252
is_image_private: bool = False,
@@ -380,7 +380,7 @@ def __init__(
380380
k8s_client: K8sClient,
381381
workspace_mount_path: PurePosixPath,
382382
work_dir: PurePosixPath,
383-
config: _NotebooksConfig,
383+
config: NotebooksConfig,
384384
gitlab_project: Project | None,
385385
internal_gitlab_user: APIUser,
386386
using_default_image: bool = False,
@@ -506,7 +506,7 @@ def __init__(
506506
workspace_mount_path: PurePosixPath,
507507
work_dir: PurePosixPath,
508508
repositories: list[Repository],
509-
config: _NotebooksConfig,
509+
config: NotebooksConfig,
510510
internal_gitlab_user: APIUser,
511511
using_default_image: bool = False,
512512
is_image_private: bool = False,

0 commit comments

Comments
 (0)