Libraries and drives

A library is declared against a pool, then activated in a maintenance window that restarts the tape emulator and rebuilds the FC target. The host sees the declared model, serial, and drive serials; those survive reboots and rebuilds.

GET /api/libraries

read-only

The persisted library rows (configuration, not live state; the live state is in GET /api/status).

[
  {"id": 10, "name": "PROD-VTL", "vendor": "IBM", "product": "3573-TL", "variant": "3573-TL", "serial": "OVTL280164", "drive_model": "ULT3580-TD5", "num_drives": 2, "label_prefix": "OVB", "media_dir": "/var/lib/openvtl/pools/prod", "home_pool": 1, "state": "active", "created_at": "2026-08-31T00:04:02Z"}
]

state is pending_restart (declared, not yet served) or active.

GET /api/libraries/{lib}/next-label

read-only

The label a mint would generate next for this library, from every label the appliance knows locally and in the S3 catalog.

{"label": "OVB004L5", "prefix": "OVB", "suffix": "L5"}

POST /api/libraries

admin

Declares a library. Nothing is served until POST /api/libraries/apply.

Body field Type Notes
name string Display name. Empty defaults to the generated serial.
product string Library model wire id from GET /api/models: 3573-TL for TS3100/TS3200 (3573), 03584L32 for TS3500 (3584).
drive_product string Drive model wire id, for example ULT3580-TD5. Must belong to the library’s family.
num_drives int 1 up to the model’s max_drives.
num_slots int 1 to 400. 0 takes the default (100).
num_map int Import/export slots, 1 to 32. 0 takes the default (4).
label_prefix string Exactly three A-Z0-9 characters. Barcodes mint as <prefix>001L5, <prefix>002L5, and so on.
pool_id int An active pool with no library on it.
curl -sk -X POST https://appliance:8443/api/libraries \
  -H "Authorization: Bearer $OVTL_KEY" -H 'Content-Type: application/json' \
  -d '{"name":"PROD-VTL","product":"3573-TL","drive_product":"ULT3580-TD5","num_drives":2,"num_slots":100,"num_map":4,"label_prefix":"OVB","pool_id":1}'
{
  "library": 10,
  "serial": "OVTL280164",
  "drive_serials": ["OVTD280164A", "OVTD280164B"],
  "state": "pending_restart",
  "note": "declared in device.conf — activation requires the Apply maintenance window (mhVTL restart + FC rebuild + operator vary off/on)"
}

201 on success. 400 when the pool is not active, already homes a library, or a field fails validation.

POST /api/libraries/apply

admin Disruptive confirm: "apply"

The maintenance window: restarts the tape emulator, rebuilds the FC target, reconciles ACLs, and marks every library that comes up live as active. Every host session drops for a minute or two. Afterwards the operator varies the host’s device descriptions off and on and creates a device description for any new library.

Preflight refuses with 400 while any job is active or queued, or while a drive holds a cartridge. The steps stream as maint_step events.

curl -sk -X POST https://appliance:8443/api/libraries/apply \
  -H "Authorization: Bearer $OVTL_KEY" -H 'Content-Type: application/json' \
  -d '{"confirm":"apply"}'
{
  "steps": ["…", "target fabrics: fc: 2 port(s), 3 LUNs identity-verified, ACLs reconciled", "READY FOR OPERATOR: vary off/on the host device descriptions; create the new MLB description for the added library"],
  "fc": {"OK": true, "Detail": "fc: 2 port(s), 3 LUNs identity-verified, ACLs reconciled"}
}

The request survives a client disconnect; do not retry it because a timeout fired. Watch the events stream or poll GET /api/status instead.

DELETE /api/libraries/{lib}

admin Destructive Disruptive confirm: library name · acknowledge: "I understand"

Deletes a library, its drives, and every cartridge on it in one operation. Copies in S3 are never touched; the catalog keeps them and the labels can be re-imported into another library. A library that is live can only release its emulated devices cleanly through a reboot, so deleting a live library reboots the appliance once the cleanup is persisted. Erasing many cartridges on a deduplicated pool takes minutes.

Body field Rules
confirm The library’s name or serial, exactly.
acknowledge The literal string I understand.

Preflight refuses with 400 while any job is active or queued, and with 409 while a graceful restart is draining.

curl -sk -X DELETE https://appliance:8443/api/libraries/10 \
  -H "Authorization: Bearer $OVTL_KEY" -H 'Content-Type: application/json' \
  -d '{"confirm":"PROD-VTL","acknowledge":"I understand"}'
{"ok": true, "library": 10, "name": "PROD-VTL", "cartridges_deleted": 40, "skipped": 0, "rebooting": true}

rebooting: false for a library that was declared but never served. A retry after an interrupted delete finishes the cleanup.

POST /api/libraries/recover

admin Disruptive

One-click disaster recovery. Reads the library’s topology.json from the bucket, recreates the library on a free pool with its original model, geometry, and serial (drives get new serials), runs the activation window, then queues an import of every cartridge’s newest generation.

Body field Type Notes
remote_id int The S3 remote holding the export.
system_name string The exporting appliance’s system name (the first key segment).
library_serial string The library to recover.
pool_id int An active pool with no library on it.
name string Optional display name; defaults to the name recorded in the topology.
curl -sk -X POST https://appliance:8443/api/libraries/recover \
  -H "Authorization: Bearer $OVTL_KEY" -H 'Content-Type: application/json' \
  -d '{"remote_id":1,"system_name":"demo01","library_serial":"OVTL280164","pool_id":2}'
{
  "library": 20,
  "serial": "OVTL280164",
  "name": "PROD-VTL",
  "drive_serials": ["OVTD9A1C02A", "OVTD9A1C02B"],
  "carts": 40,
  "import_jobs": [101, 102, 103],
  "steps": ["…"]
}

201 on success. 400 when the serial already exists here, the pool is in use, no topology exists in the bucket (only libraries exported by v0.7 or later wrote one), or jobs are active. If activation fails the response is a 500 that still carries library and import_jobs: the library row exists as pending_restart, and once it is live the queued imports can be retried from Jobs. The host re-attaches on vary off/on because the serial is preserved; the drives are new resources.

Drives

Manual changer moves, the same path a host MOVE MEDIUM takes. The library must be live.

POST /api/libraries/{lib}/drives/{index}/load

admin

Loads a cartridge from its storage or import/export slot into drive {index} (0-based).

curl -sk -X POST https://appliance:8443/api/libraries/10/drives/0/load \
  -H "Authorization: Bearer $OVTL_KEY" -H 'Content-Type: application/json' \
  -d '{"label":"OVB001L5"}'
{"ok": true, "detail": "OVB001L5: storage:1 -> library 10 drive 0 (manual)"}

400 if the drive already holds a cartridge, the label is not in this library, or the library is not live.

POST /api/libraries/{lib}/drives/{index}/unload

admin

Returns the loaded cartridge to its home slot (or the first empty storage slot). The one hard rule: media is never pulled from a drive that is reading, writing, or mounting, or that was active in the last 15 seconds. The host sees the usual not-ready/unit-attention on its next access, as with an operator move on a physical library.

{"ok": true, "detail": "OVB001L5: library 10 drive 0 -> storage slot 1 (manual)", "slot": 1}