Never touch CloudSync tasks; restore the logger the async cleanup path dropped
CI / shell (shellcheck + syntax) (push) Successful in 8s
CI / python 3.11 (push) Successful in 14s
CI / python 3.12 (push) Successful in 14s
CI / python 3.13 (push) Successful in 13s

create_snapshot is module-global in plugins/cloud/snapshot.py, and cloud_sync.py
imports it as well as cloud_backup/sync.py. So the wrapper sat in the path of every
rclone/Storj CloudSync task with snapshot=true, and ran a zfs.dataset.query before
concluding it had nothing to do -- a new failure mode for jobs that worked before
this patch existed.

Worse: a CloudSync task that ever got staged would never be torn down. The teardown
is wired into cloud_backup's restic_backup finally, and CRUD_BLOCK deliberately
leaves CloudSync's guard intact, so the bind mounts would pin the snapshot forever.
The staging path now bails out unless the snapshot is named cloud_backup-*, before
any middleware call.

Separately: the async wrapper's finally dropped logger=, which the sync one passes.
run_in_thread forwards **kwargs, so a cleanup that failed to unmount a bind mount or
delete a snapshot tree logged nothing at all -- on the only platform anyone runs.
This commit is contained in:
2026-07-13 18:30:35 +00:00
parent ecb64878ff
commit a54b4dd7f6
3 changed files with 76 additions and 0 deletions
+36
View File
@@ -407,3 +407,39 @@ class TestTheTwoNativeProbesCannotDrift:
assert static_native == expect_native, (
f"compat.py says native={static_native} for {src!r}"
)
class TestOnlyOurOwnTasksAreTouched:
"""create_snapshot is module-global, and cloud_sync.py imports it too.
plugins/cloud/snapshot.py::create_snapshot is imported by BOTH
cloud_backup/sync.py and cloud_sync.py, so our wrapper sits in the path of every
rclone/Storj CloudSync task with snapshot=true -- tasks this patch has no business
touching. Two consequences, the second much worse than the first:
* every middleware call we add is a NEW failure mode for a job that worked
before we were installed;
* a staged CloudSync task would NEVER be torn down. The teardown is wired into
cloud_backup's restic_backup finally, and CRUD_BLOCK deliberately leaves
CloudSync's nesting guard intact -- so the bind mounts would pin the ZFS
snapshot forever.
cloud_backup names its snapshot "cloud_backup-<id>", cloud_sync "cloud_sync-<id>",
and stock's default is "cloud_task-onetime".
"""
@pytest.mark.parametrize("name", ["SNAPSHOT_ASYNC", "SNAPSHOT_SYNC"])
def test_the_staging_path_is_gated_on_cloud_backup(self, name):
block = extract_blocks()[name]
assert 'if not name.startswith("cloud_backup"):' in block
@pytest.mark.parametrize("name", ["SNAPSHOT_ASYNC", "SNAPSHOT_SYNC"])
def test_the_bail_out_precedes_every_middleware_call(self, name):
# The point is to add NO new failure mode to a CloudSync task. If any
# middleware call happened before the bail-out, we would already have broken
# the thing we are trying not to touch.
block = extract_blocks()[name]
gate = block.index('if not name.startswith("cloud_backup"):')
for call in ("middleware.call_sync(", "_tc_nested.stage_nested(",
"_tc_nested.delete_snapshot_tree("):
assert gate < block.index(call), f"{call} runs before the cloud_backup gate"