compat: check the middlewared METHODS we call, not just the symbols we wrap
This gap was hiding a catastrophe. TrueNAS 26 deletes plugins/zfs_/dataset.py and plugins/zfs_/snapshot.py outright, taking zfs.dataset.query, zfs.snapshot.query and zfs.snapshot.delete with them (26 uses filesystem.statfs and zfs.resource.*). Nothing about the five cloud_backup files reveals that, so every other check went green -- including the one I had just added. The patch would have applied cleanly and then failed on the first backup, or worse: snapshotted fine and failed to DELETE, orphaning one snapshot per descendant dataset (250 on a real pool) on every run, forever. So 26 is BROKEN and the nested module will not apply there. The async/sync wrapper work and the vendored get_dataset_recursive stay -- they are correct and necessary -- but 26 is not supported until the ZFS calls are ported, and that needs a real 26 box to verify. Shipping a port nobody has run is the failure this project exists to avoid. Also: do_delete is recognised as delete (24.10/25.04 use the CRUDService convention), which was reporting both as BROKEN -- a false verdict that would have disabled nested snapshots on boxes where they work.
This commit is contained in:
+44
-19
@@ -59,28 +59,53 @@ worse than no alert, because one day it carries a security fix.
|
||||
|
||||
### Added
|
||||
|
||||
- **TrueNAS 26 support.** 26 rewrites the entire `cloud_backup` path from async to
|
||||
**synchronous**, and separately **deletes `get_dataset_recursive()`** — which one
|
||||
of the injected blocks called out of the host module's namespace. Either one is a
|
||||
broken backup found at restore time: an `async def` wrapper hands `sync.py` a
|
||||
coroutine where it unpacks a tuple, and the vanished helper is a straight
|
||||
`NameError`.
|
||||
- **The compatibility check now covers the middlewared methods the patch _calls_,**
|
||||
not only the symbols it wraps — and that gap was hiding a catastrophe.
|
||||
|
||||
The nested module is now **one synchronous implementation** (talking to middlewared
|
||||
through `call_sync`) behind **two thin wrappers**. `apply.sh` reads which flavour
|
||||
the installed middleware declares and injects the matching one: TrueNAS ≤ 25.10
|
||||
reaches it via `await middleware.run_in_thread(...)`, and TrueNAS 26 — already in a
|
||||
worker thread — calls it directly. The logic that owns the snapshots, the bind
|
||||
mounts and the failure modes exists **once**; an async twin would mean every future
|
||||
fix had to land twice, and the one that got missed would be the one that eats a
|
||||
backup.
|
||||
TrueNAS 26 **deletes `plugins/zfs_/dataset.py` and `plugins/zfs_/snapshot.py`
|
||||
outright**, taking `zfs.dataset.query`, `zfs.snapshot.query` and
|
||||
`zfs.snapshot.delete` with them (26 uses `filesystem.statfs` and `zfs.resource.*`).
|
||||
Nothing about the five `cloud_backup` files reveals that, so every other check went
|
||||
green. The patch would have applied perfectly and then **failed on the first
|
||||
backup** — or, far worse, snapshotted successfully and failed to *delete*,
|
||||
orphaning one snapshot per descendant dataset (**250 on a real pool**) on every
|
||||
single run, forever.
|
||||
|
||||
A middleware whose three wrapped functions **disagree** about async-ness is refused
|
||||
outright rather than guessed at. And `get_dataset_recursive` is now carried as our
|
||||
own copy — removing the dependency on both versions instead of asserting it.
|
||||
This is now an assumption class of its own, so a method disappearing is a BROKEN
|
||||
verdict rather than a silent time bomb.
|
||||
|
||||
Both breaks were found by the daily compatibility check **while 26 was still in
|
||||
beta**, which is the entire point of it.
|
||||
- **Groundwork for TrueNAS 26** (async→sync and the deleted helper — see below).
|
||||
**26 is still reported BROKEN and the nested module will not apply there**, because
|
||||
the ZFS API rewrite above is not yet ported. Porting it needs a real 26 box to
|
||||
verify against, and shipping a port nobody has run is exactly the failure this
|
||||
project exists to avoid. On 26, TrueNAS is left stock: B2/S3 keeps working, nested
|
||||
datasets are simply not covered.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **The nested module is now one synchronous implementation behind two thin
|
||||
wrappers.** TrueNAS 26 rewrites `cloud_backup` from async to **synchronous** and
|
||||
separately **deletes `get_dataset_recursive()`**, which an injected block called out
|
||||
of the host module's namespace. Either alone is a broken backup found at restore
|
||||
time: an `async def` wrapper hands `sync.py` a coroutine where it unpacks a tuple,
|
||||
and the vanished helper is a straight `NameError`.
|
||||
|
||||
The module now talks to middlewared through `call_sync`, and `apply.sh` reads which
|
||||
flavour the installed middleware declares and injects the matching wrapper —
|
||||
TrueNAS ≤ 25.10 reaches it via `await middleware.run_in_thread(...)`; a synchronous
|
||||
TrueNAS, already in a worker thread, calls it directly. The logic that owns the
|
||||
snapshots, the bind mounts and the failure modes exists **once**; an async twin
|
||||
would mean every future fix had to land twice, and the one that got missed would be
|
||||
the one that eats a backup. A middleware whose three wrapped functions **disagree**
|
||||
about async-ness is refused outright rather than guessed at, and
|
||||
`get_dataset_recursive` is carried as our own copy — removing the dependency on both
|
||||
versions instead of asserting it.
|
||||
|
||||
- **`do_delete` is recognised as `delete`.** TrueNAS 24.10 and 25.04 declare
|
||||
`do_delete` (the `CRUDService` convention); 25.10 renamed it to `delete`. Both
|
||||
answer to `zfs.snapshot.delete`. Accepting only the literal name reported both older
|
||||
releases as BROKEN — a false verdict that would have switched nested snapshots off
|
||||
on boxes where they work perfectly.
|
||||
|
||||
- **An incompatible TrueNAS no longer sets the permanent kill switch.** `apply.sh`
|
||||
reused a "nothing left to do" exit that touches `disabled`, which suppresses
|
||||
|
||||
Reference in New Issue
Block a user