truenas: migrate client from deprecated REST to JSON-RPC 2.0 over WebSocket #1

Merged
brandon merged 1 commit from truenas-jsonrpc-migration into main 2026-07-14 00:12:28 -04:00
Owner

TrueNAS removes the /api/v2.0 REST API in 26.04. REST was stateless, so the
12s poll loop re-authenticated on every request (~130k auths/day, tripping the
deprecation alarm). Switch app/truenas.py to JSON-RPC 2.0 over a single
persistent, authenticated wss://.../api/current connection: requests are
multiplexed by JSON-RPC id and demuxed by one background reader task, so auth
happens once per connection instead of per request.

Method mappings verified against a live TrueNAS SCALE 25.10.4 box:
disk.query, core.get_jobs, system.info, disk.temperatures, core.job_abort,
disk.wipe. The smart.test.* namespace was removed in 25.10; those two methods
are the SSH-superseded fallback and now raise cleanly (caller already skips).

disk.wipe is a @job method: over JSON-RPC a job call returns the integer job id
immediately (verified via pool.import_find -> core.get_jobs), same as the old
REST endpoint. Documented that contract and guard it — an untracked destructive
wipe is worse than a loud failure.

Reads keep the exponential-backoff retry; writes fail loud rather than risk a
double-execute on a dropped socket.

Concurrency: each socket owns its own pending-waiter map, so a superseded reader
running its cleanup after a reconnect can only fail its own in-flight calls and
never clobbers the current connection's pending requests.

Also:

  • mock-truenas: add a /api/current JSON-RPC WS endpoint over the same in-memory
    state so dev-against-mock keeps working; disk.wipe creates a trackable job so
    get_job() resolves. REST routes stay for /health + /debug.
  • requirements.in: promote websockets to a direct dep (already pinned at 16.0
    transitively via uvicorn[standard], so no lockfile regen).
  • tests: offline checks of the reader demux / error mapping / concurrency, the
    wipe_disk job-id contract, and stale-reader isolation (fake socket, no network).

Verified: full suite 158/158 · live read-only smoke test of the new client
against TrueNAS 25.10.4 (disks, temps, jobs, system.info) succeeded.

Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com

TrueNAS removes the /api/v2.0 REST API in 26.04. REST was stateless, so the 12s poll loop re-authenticated on every request (~130k auths/day, tripping the deprecation alarm). Switch app/truenas.py to JSON-RPC 2.0 over a single persistent, authenticated wss://.../api/current connection: requests are multiplexed by JSON-RPC id and demuxed by one background reader task, so auth happens once per connection instead of per request. Method mappings verified against a live TrueNAS SCALE 25.10.4 box: disk.query, core.get_jobs, system.info, disk.temperatures, core.job_abort, disk.wipe. The smart.test.* namespace was removed in 25.10; those two methods are the SSH-superseded fallback and now raise cleanly (caller already skips). disk.wipe is a @job method: over JSON-RPC a job call returns the integer job id immediately (verified via pool.import_find -> core.get_jobs), same as the old REST endpoint. Documented that contract and guard it — an untracked destructive wipe is worse than a loud failure. Reads keep the exponential-backoff retry; writes fail loud rather than risk a double-execute on a dropped socket. Concurrency: each socket owns its own pending-waiter map, so a superseded reader running its cleanup after a reconnect can only fail its own in-flight calls and never clobbers the current connection's pending requests. Also: - mock-truenas: add a /api/current JSON-RPC WS endpoint over the same in-memory state so dev-against-mock keeps working; disk.wipe creates a trackable job so get_job() resolves. REST routes stay for /health + /debug. - requirements.in: promote websockets to a direct dep (already pinned at 16.0 transitively via uvicorn[standard], so no lockfile regen). - tests: offline checks of the reader demux / error mapping / concurrency, the wipe_disk job-id contract, and stale-reader isolation (fake socket, no network). Verified: full suite 158/158 · live read-only smoke test of the new client against TrueNAS 25.10.4 (disks, temps, jobs, system.info) succeeded. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
truenas: migrate client from deprecated REST to JSON-RPC 2.0 over WebSocket
Some checks failed
Security scan / pip-audit (pull_request) Has been cancelled
Security scan / bandit (pull_request) Has been cancelled
Security scan / gitleaks (pull_request) Has been cancelled
Security scan / mypy (pull_request) Has been cancelled
6a850581f2
TrueNAS removes the /api/v2.0 REST API in 26.04. REST was stateless, so the
12s poll loop re-authenticated on every request (~130k auths/day, tripping the
deprecation alarm). Switch app/truenas.py to JSON-RPC 2.0 over a single
persistent, authenticated wss://.../api/current connection: requests are
multiplexed by JSON-RPC id and demuxed by one background reader task, so auth
happens once per connection instead of per request.

Method mappings verified against a live TrueNAS SCALE 25.10.4 box:
  disk.query, core.get_jobs, system.info, disk.temperatures, core.job_abort,
  disk.wipe. The smart.test.* namespace was removed in 25.10; those two methods
  are the SSH-superseded fallback and now raise cleanly (caller already skips).

disk.wipe is a @job method: over JSON-RPC a job call returns the integer job id
immediately (verified via pool.import_find -> core.get_jobs), same as the old
REST endpoint. Documented that contract and guard it — an untracked destructive
wipe is worse than a loud failure.

Reads keep the exponential-backoff retry; writes fail loud rather than risk a
double-execute on a dropped socket.

Concurrency: each socket owns its own pending-waiter map, so a superseded reader
running its cleanup after a reconnect can only fail its own in-flight calls and
never clobbers the current connection's pending requests.

Also:
- mock-truenas: add a /api/current JSON-RPC WS endpoint over the same in-memory
  state so dev-against-mock keeps working; disk.wipe creates a trackable job so
  get_job() resolves. REST routes stay for /health + /debug.
- requirements.in: promote websockets to a direct dep (already pinned at 16.0
  transitively via uvicorn[standard], so no lockfile regen).
- tests: offline checks of the reader demux / error mapping / concurrency, the
  wipe_disk job-id contract, and stale-reader isolation (fake socket, no network).

Verified: full suite 158/158 · live read-only smoke test of the new client
against TrueNAS 25.10.4 (disks, temps, jobs, system.info) succeeded.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
brandon/nas-burnin!1
No description provided.