fix(mounts): Ergebnis pruefen statt glauben - "mount" meldet Erfolg und liefert nicht
Ampel / ampel (push) Successful in 28s
Ampel / ampel (push) Successful in 28s
Nachtrag zum vorigen Commit, weil der die Freigabe noch nicht zurueckbrachte. Dreimal reproduziert: Beim API-Start meldete `mount` Rueckgabewert 0, das Log schrieb "rippy: eingehaengt", /proc/mounts zeigte GENAU EINE korrekt aussehende Schicht mit den richtigen Optionen - und `timeout 6 ls /app/media/rippy` lief trotzdem in die Zeitgrenze. Derselbe Ablauf ein zweites Mal, per POST /storage-mounts/rippy/repair, stellte sie sofort her (30 s, danach erreichbar). Der erste SMB-Sitzungsaufbau kurz nach dem Container-Start geht also gelegentlich schief, ohne es zu melden. Ein Rueckgabewert von `mount` beweist deshalb nichts. Jetzt: Nach dem Mount wird geprueft, ob die Freigabe ANTWORTET (ist_erreichbar, harte Grenze). Wenn nicht, einmal loesen und neu mounten. Hilft auch das nicht, fliegt ein Fehler mit Klartext - dann steht im Log "FEHLER" statt "eingehaengt", was schlicht die Wahrheit ist, und der Nutzer bekommt den Hinweis auf die Reparatur-Funktion statt eines Rips, der spaeter still scheitert. Damit ist die Kette geschlossen: os.path.isdir kann nicht mehr im Kernel haengen (voriger Commit), die Rohdaten-Schleife stirbt nicht mehr daran, und ein Mount gilt erst als hergestellt, wenn er antwortet. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -299,6 +299,19 @@ def mounten(name: str, typ: str, quelle: str, optionen: str = "",
|
||||
else:
|
||||
raise RuntimeError(f"Unbekannter Typ: {typ} (nfs oder cifs)")
|
||||
|
||||
# Zwei Versuche. Grund (26.07.2026, dreimal reproduziert): Beim API-Start
|
||||
# meldete `mount` Erfolg — und die Freigabe antwortete danach trotzdem
|
||||
# nicht (`timeout 6 ls` lief in die Grenze, eine einzige, korrekt
|
||||
# aussehende Mount-Schicht in /proc/mounts). Derselbe Ablauf ein zweites
|
||||
# Mal, per POST /storage-mounts/<name>/repair, stellte sie sofort her.
|
||||
# Der erste SMB-Sitzungsaufbau kurz nach dem Container-Start geht also
|
||||
# gelegentlich schief, ohne es zu melden.
|
||||
#
|
||||
# Deshalb wird das Ergebnis GEPRÜFT statt geglaubt: Antwortet die
|
||||
# Freigabe nach dem Mount nicht, wird einmal gelöst und neu gemountet.
|
||||
# Und wenn das auch nicht hilft, fliegt ein Fehler — dann steht im Log
|
||||
# „FEHLER" statt „eingehängt", was schlicht die Wahrheit ist.
|
||||
for versuch in (1, 2):
|
||||
ergebnis = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
||||
if ergebnis.returncode != 0:
|
||||
fehler = (ergebnis.stderr or ergebnis.stdout or "").strip()
|
||||
@@ -310,7 +323,15 @@ def mounten(name: str, typ: str, quelle: str, optionen: str = "",
|
||||
"NAS meist ablehnen."
|
||||
)
|
||||
raise RuntimeError(f"mount schlug fehl: {fehler[:300]}{hinweis}")
|
||||
if ist_erreichbar(name):
|
||||
return schreibtest(ziel)
|
||||
if versuch == 1:
|
||||
_lazy_umount(ziel)
|
||||
raise RuntimeError(
|
||||
f"{quelle} wurde eingehängt, antwortet aber nicht (zwei Versuche). "
|
||||
"Läuft die Freigabe? Bei einem NAS im Ruhezustand hilft meist ein "
|
||||
"erneutes Einhängen über Einstellungen → Speicherziele → Reparieren."
|
||||
)
|
||||
finally:
|
||||
if creds_datei:
|
||||
try:
|
||||
|
||||
@@ -183,8 +183,9 @@ def test_mounten_geht_bei_totem_mount_den_reparatur_weg(monkeypatch):
|
||||
import mounts
|
||||
|
||||
ablauf = []
|
||||
antworten = iter([False, True]) # vorher tot, nach dem Mount erreichbar
|
||||
monkeypatch.setattr(mounts.os, "makedirs", lambda *a, **k: None)
|
||||
monkeypatch.setattr(mounts, "ist_erreichbar", lambda name: False)
|
||||
monkeypatch.setattr(mounts, "ist_erreichbar", lambda name: next(antworten))
|
||||
monkeypatch.setattr(mounts, "_stale_mounts_loesen",
|
||||
lambda ziel: ablauf.append("loesen"))
|
||||
monkeypatch.setattr(mounts, "schreibtest", lambda p: True)
|
||||
@@ -202,6 +203,38 @@ def test_mounten_geht_bei_totem_mount_den_reparatur_weg(monkeypatch):
|
||||
assert ablauf == ["loesen", "mount"]
|
||||
|
||||
|
||||
def test_mounten_prueft_das_ergebnis_und_versucht_es_zweimal(monkeypatch):
|
||||
"""Befund 26.07.2026, dreimal reproduziert: `mount` meldete Erfolg, und die
|
||||
Freigabe antwortete danach TROTZDEM nicht (eine einzige, korrekt aussehende
|
||||
Schicht in /proc/mounts). Derselbe Ablauf ein zweites Mal stellte sie sofort
|
||||
her. Also wird das Ergebnis geprueft statt geglaubt."""
|
||||
import types
|
||||
|
||||
import mounts
|
||||
|
||||
ablauf = []
|
||||
# nie erreichbar: vorher, nach Versuch 1, nach Versuch 2
|
||||
monkeypatch.setattr(mounts.os, "makedirs", lambda *a, **k: None)
|
||||
monkeypatch.setattr(mounts, "ist_erreichbar", lambda name: False)
|
||||
monkeypatch.setattr(mounts, "_stale_mounts_loesen", lambda ziel: None)
|
||||
monkeypatch.setattr(mounts, "_lazy_umount", lambda ziel: ablauf.append("lazy"))
|
||||
monkeypatch.setattr(mounts, "schreibtest", lambda p: True)
|
||||
monkeypatch.setattr(
|
||||
mounts.subprocess, "run",
|
||||
lambda cmd, **k: (ablauf.append(cmd[0]),
|
||||
types.SimpleNamespace(returncode=0, stdout="", stderr=""))[1])
|
||||
|
||||
import pytest
|
||||
|
||||
with pytest.raises(RuntimeError) as fehler:
|
||||
mounts.mounten("rippy", "cifs", "//nas/rippy")
|
||||
|
||||
# Zweimal gemountet, dazwischen einmal geloest
|
||||
assert ablauf == ["mount", "lazy", "mount"]
|
||||
# Und die Meldung sagt die Wahrheit statt "eingehaengt"
|
||||
assert "antwortet aber nicht" in str(fehler.value)
|
||||
|
||||
|
||||
def test_mounten_laesst_gesunden_mount_in_ruhe(monkeypatch):
|
||||
"""Antwortet die Freigabe, bleibt sie unangetastet - kein Loesen, kein
|
||||
zweites Mounten (das wuerde stapeln)."""
|
||||
|
||||
Reference in New Issue
Block a user