Disks and pools
Storage is set up once: data disks plus one SSD for the dedupe table become the system zpool. Pools are deduplicated datasets on it, and each library pairs with exactly one pool. The order the UI enforces is the order the API expects: system storage, then pool, then library.
GET /api/devices
Block devices with eligibility, plus the system storage status.
curl -sk https://appliance:8443/api/devices -H "Authorization: Bearer $OVTL_KEY"
{
"devices": [
{"path": "/dev/sda", "by_id": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi0", "size_bytes": 53687091200, "model": "QEMU HARDDISK", "transport": "scsi", "rotational": true, "eligible": false, "reason": "operating system disk", "role": "os"},
{"path": "/dev/sdb", "by_id": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi1", "size_bytes": 3298534883328, "model": "QEMU HARDDISK", "transport": "scsi", "rotational": true, "eligible": true},
{"path": "/dev/sdc", "by_id": "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi2", "size_bytes": 107374182400, "model": "QEMU HARDDISK", "transport": "scsi", "rotational": false, "eligible": true}
],
"system": {"ready": false, "zpool": "ovz", "data_devs": [], "dedup_dev": "", "dedup_fixed": false, "dedup_ratio": 0, "size_bytes": 0, "alloc_bytes": 0, "data_size_bytes": 0, "data_alloc_bytes": 0, "dedup_size_bytes": 0, "dedup_alloc_bytes": 0}
}
Choose disks by size and type. path can re-letter across boots;
by_id is stable. role is os, cache-device, or pool:<name> when
a disk is already in use.
POST /api/storage/rescan
Re-probes every SCSI host so a disk hot-added by the hypervisor appears without a reboot, then returns the fresh device list.
{"scsi_hosts": 3, "devices": ["…"], "system": {"…": "…"}}
POST /api/storage/setup
Builds the system zpool from the selected data disks and one dedupe SSD. Every selected disk is erased. The dedupe device choice is permanent for the life of the system storage. There is no force path past a disk the daemon considers ineligible.
| Body field | Type | Notes |
|---|---|---|
data_devs |
string[] | Paths of the data disks. |
dedup_dev |
string | Path of the SSD for the dedupe table. |
confirm |
string | Must be "create". |
curl -sk -X POST https://appliance:8443/api/storage/setup \
-H "Authorization: Bearer $OVTL_KEY" -H 'Content-Type: application/json' \
-d '{"data_devs":["/dev/sdb"],"dedup_dev":"/dev/sdc","confirm":"create"}'
Returns the system status object with ready: true. Validation
failures (wrong confirmation, ineligible disk, storage already set up)
are 400.
POST /api/storage/grow
Expands the zpool onto a data disk the hypervisor enlarged underneath it. Online, non-destructive, idempotent. No body.
{"before_bytes": 3298534883328, "after_bytes": 4398046511104, "grew": true, "system": {"…": "…"}}
POST /api/storage/teardown
Destroys the system zpool and frees its data disks. Valid only once every pool has been removed. The dedupe SSD stays reserved as the permanent metadata device.
curl -sk -X POST https://appliance:8443/api/storage/teardown \
-H "Authorization: Bearer $OVTL_KEY" -H 'Content-Type: application/json' \
-d '{"confirm":"teardown"}'
Returns the system status object.
GET /api/pools
[
{"id": 1, "name": "prod", "vg": "", "data_lv": "", "mountpoint": "/var/lib/openvtl/pools/prod", "data_dev": "", "cache_slice_bytes": 0, "virtual_size_bytes": 0, "state": "active", "created_at": "2026-08-31T00:03:40Z"}
]
state is creating, active, or removing. Only an active pool can
home a library. The vg, data_lv, data_dev, and size fields are
kept for shape stability from earlier storage designs and are empty on
ZFS appliances. Live capacity and dedupe figures for a pool are in
GET /api/status under pools[].
POST /api/pools
Creates a deduplicated dataset on the system storage. Dedupe granularity (recordsize) is chosen from installed RAM at this moment and is fixed for the pool.
| Body field | Rules |
|---|---|
name |
Lowercase letters, digits, _. |
curl -sk -X POST https://appliance:8443/api/pools \
-H "Authorization: Bearer $OVTL_KEY" -H 'Content-Type: application/json' \
-d '{"name":"prod"}'
{"pool_id": 1}
201 on success. 409 while a graceful restart is draining.
DELETE /api/pools/{id}
Destroys the pool’s dataset and everything on it. Refused while a library is paired with the pool: delete the library first. The system storage and its disks are untouched.
curl -sk -X DELETE https://appliance:8443/api/pools/1 \
-H "Authorization: Bearer $OVTL_KEY" -H 'Content-Type: application/json' \
-d '{"confirm":"prod"}'
{"ok": true, "pool_id": 1}
404 for an unknown pool; 400 if the name does not match or a library
still uses it.
GET /api/pool/history
Persisted capacity samples for the dashboard trend.
| Query | Default | Notes |
|---|---|---|
pool |
first pool | Pool name. |
hours |
24 | 1 to 2160 (90 days). |
[
{"ts": "2026-09-01T06:00:00Z", "pool": "prod", "fs_used_bytes": 622770257920, "fs_total_bytes": 3408486400000, "vdo_used_bytes": 622770257920, "vdo_phys_bytes": 0, "vdo_saving_pct": 0, "logical_bytes": 7696581394432, "cache_used_pct": 0}
]
The vdo_* field names are historical; vdo_used_bytes is the
dataset’s compressed size and logical_bytes is what the host wrote.