Fix native-nested probe: guard message is split across string literals
The probe searched plugins/cloud/crud.py for the contiguous string
"no further nesting". Stock does not contain it. The message is split across
adjacent string literals:
verrors.add(f"{name}.snapshot", "This option is only available for datasets that have no further "
"nesting")
Python concatenates those at runtime, so the errmsg IS contiguous and CRUD_BLOCK's
runtime filter matches correctly -- but the SOURCE never contains the whole
phrase. The probe therefore found nothing, concluded iX had removed the guard, and
skipped the nested module as "already native" on every boot. apply.log would say
"TrueNAS now handles nesting natively" and the feature would never work.
It fails safe -- the stock guard stays in place, so no backup could be
misconfigured and no data was at risk -- but the module was 100% dead.
Verified against real middlewared on a live box: the probe returned native=yes
(wrong) before this change and native=no (correct) after.
The probe now strips whitespace and quote characters before matching, which is
robust to any wrapping or concatenation style. Added a regression test that
EXECUTES apply.sh's own probe code (extracted, not reimplemented -- a
reimplementation would pass while the shipped probe stayed broken) against the
real wrapped source, a single-line variant, and a three-way split.
75 tests, ruff and shellcheck clean.
This commit is contained in:
@@ -146,6 +146,25 @@
|
||||
only record that an interrupted run's snapshot tree is still on disk; both
|
||||
scripts now name the snapshot (`zfs destroy -r ...`) before clearing it.
|
||||
|
||||
- **The native-nested probe could never detect the guard, silently disabling the
|
||||
whole module.** Stock splits the message across adjacent string literals:
|
||||
|
||||
```python
|
||||
verrors.add(f"{name}.snapshot", "This option is only available for datasets that have no further "
|
||||
"nesting")
|
||||
```
|
||||
|
||||
Python concatenates those at runtime — so the *errmsg* is contiguous and the
|
||||
runtime filter works — but the **source never contains the whole phrase**. The
|
||||
probe's substring search found nothing, concluded iX had removed the guard, and
|
||||
skipped the nested module as "already native". `apply.log` would report
|
||||
*"TrueNAS now handles nesting natively"* and the feature would never work.
|
||||
It fails safe (the stock guard stays, so no data is at risk) but the module was
|
||||
100% dead. The probe now strips whitespace and quotes before matching, which is
|
||||
robust to any wrapping style. Caught only by running the probe against real
|
||||
middlewared; there is now a regression test that executes apply.sh's own probe
|
||||
code against the real wrapped source.
|
||||
|
||||
### Refactored
|
||||
|
||||
- Staging teardown had been copy-pasted into `uninstall.sh` and `recover.sh` —
|
||||
|
||||
+14
-1
@@ -167,7 +167,20 @@ try:
|
||||
crud = os.path.join(result['mw_dir'], 'plugins', 'cloud', 'crud.py')
|
||||
with open(crud, encoding='utf-8', errors='replace') as fh:
|
||||
stock_src = fh.read().split('\n# TRUECLOUD_PATCH', 1)[0]
|
||||
if 'no further nesting' not in stock_src:
|
||||
# The guard message is SPLIT across adjacent string literals in the source:
|
||||
#
|
||||
# verrors.add(..., 'This option is only available for datasets that have no further '
|
||||
# 'nesting')
|
||||
#
|
||||
# Python concatenates those at runtime, so the errmsg is contiguous -- but the
|
||||
# SOURCE never contains the whole phrase. A raw search finds nothing, concludes
|
||||
# iX removed the guard, and silently skips this module FOREVER. (Caught only by
|
||||
# running the probe against real middlewared.)
|
||||
#
|
||||
# Strip whitespace and quote characters, then match the compacted phrase. That
|
||||
# is robust to any wrapping or concatenation style iX may use.
|
||||
_drop = str.maketrans('', '', ' \\t\\n\\r' + chr(34) + chr(39))
|
||||
if 'nofurthernesting' not in stock_src.translate(_drop):
|
||||
result['native_nested'] = 'yes'
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -8,6 +8,7 @@ these tests do.
|
||||
import ast
|
||||
import os
|
||||
import re
|
||||
import textwrap
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -46,6 +47,35 @@ def extract_blocks():
|
||||
return blocks
|
||||
|
||||
|
||||
def _nested_native_detector():
|
||||
"""The REAL native-nested probe, lifted out of apply.sh.
|
||||
|
||||
Extracted rather than reimplemented: a reimplementation would happily pass
|
||||
while the shipped probe stayed broken, which is precisely the bug this guards.
|
||||
"""
|
||||
with open(APPLY_SH, encoding="utf-8") as fh:
|
||||
sh = fh.read()
|
||||
|
||||
m = re.search(
|
||||
r"^(\s*)_drop = str\.maketrans\(.*?\n\s*if 'nofurthernesting' not in "
|
||||
r"stock_src\.translate\(_drop\):\n\s*result\['native_nested'\] = 'yes'",
|
||||
sh, re.S | re.M,
|
||||
)
|
||||
assert m, "could not find the native-nested probe in apply.sh"
|
||||
|
||||
# The block lives inside a double-quoted shell string; undo bash's escaping.
|
||||
body = m.group(0)
|
||||
body = body.replace("\\\\", "\x00").replace('\\"', '"').replace("\x00", "\\")
|
||||
body = textwrap.dedent(body)
|
||||
|
||||
def detect(stock_src):
|
||||
ns = {"stock_src": stock_src, "result": {"native_nested": "no"}, "chr": chr}
|
||||
exec(body, ns) # noqa: S102 - executing our own shipped code, on purpose
|
||||
return ns["result"]["native_nested"]
|
||||
|
||||
return detect
|
||||
|
||||
|
||||
def test_heredoc_itself_compiles():
|
||||
compile(heredoc_source(), "apply.sh:PYEOF", "exec")
|
||||
|
||||
@@ -153,6 +183,38 @@ class TestIndependentModules:
|
||||
assert "'ok': (not providers_needed) or bool(b2_ok and restic_ok)" in src
|
||||
assert "'active': nested_needed" in src
|
||||
|
||||
def test_nested_native_probe_matches_the_real_wrapped_source(self):
|
||||
"""Stock splits the guard message across adjacent string literals.
|
||||
|
||||
Python concatenates them at runtime, so the errmsg is contiguous -- but the
|
||||
SOURCE never contains the whole phrase. A raw substring search finds
|
||||
nothing, concludes iX removed the guard, and silently skips this module
|
||||
forever. This is exactly what happened, and only a run against real
|
||||
middlewared caught it.
|
||||
"""
|
||||
detect = _nested_native_detector()
|
||||
|
||||
# Verbatim shape from TrueNAS plugins/cloud/crud.py.
|
||||
stock_wrapped = (
|
||||
' verrors.add(f"{name}.snapshot", '
|
||||
'"This option is only available for datasets that have no further "\n'
|
||||
' "nesting")\n'
|
||||
)
|
||||
assert detect(stock_wrapped) == "no", "guard is present; must NOT report native"
|
||||
|
||||
# Same message on a single line — must also be detected.
|
||||
assert detect('verrors.add(x, "... have no further nesting")\n') == "no"
|
||||
|
||||
# Single-quoted, three-way split — still the guard.
|
||||
assert detect(
|
||||
"verrors.add(x, 'This option is only available for '\n"
|
||||
" 'datasets that have no further '\n"
|
||||
" 'nesting')\n"
|
||||
) == "no"
|
||||
|
||||
# Guard genuinely gone -> native support.
|
||||
assert detect("def _validate(self):\n pass\n") == "yes"
|
||||
|
||||
def test_nested_native_probe_ignores_our_own_block(self):
|
||||
# CRUD_BLOCK quotes the guard message, so scanning the whole file would
|
||||
# find the string in our own patch and never detect native support.
|
||||
|
||||
Reference in New Issue
Block a user