fix(media): order the sidecar reap only where the view is mounted
Every apply and rollback printed "next: scripts/media-view.sh reap", but on Dankserver there is nothing to reap: the metadata view has never been installed (`media-view.sh status` -> NOT MOUNTED, `/var/lib/media-meta` absent) and the live quadlets still bind the array read-only. All three relayout arms tonight issued that order — 242,084 moves — and it pointed at a directory that does not exist. Where no overlay is mounted the servers bind the array itself, so the sidecars moved with the media. `--metadata-view` (default /var/lib/media-meta/view) names the overlay and the note now rides only when that path is a mountpoint. Red first: two new cases in tests/media_relayout.bats (apply and rollback); the shared helper names a real mountpoint so the pre-existing presence assertion keeps its meaning. 37 ok.
This commit is contained in:
1 parent
96944f0b17
commit
9eb85ea1f9
2 files changed
+55
-6
No files matched your search
+25
-5
@@ -32,6 +32,12 @@ cross a filesystem (rename cannot do it and a silent copy at 24 TB would not).
|
|||||||
--force relaxes only the guards that are opinion (journal already rolled back,
|
--force relaxes only the guards that are opinion (journal already rolled back,
|
||||||
paths outside --root); it never allows overwriting a file.
|
paths outside --root); it never allows overwriting a file.
|
||||||
|
|
||||||
|
--metadata-view names the overlay the media servers read their library through
|
||||||
|
(default /var/lib/media-meta/view). The sidecar follow-up printed after an apply
|
||||||
|
or rollback is conditioned on that path being a mountpoint: where no view is
|
||||||
|
mounted the servers bind the array itself, the sidecars moved with the media,
|
||||||
|
and a reap order would point at a directory that is not there.
|
||||||
|
|
||||||
The journal defaults to `media-relayout.jsonl` BESIDE the library root, never
|
The journal defaults to `media-relayout.jsonl` BESIDE the library root, never
|
||||||
inside it: a log inside the library is a file Jellyfin will index.
|
inside it: a log inside the library is a file Jellyfin will index.
|
||||||
"""
|
"""
|
||||||
@@ -114,13 +120,24 @@ def read_plan(path, root, force):
|
|||||||
return mkdirs, keeps, moves
|
return mkdirs, keeps, moves
|
||||||
|
|
||||||
|
|
||||||
# A relayout moves bytes on the raw array; the sidecars the media servers wrote
|
# The media servers may read the library through the SSD overlay view
|
||||||
# live in the SSD overlay and stay at the old coordinates. Naming the follow-up
|
# (scripts/media-view.sh). A relayout moves bytes on the raw array, so sidecars
|
||||||
# here is the point of use for that fact — see the Media Metadata View page.
|
# the servers wrote through that view stay at the old coordinates — naming the
|
||||||
|
# follow-up here is the point of use for that fact, but only where the view
|
||||||
|
# exists: where it is not mounted the servers bind the array itself, the
|
||||||
|
# sidecars moved with the media, and a reap order points at a directory that
|
||||||
|
# is not there. See the Media Metadata View page for the host's state.
|
||||||
|
METADATA_VIEW = "/var/lib/media-meta/view"
|
||||||
METADATA_HINT = ("note: sidecars written through the metadata view did not follow"
|
METADATA_HINT = ("note: sidecars written through the metadata view did not follow"
|
||||||
" these moves — next: scripts/media-view.sh reap")
|
" these moves — next: scripts/media-view.sh reap")
|
||||||
|
|
||||||
|
|
||||||
|
def metadata_note(args):
|
||||||
|
"""Print the sidecar follow-up only when the overlay is really mounted."""
|
||||||
|
if os.path.ismount(args.metadata_view):
|
||||||
|
print(METADATA_HINT)
|
||||||
|
|
||||||
|
|
||||||
def do_apply(args, root, journal, mkdirs, keeps, moves):
|
def do_apply(args, root, journal, mkdirs, keeps, moves):
|
||||||
"""Preflight everything, then folders, then renames. Never a partial start."""
|
"""Preflight everything, then folders, then renames. Never a partial start."""
|
||||||
problems = []
|
problems = []
|
||||||
@@ -202,7 +219,7 @@ def do_apply(args, root, journal, mkdirs, keeps, moves):
|
|||||||
f" {dirs_new} folders created, {len(keeps)} files stayed put")
|
f" {dirs_new} folders created, {len(keeps)} files stayed put")
|
||||||
print(f"journal: {journal.path} — undo with: media-apply.py rollback"
|
print(f"journal: {journal.path} — undo with: media-apply.py rollback"
|
||||||
f" --journal {journal.path} --root {root} --apply")
|
f" --journal {journal.path} --root {root} --apply")
|
||||||
print(METADATA_HINT)
|
metadata_note(args)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
@@ -386,7 +403,7 @@ def do_rollback(args, root, journal):
|
|||||||
t=datetime.datetime.now(datetime.timezone.utc).isoformat())
|
t=datetime.datetime.now(datetime.timezone.utc).isoformat())
|
||||||
print(f"rolled back: {undone} files back where the census found them,"
|
print(f"rolled back: {undone} files back where the census found them,"
|
||||||
f" {dirs_gone} empty folders removed, {recreated} swept folders recreated")
|
f" {dirs_gone} empty folders removed, {recreated} swept folders recreated")
|
||||||
print(METADATA_HINT)
|
metadata_note(args)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
@@ -398,6 +415,9 @@ def main():
|
|||||||
ap.add_argument("--journal")
|
ap.add_argument("--journal")
|
||||||
ap.add_argument("--apply", action="store_true", help="actually change the library")
|
ap.add_argument("--apply", action="store_true", help="actually change the library")
|
||||||
ap.add_argument("--force", action="store_true", help="relax the opinion guards")
|
ap.add_argument("--force", action="store_true", help="relax the opinion guards")
|
||||||
|
ap.add_argument("--metadata-view", default=METADATA_VIEW,
|
||||||
|
help="overlay the media servers read through; the sidecar"
|
||||||
|
" note prints only when this path is a mountpoint")
|
||||||
args = ap.parse_args()
|
args = ap.parse_args()
|
||||||
|
|
||||||
root = os.path.abspath(args.root)
|
root = os.path.abspath(args.root)
|
||||||
|
|||||||
@@ -374,7 +374,10 @@ PY
|
|||||||
# apply_plan — plan, then apply for real; the journal goes in the tmpdir.
|
# apply_plan — plan, then apply for real; the journal goes in the tmpdir.
|
||||||
apply_plan() {
|
apply_plan() {
|
||||||
JOURNAL="$BATS_TEST_TMPDIR/journal.jsonl"
|
JOURNAL="$BATS_TEST_TMPDIR/journal.jsonl"
|
||||||
run python3 "$APPLY" apply --plan "$OUT" --root "$LIB" --journal "$JOURNAL" --apply
|
# `/` is always a mountpoint, so the overlay note behaves as it does on a
|
||||||
|
# host that runs the metadata view.
|
||||||
|
run python3 "$APPLY" apply --plan "$OUT" --root "$LIB" \
|
||||||
|
--journal "$JOURNAL" --metadata-view / --apply
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "a run without --apply says what it would do and touches nothing" {
|
@test "a run without --apply says what it would do and touches nothing" {
|
||||||
@@ -409,6 +412,32 @@ print(sum(1 for l in open(sys.argv[1]) if json.loads(l).get("op") == "move"))' "
|
|||||||
[ "$status" -eq 0 ]
|
[ "$status" -eq 0 ]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# The overlay note is only true where the metadata view is really mounted. On a
|
||||||
|
# host whose media servers bind the array directly (dankserver today: both
|
||||||
|
# quadlets mount /mnt/entropy, /var/lib/media-meta does not exist) the sidecars
|
||||||
|
# moved with the media, and "next: run reap" is an order that cannot be carried
|
||||||
|
# out. --metadata-view is how the caller names that path; the default is the
|
||||||
|
# host's view path, and the note rides only when it is a mountpoint.
|
||||||
|
@test "an apply with no mounted metadata view does not order a reap" {
|
||||||
|
plan
|
||||||
|
JOURNAL="$BATS_TEST_TMPDIR/journal.jsonl"
|
||||||
|
run python3 "$APPLY" apply --plan "$OUT" --root "$LIB" --journal "$JOURNAL" --apply
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"moved"* ]]
|
||||||
|
[[ "$output" != *"media-view.sh reap"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "a rollback with no mounted metadata view does not order a reap" {
|
||||||
|
plan
|
||||||
|
JOURNAL="$BATS_TEST_TMPDIR/journal.jsonl"
|
||||||
|
run python3 "$APPLY" apply --plan "$OUT" --root "$LIB" --journal "$JOURNAL" --apply
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
run python3 "$APPLY" rollback --journal "$JOURNAL" --root "$LIB" --apply
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"rolled back"* ]]
|
||||||
|
[[ "$output" != *"media-view.sh reap"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
@test "a target already holding a different file stops the whole run" {
|
@test "a target already holding a different file stops the whole run" {
|
||||||
plan
|
plan
|
||||||
JOURNAL="$BATS_TEST_TMPDIR/journal.jsonl"
|
JOURNAL="$BATS_TEST_TMPDIR/journal.jsonl"
|
||||||
|
|||||||
Reference in new issue
Block a user