Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6a082cf77c |
+29
-1
@@ -73,6 +73,30 @@ def schreibtest(pfad: str) -> bool:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _stale_mounts_loesen(ziel: str) -> int:
|
||||||
|
"""Loest ALLE (evtl. gestapelten/toten) Mounts an `ziel` per lazy umount.
|
||||||
|
|
||||||
|
Warum: mounten() faellt bei einem TOTEN Mount (os.path.ismount wirft OSError)
|
||||||
|
auf den echten `mount` durch — der stapelt dann auf die Leiche. Vorfall 24.07.:
|
||||||
|
ueber viele Neustarts 12 Schichten, die tote oberste blockierte jeden Zugriff
|
||||||
|
(ls-Timeout, obwohl SMB-445 offen). Erst alle Schichten loesen macht das
|
||||||
|
Re-Mounten idempotent. `umount -l` (lazy) haengt nicht an einem toten CIFS.
|
||||||
|
Rueckgabe: Zahl der geloesten Schichten.
|
||||||
|
"""
|
||||||
|
geloest = 0
|
||||||
|
for _ in range(20): # harte Obergrenze gegen Endlosschleife
|
||||||
|
try:
|
||||||
|
ergebnis = subprocess.run(
|
||||||
|
["umount", "-l", ziel], capture_output=True, timeout=10
|
||||||
|
)
|
||||||
|
except (OSError, subprocess.TimeoutExpired):
|
||||||
|
break
|
||||||
|
if ergebnis.returncode != 0:
|
||||||
|
break # nichts (mehr) gemountet
|
||||||
|
geloest += 1
|
||||||
|
return geloest
|
||||||
|
|
||||||
|
|
||||||
def uebersetze_smb_fehler(fehler: str, mit_credentials: bool) -> str:
|
def uebersetze_smb_fehler(fehler: str, mit_credentials: bool) -> str:
|
||||||
"""Pure Funktion (testbar): NT_STATUS-Kauderwelsch → handelbarer Klartext.
|
"""Pure Funktion (testbar): NT_STATUS-Kauderwelsch → handelbarer Klartext.
|
||||||
|
|
||||||
@@ -155,7 +179,11 @@ def mounten(name: str, typ: str, quelle: str, optionen: str = "",
|
|||||||
if os.path.ismount(ziel):
|
if os.path.ismount(ziel):
|
||||||
return schreibtest(ziel)
|
return schreibtest(ziel)
|
||||||
except OSError:
|
except OSError:
|
||||||
pass # toter Mount → als „nicht gemountet" behandeln und neu mounten
|
pass # toter Mount → unten erst lösen, dann frisch mounten (kein Stapeln)
|
||||||
|
|
||||||
|
# Idempotent: etwaige (auch gestapelte/tote) Alt-Mounts erst lösen, damit der
|
||||||
|
# folgende mount NICHT auf eine Leiche stapelt (Vorfall 24.07.: 12 Schichten).
|
||||||
|
_stale_mounts_loesen(ziel)
|
||||||
|
|
||||||
creds_datei = None
|
creds_datei = None
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -39,3 +39,33 @@ def test_unbekannter_fehler_bleibt_erhalten_und_gekappt():
|
|||||||
def test_validiere_name_bleibt_streng():
|
def test_validiere_name_bleibt_streng():
|
||||||
assert validiere_name("nas-filme")
|
assert validiere_name("nas-filme")
|
||||||
assert not validiere_name("NAS Filme")
|
assert not validiere_name("NAS Filme")
|
||||||
|
|
||||||
|
|
||||||
|
def test_stale_mounts_loesen_loest_bis_nichts_mehr_geht(monkeypatch):
|
||||||
|
"""Löst gestapelte Schichten per lazy umount, bis umount nichts mehr findet
|
||||||
|
(returncode != 0), und meldet die Zahl der gelösten Schichten."""
|
||||||
|
import types
|
||||||
|
|
||||||
|
import mounts
|
||||||
|
|
||||||
|
aufrufe = []
|
||||||
|
|
||||||
|
def fake_run(cmd, **kwargs):
|
||||||
|
aufrufe.append(cmd)
|
||||||
|
rc = 0 if len(aufrufe) <= 3 else 1 # 3 Schichten lösen, dann leer
|
||||||
|
return types.SimpleNamespace(returncode=rc, stdout=b"", stderr=b"")
|
||||||
|
|
||||||
|
monkeypatch.setattr(mounts.subprocess, "run", fake_run)
|
||||||
|
assert mounts._stale_mounts_loesen("/app/media/x") == 3
|
||||||
|
assert all(cmd[:2] == ["umount", "-l"] for cmd in aufrufe)
|
||||||
|
|
||||||
|
|
||||||
|
def test_mounten_loest_stale_vor_dem_mount():
|
||||||
|
"""Regression (Vorfall 24.07.): mounten() muss Alt-Mounts LÖSEN, bevor es neu
|
||||||
|
mountet — sonst stapelt es auf eine Mount-Leiche (12 Schichten, ls-Timeout)."""
|
||||||
|
import inspect
|
||||||
|
|
||||||
|
import mounts
|
||||||
|
|
||||||
|
quelle = inspect.getsource(mounts.mounten)
|
||||||
|
assert "_stale_mounts_loesen(ziel)" in quelle
|
||||||
|
|||||||
Reference in New Issue
Block a user