Skip to main content

Airavata persistence

Everything Airavata's session server (cs serve) and batch server (airavata-server) store. The procedures are in Backups and restore and Upgrades.

The two servers may share a database but not a schema: both define a table named ssh_keys, with different columns.

ServerConnectionSchemaQuery layer
cs serveCS_DATABASE_URL, one connectionCreated once in an empty schema; never migratedGo generated by sqlc from plain SQL
airavata-serverAIRAVATA_DB_DSN, up to 20 connectionsGORM AutoMigrate at startup, or versioned files through migrateGORM

Session tables​

Metadata lives in PostgreSQL, credentials in files, and log tails, usage samples and links in memory. CS_DATABASE_URL selects the schema through search_path. The server creates its tables only in an empty schema and refuses a non-empty one without its format marker (cs: unsupported state database format).

subsystems/session/schema.sql, subsystems/ssh/schema.sql
CREATE TABLE sessions (
id TEXT PRIMARY KEY,
owner TEXT NOT NULL,
payload TEXT NOT NULL
);

CREATE TABLE runs (
position BIGINT GENERATED ALWAYS AS IDENTITY,
session_id TEXT NOT NULL,
seq BIGINT NOT NULL,
owner TEXT NOT NULL,
payload TEXT NOT NULL,
PRIMARY KEY (session_id, seq)
);

CREATE TABLE ssh_hosts (
principal TEXT NOT NULL,
host TEXT NOT NULL,
payload TEXT NOT NULL,
PRIMARY KEY (principal, host)
);

-- An alias is unique regardless of case, as OpenSSH matches Host patterns.
CREATE UNIQUE INDEX ssh_hosts_alias ON ssh_hosts (principal, lower(host));

CREATE TABLE ssh_keys (
principal TEXT NOT NULL,
name TEXT NOT NULL,
type TEXT NOT NULL,
fingerprint TEXT NOT NULL,
PRIMARY KEY (principal, name)
);

internal/db adds schema_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL) holding ('format', 'sql-v1').

ColumnHolds
owner, principalThe principal hash: hex of the first 16 bytes of sha256(sub + "\0" + tenant), tenant cilogon
sessions.payloadThe session record as JSON, including owner, job ID, node and Dev Tunnel metadata
runs.payloadA finished run as JSON; position orders runs newest first, at most 200 across all callers
ssh_hosts.payloadThe parsed SSH host as JSON; the key's path is never stored

A row whose payload identity (id, session_id and seq, or host) disagrees with its key columns is refused, not served. To inspect a record, cast the column:

psql -d cybershuttle -c "SELECT id, payload::json->>'state', payload::json->>'jobId' FROM cs_plane.sessions"

Each subsystem owns its schema.sql and query.sql; go generate ./internal/db runs sqlc over sqlc.yaml and regenerates the committed query.sql.go, sql.go and sql_models.go.

SubsystemQueries
sessionListSessions, ListRuns (by position), ClearSessions, ClearRuns, InsertSession, InsertRun, GetSession, ListSessionsByOwner, ListRunsByOwner
sshListHosts (by lower(host)), ListHostPrincipals, InsertHost, UpdateHost, DeleteHost, GetKey, ListKeys, InsertKey, DeleteKey

Locked in internal/db serialises every read-modify-write cycle behind a process mutex and a flock on <state dir>/.lock. A session change deletes and reinserts every session and run in one transaction; reads are unlocked. psql takes neither lock, so change rows only while the server is stopped.

Session files​

The state directory is ~/.cybershuttle/control, mode 0700. The server refuses a private file that another user owns or that has any group or world permission bit, so restore the directory as the service account with its modes intact. In the paths below, <p> is the principal.

PathContents
.lockThe database cycle lock
.session-create-XX.lock64 lock slots (00 to 3f) serialising start and attach per session ID
.devtunnels-account-<p>.lockPer-principal Dev Tunnels credential lock
devtunnels-account.keyKey sealing every Dev Tunnels account, created at mode 0600 on first start
credentials/<session id>-<seq>.tokenJSON {jupyterToken, linkToken, connectToken?} for one run, mode 0600
hosts/<p>/configThe caller's rendered SSH configuration, mode 0600
hosts/<p>/keys/<id>Uploaded SSH private keys, mode 0600; .put-* staging and .delete-* tombstones during changes
hosts/<p>/devtunnels-accountThe sealed Dev Tunnels account, mode 0600

SSH control sockets live outside it, under $TMPDIR/cs-<uid>/ or /tmp/cs-<uid>/.

Session restarts​

A restart is safe while sessions run. At startup the session server, in order:

  1. Refuse a non-empty schema without format sql-v1, before creating any credential file.
  2. Resolve interrupted SSH key writes and deletions from committed metadata.
  3. Re-render every principal's SSH configuration from committed rows.

In-memory state is lost:

StateEffect
LinksLinkspan redials within seconds
Log tailsA live session's tail restarts empty
Usage samplesA live session's samples restart empty
Pending Dev Tunnels authorizationsThe user restarts the device flow
Control mastersInteractive masters end; BatchMode masters (ControlPersist=600) may survive up to ten idle minutes. Otherwise the user authenticates again

Batch tables​

airavata-server stores every batch resource in PostgreSQL through GORM (internal/db). The schema below is internal/db/migrations/0001_baseline.sql, recorded from AutoMigrate against PostgreSQL 17. Every record ID is a varchar(36) UUID. Columns naming a user (users.user_id, owner_id, other user_id columns) are varchar(255) holding the principal name, such as cilogon:12345 or root; job_id columns hold Slurm job IDs.

TablePrimary keyReferences (on delete)
usersuser_id
user_rolesuser_id, roleusers (cascade)
groupsgroup_idowner user_id → users (restrict)
group_membersgroup_id, user_idgroups (cascade), users (cascade)
ssh_keysssh_key_idowner_id → users (restrict)
slurm_clustersslurm_cluster_id
cluster_partitionspartition_idcluster_id → slurm_clusters (cascade)
slurm_cluster_configsslurm_cluster_config_idslurm_clusters, ssh_keys, owner users (all restrict)
slurm_cluster_config_user_sharingssharing IDthe config, and users (restrict)
slurm_cluster_config_group_sharingssharing IDthe config, and groups (restrict)
application_templatestemplate_id
application_template_inputs, _outputsinput_id, output_idapplication_templates (cascade); (template_id, input_name) unique
batch_job_configsbatch_job_config_id
batch_application_deploymentsdeployment_idslurm_clusters, application_templates, batch_job_configs (restrict)
scp_data_storagesdata_idssh_keys, owner users (restrict)
scp_data_storage_user_sharings, _group_sharingssharing IDthe storage, and users or groups (restrict)
data_productsdata_idowner users (restrict); data_storage_id has no foreign key
data_product_user_sharings, _group_sharingssharing IDthe product, and users or groups (restrict)
processesprocess_idowner users (restrict); last_status_id has no foreign key
process_statusesprocess_status_idprocesses (restrict)
batch_processesbatch_process_idparent_process_id → processes (cascade); batch_application_deployments, slurm_cluster_configs, batch_job_configs (restrict)
batch_process_statusesbatch_process_status_idbatch_processes (cascade)
process_template_input_mappings, _output_mappingsmapping IDbatch_processes (cascade), the template input or output (cascade)
data_staging_tasks, job_submission_tasks, job_monitoring_tasks, interactive_command_taskstask_idprocesses (cascade)

ssh_keys.private_key (text) and ssh_keys.passphrase (varchar(255)) are stored unencrypted. Enum columns (status, role, permission, process_type, storage types) are varchar holding the API's upper-case values. created_at and timestamp columns hold epoch milliseconds.

The workflow engine that carries out a launched process keeps its state outside PostgreSQL, in the SQLite file AIRAVATA_WORKFLOW_BACKEND_SQLITE_PATH (default /tmp/airavataorchestrator.sqlite, lost when the host clears /tmp).

Batch migrations​

PathEffectRecords in schema_migrations
AIRAVATA_DB_AUTO_MIGRATE=true (default)Adds missing tables, columns and indexes at startup; drops nothingNo
airavata-server migrate upApplies each unapplied NNNN_description.sql in version order, one transaction per fileYes: version, description, applied_at

Files are embedded in the binary; statements are split on ; followed by a newline. A file that fails part-way is rolled back, DDL included; correct it and run migrate up again. There are no down migrations; a later file corrects a mistake. 0001_baseline.sql is the only file.

The paths do not mix: on a database auto-migration created, 0001_baseline.sql is still pending and migrate up fails on the existing tables.