Skip to content

Latest commit

 

History

86 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Deploying Multi-Dimensional Viewer (MDV) via OpenOnDemand

icon

Multi-Dimensional Viewer (MDV) is a tool for analyzing, annotating and sharing multi-dimensional data.

This repository packages MDV as an OpenOnDemand interactive app. MDV runs inside an Apptainer container on a compute node, with a small proxy in front of Flask to make its absolute asset paths work behind rnode. Developed and in production on the BMRC cluster at the University of Oxford, and intended to be portable to other OOD sites. For MDV itself, see the upstream project.

License & Attribution

The MIT license in this repository applies solely to the deployment scripts, configuration files, and documentation provided here for running MDV (https://mdv.ndm.ox.ac.uk/) via OpenOnDemand. MDV itself is not covered by this license.

All intellectual property rights for MDV remain with the original authors. Please refer to the original license before using, modifying, or redistributing MDV.

How a session starts

flowchart LR
    B[Browser] -->|/rnode/host/port/...| R[OOD rnode]
    R -->|strips prefix| P["proxy.py<br/>BIND_PORT"]
    P -->|rewrites asset paths| F["Flask + Gunicorn<br/>BIND_PORT + 1"]
Loading

On the compute node, inside one Apptainer container:

Component Notes
Flask + Gunicorn gevent worker, serves the app and API
Vite frontend Built at image time into /app/dist
PostgreSQL Initialised per session, not a sidecar
Projects directory On GPFS, supplied by the user — persists across sessions

The database is an index rebuilt from the projects directory at startup. The projects directory is the only thing that has to survive.

Version handling

The container path lives in an Lmod module, not in the app config. The form offers a version dropdown; the chosen module is loaded at launch and exports MDV_SIF.

-- MDV/main.lua
local version = "main"
local sif = "/apps/.../MDV/mdv-" .. version .. ".sif"

setenv("MDV_SIF",     sif)
setenv("MDV_VERSION", version)
# template/script.sh.erb
module load <%= context.mdv_module %>
apptainer run ... "${MDV_SIF}"

To add a version: build a SIF, drop in a .lua, add one line to the form dropdown. Nothing in the launch script changes.

template/proxy.py

MDV is designed to run as a standalone web server at the root path (/), with its Vite-built frontend referencing static assets using absolute URLs such as /flask/assets/catalog.css and /flask/js/mdv.js.

OpenOnDemand's reverse proxy (rnode) works by routing requests through a URL of the form /rnode/<hostname>/<port>/path on the OOD server, stripping the /rnode/<hostname>/<port> prefix before forwarding to the application. This means the application itself always receives requests at the correct path — but the browser does not know about the stripping. When the browser loads a page served at https://ood-server/rnode/host/port/, it resolves absolute asset paths such as
/flask/assets/catalog.css relative to the OOD server root, not through the rnode proxy. The result is 404 or 500 errors for all static assets, leaving the application as a blank page.

A secondary complication arises from nested routes. When a user opens a project, the page URL deepens to something similar to /rnode/host/port/project/1/. Simply converting absolute paths to relative ones (e.g. flask/assets/catalog.css) does not solve the problem here, because the browser would then resolve those relative paths against the current sub-path, producing incorrect URLs such as /rnode/host/port/project/1/flask/assets/catalog.css.

proxy.py solves this by sitting between OOD's rnode proxy and the MDV Flask application. Flask runs on an internal port (BIND_PORT + 1), invisible to OOD. The proxy listens on BIND_PORT (the port OOD knows about) and forwards all requests to Flask. For HTML responses specifically, it rewrites every absolute /flask/ asset reference to a fully-qualified r node-prefixed path — for example /flask/assets/catalog.css becomes /rnode/<hostname>/<port>/flask/assets/catalog.css. Because these are now absolute URLs that include the full rnode path, the browser routes them correctly through OOD's proxy to Flask regardless of the current page depth.

WebSocket connections (used by MDV's socket.io real-time features) are handled separately via a raw socket bridge, since HTTP-level proxying cannot perform the WebSocket protocol upgrade.

Constraints — don't undo these

Rewrite to the absolute rnode-prefixed path, not a relative one. See the nested-route problem above. Relative paths appear to work until someone opens a project.

Strip Content-Encoding on the way out. Flask gzips responses; urllib silently decompresses them. Forwarding the original Content-Encoding header makes the browser try to gunzip plain bytes. Drop the header and send a fresh Content-Length.

Runtime data

MDV's internal data — the PostgreSQL cluster, logs and caches — lives in a per-session directory on node-local /tmp, keyed by the OOD session ID. Cleanup is via a trap on exit, with a stale-directory sweep as a backstop.

The form asks only for the project directory, matching standard MDV startup.

Why not a user-supplied data directory?

An earlier version had a second form field for a BMRC path. Three problems:

  1. Shared paths collide. Two sessions pointed at one data directory clash on the PostgreSQL data directory and port 5432.
  2. Group-write is not inherited. On shared directories, group-write permissions aren't inherited by default — sharing a directory meant manual checking each time.
  3. Divergence from upstream. Standard MDV startup asks only for the project directory.

Important

Caches are rebuilt each session, so expect a ~90 s numba recompile on first launch. This is the trade-off for the /tmp move, not a bug. Projects persist regardless — the database is rebuilt from the projects directory.

Container gotchas

Relevant when rebuilding the SIF from the upstream Dockerfile.

Apptainer has no build stages. Multi-stage Docker builds have to be flattened; the whole build runs sequentially in %post.

PostgreSQL over TCP, not a Unix socket. MDV constructs a postgresql://host/db URL, so a socket path won't fit.

/var/run is read-only. Redirect the PostgreSQL lock file to writable space.

About

Multi-Dimensional Viewer (MDV) app for OOD

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages