Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
10c50e5
client configuration and cors for ingress for credentials
ddelpiano Mar 19, 2026
3a2e5bf
netpyne revision
ddelpiano Mar 24, 2026
053f9ed
fixing permission issue
ddelpiano Mar 25, 2026
ca40727
lower neuron version below 9
ddelpiano Mar 25, 2026
0cb8717
fixing netpyne build
ddelpiano Mar 25, 2026
7234db9
node option removed
ddelpiano Mar 25, 2026
b7888c3
Merge branch 'develop' of https://github.com/opensourcebrain/osbv2 in…
ddelpiano Mar 25, 2026
07dc7c0
Accounts-api update connexion
filippomc Mar 26, 2026
ebfdde0
Accounts-api update entrypoint
filippomc Mar 26, 2026
6d1e25f
adding required scopes for idp arc
ddelpiano Mar 26, 2026
c85806b
purge immutable changes due to CH changes
ddelpiano Mar 28, 2026
69a41da
purge immutable changes due to CH changes 2
ddelpiano Mar 28, 2026
fd499f6
remove unmutable cleanup step
ddelpiano Mar 28, 2026
508af4a
Fix issue with regex escape character
filippomc Apr 22, 2026
bac79d5
Improve workspace resources requests
filippomc Apr 22, 2026
3fade69
Update NFS server
filippomc Apr 27, 2026
8573bc1
Dev pipeline update
filippomc Apr 29, 2026
a0d7002
Merge branch 'feature/idp' of github.com:OpenSourceBrain/OSBv2 into f…
filippomc Apr 29, 2026
50d891c
Dev pipeline fix
filippomc Apr 29, 2026
69125ac
Dev deployment fix to match storage
filippomc Apr 29, 2026
10ac8ef
Increase resources for stability
filippomc Jun 29, 2026
8d10e2a
Handle resilient workspace pvc recreation
filippomc Jun 30, 2026
2146bbd
Fix netpyne embedding
filippomc Jun 30, 2026
ed633c4
Netpyne build fix
filippomc Jun 30, 2026
950a315
Fix user page issue
filippomc Jul 20, 2026
2b89c5e
Set minimum hub requirements
filippomc Jul 20, 2026
c8e7a1a
Reconfigure sentry, improve robustness
filippomc Jul 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions applications/accounts-api/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,3 @@ RUN pip3 install --no-cache-dir -r requirements.txt
COPY backend /usr/src/app

RUN pip3 install -e .

ENTRYPOINT gunicorn --workers=$WORKERS --bind=0.0.0.0:$PORT $MODULE_NAME.__main__:app
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,15 @@ def get_users(query: str) -> typing.List[User]:


def map_user(kc_user) -> User:
user = User.from_dict(kc_user if isinstance(kc_user, dict) else kc_user._raw_dict)
if isinstance(kc_user, dict):
raw = kc_user
else:
try:
raw = kc_user._raw_dict
except AttributeError:
# cloudharness model base changed the raw dict attribute name
raw = kc_user.var__raw_dict
user = User.from_dict(raw)
if 'attributes' not in kc_user or not kc_user['attributes']:
kc_user['attributes'] = {}

Expand Down
20 changes: 9 additions & 11 deletions applications/accounts-api/backend/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
connexion[swagger-ui] >= 2.6.0; python_version>="3.6"
# 2.3 is the last version that supports python 3.4-3.5
connexion[swagger-ui] <= 2.3.0; python_version=="3.5" or python_version=="3.4"
# connexion requires werkzeug but connexion < 2.4.0 does not install werkzeug
# we must peg werkzeug versions below to fix connexion
# https://github.com/zalando/connexion/pull/1044
werkzeug == 2.2.3; python_version=="3.5" or python_version=="3.4"
swagger-ui-bundle >= 0.0.2
python_dateutil >= 2.6.0
setuptools >= 21.0.0
cloudharness
connexion[swagger-ui,flask,uvicorn]>=3.0.0,<4.0.0
swagger-ui-bundle>=1.1.0
python_dateutil>=2.9.0
setuptools>=21.0.0
uvicorn
# Following some unnecessary requirements to make sure they can be installed
psycopg2-binary
sqlalchemy<2.0.0
scipy
8 changes: 4 additions & 4 deletions applications/accounts-api/backend/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@
# http://pypi.python.org/pypi/setuptools

REQUIRES = [
"connexion>=2.0.2",
"swagger-ui-bundle>=0.0.2",
"python_dateutil>=2.6.0",
"cloudharness"
"connexion[swagger-ui,flask,uvicorn]>=3.0.0,<4.0.0",
"Flask",
"swagger-ui-bundle",
"cloudharness",
]

setup(
Expand Down
28 changes: 28 additions & 0 deletions applications/accounts/deploy/resources/realm.json
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,34 @@
"offline_access",
"microprofile-jwt"
]
},
{
"clientId": "idp-arc",
"name": "idp-arc",
"enabled": true,
"publicClient": true,
"standardFlowEnabled": true,
"implicitFlowEnabled": false,
"directAccessGrantsEnabled": false,
"serviceAccountsEnabled": false,
"protocol": "openid-connect",
"rootUrl": "{{ .Values.idpArc.rootUrl }}",
"redirectUris": [
"{{ .Values.idpArc.rootUrl }}/*"
],
"webOrigins": [
"{{ .Values.idpArc.rootUrl }}"
],
"attributes": {
"pkce.code.challenge.method": "S256"
},
"fullScopeAllowed": true,
"defaultClientScopes": [
"web-origins", "profile", "roles", "email"
],
"optionalClientScopes": [
"offline_access", "{{ .Values.apps.accounts.admin.role }}-scope"
]
}
],
"clientScopes": [
Expand Down
32 changes: 32 additions & 0 deletions applications/jupyterhub/deploy/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,30 @@ harness:
quota-storage-max : 1.25
nfs_volumes: false
hub:
# Control-plane sizing to sustain ~20 concurrent users. The hub coordinates
# auth/spawn and talks to the sqlite DB; without explicit resources it was
# CPU-throttled/OOM-killed under load, which surfaced downstream as 503s.
resources:
requests:
cpu: 0.1
memory: 1Gi
limits:
cpu: 4
memory: 1Gi
config:
Authenticator:
# Users granted JupyterHub admin rights.
admin_users:
- a
- filippo@metacell.us
JupyterHub:
admin_access: true
authenticator_class: ch
# Cap simultaneous spawns so a burst of logins doesn't stampede the
# hub/proxy; extra spawns queue instead of overwhelming the control plane.
concurrent_spawn_limit: 10
# Headroom above the 20-user target (named servers count individually).
active_server_limit: 40
extraConfig:
timing: |
c.Spawner.port = 8000
Expand All @@ -42,6 +62,18 @@ hub:
c.JupyterHub.tornado_settings = { "headers": { "Content-Security-Policy": "frame-ancestors 'self' localhost localhost:3000 *.osb.local *.metacell.us *.opensourcebrain.org *.v2.opensourcebrain.org"}}
spawner: >-
c.Spawner.args = []
proxy:
# configurable-http-proxy (CHP) fronts every request and returns 503 when it
# is starved or the hub behind it is unreachable. Give it guaranteed capacity
# so it stays responsive while routing ~20 concurrent users.
chp:
resources:
requests:
cpu: 0.2
memory: 256Mi
limits:
cpu: 1
memory: 512Mi
singleuser:
storage:
type: dynamic
Expand Down
2 changes: 1 addition & 1 deletion applications/jupyterhub/kubespawner
Submodule kubespawner updated from 812d30 to bd460b
6 changes: 3 additions & 3 deletions applications/jupyterlab/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -77,15 +77,15 @@ USER root
RUN conda install conda-forge::nest-simulator
ENV NEST_HOME=/opt/conda

USER jovyan

#### Install Python packages
#### Install Python packages (as root to avoid permission issues with conda-managed packages)

RUN --mount=type=cache,target=/root/.cache python -m pip install --upgrade pip && \
pip install backports.tarfile>=1.2 # temp fix for error: ImportError: cannot import name 'tarfile' from 'backports'
RUN --mount=type=cache,target=/root/.cache python -m pip install --upgrade pip &&\
pip install -r requirements.txt --upgrade --no-cache-dir

USER jovyan

# Compile NEURON mod files for PyNN
RUN cd /opt/conda/lib/python3.12/site-packages/pyNN/neuron/nmodl && nrnivmodl

Expand Down
2 changes: 1 addition & 1 deletion applications/jupyterlab/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ pyelectro
neurotune

#### NEURON & NetPyNE
neuron
neuron<9

# Install specific version of NetPyNE
git+https://github.com/Neurosim-lab/netpyne.git@osbv2#egg=netpyne
Expand Down
2 changes: 1 addition & 1 deletion applications/netpyne/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@


FROM node:18 AS jsbuild
FROM node:16-bullseye AS jsbuild
ARG APP_DIR=dependencies/NetPyNE-UI
ENV FOLDER=netpyne

Expand Down
1 change: 0 additions & 1 deletion applications/netpyne/dependencies/NetPyNE-UI
Submodule NetPyNE-UI deleted from 73c357
2 changes: 0 additions & 2 deletions applications/netpyne/deploy/values.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
harness:
subdomain: netpyne
secured: true
uri_role_mapping: []
service:
auto: false
port: 80
Expand Down
2 changes: 1 addition & 1 deletion applications/nfsserver/deploy/values-dev.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
nfs:
useDNS: true
server:
diskSize: 80Gi
diskSize: 160Gi
2 changes: 1 addition & 1 deletion applications/nfsserver/deploy/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
harness:
deployment:
auto: false
image: gcr.io/metacellllc/osb/nfsserver:0.7.6
image: gcr.io/metacellllc/cloud-harness/nfsserver:featurech-261
# nfs server pvc disk size (/exports)
server:
diskSize: 240Gi
101 changes: 88 additions & 13 deletions applications/osb-portal/src/components/workspace/WorkspaceFrame.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@ import { useParams } from "react-router-dom";
import { AnyAction, Dispatch } from "redux";
import { PayloadAction } from "@reduxjs/toolkit";

import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import Typography from "@mui/material/Typography";

import {
Workspace,
WorkspaceResource,
OSBApplications,
ResourceStatus,
} from "../../types/workspace";
import { UserInfo } from "../../types/user";
import workspaceService from "../../service/WorkspaceService";
import WorkspaceResourceService from "../../service/WorkspaceResourceService";
import { getBaseDomain, getApplicationDomain } from "../../utils";

Expand All @@ -30,6 +35,8 @@ export const WorkspaceFrame = (props: {
currentResource: WorkspaceResource;
}) => {
const [frameUrl, setFrameUrl] = React.useState(null);
const [frameError, setFrameError] = React.useState(false);
const [retryToken, setRetryToken] = React.useState(0);
const { app } = useParams<{ app: string }>();

const { user, workspace, dispatch, currentResource } = props;
Expand Down Expand Up @@ -57,7 +64,7 @@ export const WorkspaceFrame = (props: {
"workspace-frame"
) as HTMLIFrameElement;
const messageListener = (message: MessageEvent<PayloadAction>) => {
if (message.source !== iFrame.contentWindow) {
if (!iFrame || message.source !== iFrame.contentWindow) {
return;
}
console.debug("Message", message);
Expand Down Expand Up @@ -99,24 +106,92 @@ export const WorkspaceFrame = (props: {
}, [openResource]);

React.useEffect(() => {
const applicationDomain = getApplicationDomain(application);
const domain = getBaseDomain();

const userParam = user == null ? "" : `${user.id}`;
const type = application.subdomain.slice(0, 4);
document.cookie = `workspaceId=${workspace.id};path=/;domain=${domain}`;
if(applicationDomain) {
setFrameUrl(`//${applicationDomain}/hub/spawn/${userParam}/${workspace.id}${type}${document.location.search ?? ''}`);
} else {
setFrameUrl(`/testapp?workspaceId=${workspace.id}&type=${type}&user=${userParam}&application=${application.name}`);
}
let cancelled = false;
setFrameError(false);
setFrameUrl(null);

const spawn = async () => {
// Make sure the workspace volume is created and ready (bound) before we
// spawn the iframe, so the app pod does not fail to mount it.
try {
await workspaceService.ensureWorkspaceReady(workspace.id);
} catch (e) {
if (e?.volumeNotReady) {
// Transient: the storage is genuinely not bound yet. Surface a
// retryable error instead of spawning a pod that would fail to mount.
console.error("Workspace volume not ready, not spawning iframe", e);
if (!cancelled) {
setFrameError(true);
}
return;
}
// Any other failure (auth, network, 5xx) is unrelated to volume
// readiness; don't block workspace access — fall through and spawn the
// iframe as we did before this readiness check existed.
console.warn("Workspace readiness check failed, spawning anyway", e);
}
if (cancelled) {
return;
}

}, [application, user, workspace.id]);
const applicationDomain = getApplicationDomain(application);
const domain = getBaseDomain();

const userParam = user == null ? "" : `${user.id}`;
const type = application.subdomain.slice(0, 4);
document.cookie = `workspaceId=${workspace.id};path=/;domain=${domain}`;
if (applicationDomain) {
setFrameUrl(`//${applicationDomain}/hub/spawn/${userParam}/${workspace.id}${type}${document.location.search ?? ''}`);
} else {
setFrameUrl(`/testapp?workspaceId=${workspace.id}&type=${type}&user=${userParam}&application=${application.name}`);
}
};

spawn();

return () => {
cancelled = true;
};
// Depend on the primitive inputs the effect actually uses, not the
// `application`/`user` object identities — those are rebuilt on unrelated
// redux updates and would otherwise reset frameUrl to null (remounting the
// iframe via key={frameUrl}) and refire the /open fetch on every re-render.
}, [application?.subdomain, application?.name, user?.id, workspace.id, retryToken]);

if (!workspace) {
return null;
}

if (frameError) {
return (
<Box
display="flex"
flexDirection="column"
alignItems="center"
justifyContent="center"
flex={1}
gap={2}
p={4}
textAlign="center"
>
<Typography variant="h6">
The workspace is temporarily unavailable
</Typography>
<Typography variant="body1" color="textSecondary">
The workspace storage is not ready yet. This is usually temporary —
please try again in a few seconds.
</Typography>
<Button
variant="contained"
color="primary"
onClick={() => setRetryToken((token) => token + 1)}
>
Retry
</Button>
</Box>
);
}

return (
<iframe
id="workspace-frame"
Expand Down
28 changes: 13 additions & 15 deletions applications/osb-portal/src/service/ErrorHandleService.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
import * as Sentry from "@sentry/react";

export async function initErrorHandler(appName: string) {
const commonUrl =
window.location.origin + "/proxy/common/api/sentry/getdsn/" + appName;
fetch(commonUrl)
.then(
(response) => response.json(),
(error) => console.error("Cannot connect to common service")
)
.then(
(sentryDSN) => {
if (sentryDSN) {
Sentry.init({ dsn: sentryDSN.dsn });
}
},
(error) => console.error("Cannot connect to error monitor", error)
);
const dsn = import.meta.env.VITE_SENTRY_DSN;
if (!dsn) {
// No DSN configured for this deployment: leave Sentry uninitialized.
return;
}

Sentry.init({
dsn,
sampleRate: Number(import.meta.env.VITE_SENTRY_SAMPLE_RATE),
tracesSampleRate: Number(import.meta.env.VITE_SENTRY_TRACES_SAMPLE_RATE),
});
// Preserve the per-app attribution the previous per-app DSN provided.
Sentry.setTag("app", appName);
}
Loading
Loading