Skip to content

docs(api): proposal: a backend-neutral storage layer API (/v2/storage) - #10

Draft
ppenguin wants to merge 2 commits into
ReCasaOS:mainfrom
ppenguin:feat/storage-layer-api
Draft

ppenguin wants to merge 2 commits into
ReCasaOS:mainfrom
ppenguin:feat/storage-layer-api

Conversation

@ppenguin

Copy link
Copy Markdown
Contributor

Draft — proposal for discussion; implemented and tested on ReCasaNix.

api/storage/openapi.yaml: a backend-neutral storage layer API (/v2/storage), what CasaOS-UI's Pool tab (draft PR) talks to.

  • one pool over the drives; layouts none · mirror (copies) · parity (p); the service picks the backend per layout (ReCasaNix: btrfs; parity → md with btrfs on top) and names it
  • GET /v2/storage: pool (state as /v1/disks/raid, protection now, members, verbs), drives (role: system, member, free, small, foreign, busy; verbs), layouts, jobs
  • GET /layouts?disks=: capacity / protection per layout on chosen free drives
  • plan → preview → apply:
    • POST /plans (intent) → plan: steps, exact commands, effects (capacity, protection before → after, disks erased), confirm text; 409 with the reason
    • POST /plans/{id}/apply {confirm} → re-plans from a fresh look, runs only when unchanged (409 stale), as a job; 400 wrong confirmation
  • spec only: not wired into codegen; LocalStorage could implement it for md (its RAID repair maps onto replace-disk / remove-disk)
  • validated with openapi-spec-validator

- one pool over the drives; layouts none / mirror(copies) / parity(p); backend chosen per layout
- drives with roles and verbs; pool with state (as /v1/disks/raid), protection, members, verbs
- plan → preview → apply: intent → plan (steps, commands, effects, disks erased, text to type);
  apply re-plans from a fresh look, runs only when unchanged, as a job
- spec only, not wired into codegen; implemented by ReCasaNix's recasanix-storage (btrfs, md)
@inkly

inkly commented Nov 1, 2026

Copy link
Copy Markdown
Collaborator

Thanks for putting this in the open as a draft, and for the matching Pool tab in CasaOS-UI#69. I have read both and the spec. A first reaction, nothing decided on our side yet.

What LocalStorage does today, so we compare the same thing: md only, through mdadm, no btrfs. GET /v1/disks/raid gives the state (including sync_action), POST /v1/disks/raid creates an array of blank disks (raid0, 1, 5, 6, 10), puts a filesystem on it (ext4, as Create Storage does for one disk) and mounts it as a storage; POST .../disks adds a disk to a degraded array, DELETE .../disks/:member takes a failed one out, POST .../level turns a RAID 1 into a RAID 5 and POST .../filesystem grows the filesystem afterwards.

Where your verbs line up: create-pool, add-disk, remove-disk, replace-disk (add, then remove), and change-layout for mirror → parity. check / cancel-check have no counterpart yet (we read sync_action, we cannot start or stop it), and mount-degraded is a btrfs notion.

Three things I would like your view on:

  1. One pool or several. A box here can hold several md arrays, each its own storage, and merged storage can span them. pool is a single nullable object in the spec. Is "one pool" the ReCasaNix model only, or would you accept pools[] so that md fits without a convention?
  2. RAID 10. We offer it; none | mirror | parity cannot say it. Left out of v2 (it stays on /v1), or a fourth kind?
  3. Files and codegen. The other services keep their spec under api/<service>/openapi.yaml with generated code. Is api/storage/ meant to become a LocalStorage API, or a contract several backends implement and LocalStorage merely joins?

Since the Pool tab only appears when GET /v2/storage answers, merging the spec as a proposal changes nothing on a ReCasaOS box, so there is no hurry on our side. What we would rather avoid is building the next RAID steps (RAID 5 → 6, deleting an array) as new /v1 routes and rewriting them as plans a month later. Until we have talked this through we will hold those two, and keep fixing what is already there.

@ppenguin

Copy link
Copy Markdown
Contributor Author

Thanks for the review, and sorry for the wait. Answers:

1. One pool or several → pools[]

  • "one pool" = ReCasaNix product policy, not a contract property
  • spec change:
    • GET /v2/storage → pools: [] (each with id)
    • intents name their pool (pool: <id>); create-pool has none
    • top-level verbs for box-wide actions (create-pool, …)
  • ReCasaNix: server stops offering create-pool once a pool exists (0..1 pools); LocalStorage: one pool per md array, merged storage untouched
  • UI: list of pool cards; looks the same with one

2. RAID 10 → stripe flag, not a fourth kind

  • kind = protection (none | mirror | parity); striping = performance, orthogonal

  • layout: { kind, copies?, parity?, stripe? }

    layout md btrfs ZFS
    mirror{copies:2, stripe} raid10 raid10 mirror vdevs
    none{stripe} raid0 raid0 stripe
    mirror{copies:2} raid1 raid1 mirror
    parity{1|2} raid5/6 — (md below) raidz1/2
  • covers your raid0 too (with the same consent: tolerates: 0 + typed confirmation)

  • backend lists what it offers via GET /v2/storage/layouts

3. api/storage/ → contract, LocalStorage = reference implementation

  • one spec, several backends; the UI targets the spec only
  • lives here (api/storage/openapi.yaml, header: backend-neutral contract); no separate repo
  • LocalStorage: generated server code like the other services
  • recasanix-storage: second implementation; we add a conformance test of our responses against the spec

Your next RAID steps

  • RAID 5→6 = change-layout parity{1} → parity{2}; delete array = delete-pool (new verb, erases, typed confirmation)
  • happy to build them as /v2 plans directly, no /v1 detour
  • I'll update this PR with 1–3 (+ delete-pool) and rework UI#69 on current main (conflicts with #163); both stay draft until tested on hardware

…several backends

- pools[] with id; intents name their pool; top-level verbs (create-pool)
- layout.stripe: speed only (RAID 10 / RAID 0), kind stays the protection
- delete-pool, restore-layout verbs; pool.problem
- the spec is a contract: LocalStorage = reference implementation
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants