diff --git a/applications/accounts-api/Dockerfile b/applications/accounts-api/Dockerfile index 0d8f6e645..c3f9ea104 100644 --- a/applications/accounts-api/Dockerfile +++ b/applications/accounts-api/Dockerfile @@ -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 diff --git a/applications/accounts-api/backend/requirements.txt b/applications/accounts-api/backend/requirements.txt index f9289e172..68760177c 100644 --- a/applications/accounts-api/backend/requirements.txt +++ b/applications/accounts-api/backend/requirements.txt @@ -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 \ No newline at end of file diff --git a/applications/accounts-api/backend/setup.py b/applications/accounts-api/backend/setup.py index e16a0a2e5..705dcf730 100644 --- a/applications/accounts-api/backend/setup.py +++ b/applications/accounts-api/backend/setup.py @@ -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( diff --git a/applications/accounts/deploy/resources/realm.json b/applications/accounts/deploy/resources/realm.json index 80888301c..c9d38951e 100644 --- a/applications/accounts/deploy/resources/realm.json +++ b/applications/accounts/deploy/resources/realm.json @@ -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": [ diff --git a/applications/jupyterlab/Dockerfile b/applications/jupyterlab/Dockerfile index 9799c8b93..3b142019c 100644 --- a/applications/jupyterlab/Dockerfile +++ b/applications/jupyterlab/Dockerfile @@ -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 diff --git a/applications/jupyterlab/requirements.txt b/applications/jupyterlab/requirements.txt index cce0760db..cd64d940d 100644 --- a/applications/jupyterlab/requirements.txt +++ b/applications/jupyterlab/requirements.txt @@ -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 diff --git a/applications/netpyne/Dockerfile b/applications/netpyne/Dockerfile index eca06eea7..f5788e99a 100644 --- a/applications/netpyne/Dockerfile +++ b/applications/netpyne/Dockerfile @@ -1,6 +1,6 @@ -FROM node:18 AS jsbuild +FROM node:16-bullseye AS jsbuild ARG APP_DIR=dependencies/NetPyNE-UI ENV FOLDER=netpyne diff --git a/applications/netpyne/dependencies/NetPyNE-UI b/applications/netpyne/dependencies/NetPyNE-UI deleted file mode 160000 index 73c357ce0..000000000 --- a/applications/netpyne/dependencies/NetPyNE-UI +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 73c357ce07c9ddc6b2a77bfa32fd5e511e389818 diff --git a/applications/netpyne/deploy/values.yaml b/applications/netpyne/deploy/values.yaml index 542737608..69f9a87d5 100644 --- a/applications/netpyne/deploy/values.yaml +++ b/applications/netpyne/deploy/values.yaml @@ -1,7 +1,5 @@ harness: subdomain: netpyne - secured: true - uri_role_mapping: [] service: auto: false port: 80 diff --git a/applications/nfsserver/deploy/values-dev.yaml b/applications/nfsserver/deploy/values-dev.yaml index 128b140b1..434336ef7 100644 --- a/applications/nfsserver/deploy/values-dev.yaml +++ b/applications/nfsserver/deploy/values-dev.yaml @@ -1,4 +1,4 @@ nfs: useDNS: true server: - diskSize: 80Gi \ No newline at end of file + diskSize: 160Gi \ No newline at end of file diff --git a/applications/nfsserver/deploy/values.yaml b/applications/nfsserver/deploy/values.yaml index 516975127..905cfc9a8 100644 --- a/applications/nfsserver/deploy/values.yaml +++ b/applications/nfsserver/deploy/values.yaml @@ -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 diff --git a/applications/osb-portal/src/components/workspace/WorkspaceFrame.tsx b/applications/osb-portal/src/components/workspace/WorkspaceFrame.tsx index cc20e2bfe..b0bf46ba5 100644 --- a/applications/osb-portal/src/components/workspace/WorkspaceFrame.tsx +++ b/applications/osb-portal/src/components/workspace/WorkspaceFrame.tsx @@ -3,6 +3,10 @@ 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, @@ -10,6 +14,7 @@ import { ResourceStatus, } from "../../types/workspace"; import { UserInfo } from "../../types/user"; +import workspaceService from "../../service/WorkspaceService"; import WorkspaceResourceService from "../../service/WorkspaceResourceService"; import { getBaseDomain, getApplicationDomain } from "../../utils"; @@ -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; @@ -57,7 +64,7 @@ export const WorkspaceFrame = (props: { "workspace-frame" ) as HTMLIFrameElement; const messageListener = (message: MessageEvent) => { - if (message.source !== iFrame.contentWindow) { + if (!iFrame || message.source !== iFrame.contentWindow) { return; } console.debug("Message", message); @@ -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 ( + + + The workspace is temporarily unavailable + + + The workspace storage is not ready yet. This is usually temporary — + please try again in a few seconds. + + + + ); + } + return (