Trading-Plattform-Pruefung: Host nicht erreichbar gibt eine Zeile, --tag frueh geprueft (Paket NT, Task 729)
- abendkontrolle.py: Exit 255 oder Zeitlimit bei Summe oder Bestaetigung beendet die Sicherung mit genau einer Zeile in der Notiz von heute und Exit 1; ein Abbruch im Archiv laesst nur die Instanz scheitern. --tag ausser der Form endet gleich mit Exit 2, ohne Notiz und ssh. - abendkontrolle_dockerhost_angriffe.py: sieben neue Faelle, zwei angepasste, Mutanten E729-01 bis E729-15 und E729-25 (38 Faelle, 56 Mutanten, keiner ueberlebt). - abendkontrolle_wsl.py: Kopf nennt die neuen fehler-Gruende. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_0126RDfXiECscXipzf67uypJ
This commit is contained in:
1 parent
02ff0e878d
commit
88ec4fd40f
3 files changed
+352
-25
No files matched your search
@@ -35,6 +35,10 @@ die Summendatei auf D: fehlt oder abweicht, <tag>-<instanz>.tar ueber denselben
|
|||||||
pruefen, nach SICHERUNG_ZIEL/<tag>/<instanz>.tar umbenennen, beim Host bestaetigen, zuletzt
|
pruefen, nach SICHERUNG_ZIEL/<tag>/<instanz>.tar umbenennen, beim Host bestaetigen, zuletzt
|
||||||
<instanz>.tar.sha256 schreiben; eine Zeile in der Notiz je Tag mit Neuem. Solange SICHERUNG_ERSTER_TAG
|
<instanz>.tar.sha256 schreiben; eine Zeile in der Notiz je Tag mit Neuem. Solange SICHERUNG_ERSTER_TAG
|
||||||
None ist, holt sie nichts. Den Abend-Ausloeser legt abendkontrolle_einrichten.ps1 in Ortszeit an (S10).
|
None ist, holt sie nichts. Den Abend-Ausloeser legt abendkontrolle_einrichten.ps1 in Ortszeit an (S10).
|
||||||
|
Seit Paket NT (Quorum 4): Endet die Frage nach einer Summe oder die Bestaetigung mit ssh-Exit 255 oder
|
||||||
|
im Zeitlimit, ist der Docker-Host nicht erreichbar, und die Sicherung hoert sofort auf: eine Zeile in der
|
||||||
|
Notiz von heute, Exit 1. Bricht nur das Archiv so ab, scheitert allein diese Instanz. --tag ausser der
|
||||||
|
Form JJJJ-MM-TT oder ohne gueltiges Datum endet gleich nach dem Zerlegen mit Exit 2, ohne Notiz und ssh.
|
||||||
Aufruf von Hand: python abendkontrolle.py [--tag JJJJ-MM-TT] [--vault D:\\Claude] [--ohne-wsl <json-datei>]
|
Aufruf von Hand: python abendkontrolle.py [--tag JJJJ-MM-TT] [--vault D:\\Claude] [--ohne-wsl <json-datei>]
|
||||||
[--quelle wsl|dockerhost] [--quelle-ibkr wsl|dockerhost] [--sicherung-ziel <ordner>]
|
[--quelle wsl|dockerhost] [--quelle-ibkr wsl|dockerhost] [--sicherung-ziel <ordner>]
|
||||||
"""
|
"""
|
||||||
@@ -104,7 +108,7 @@ SICHERUNG_ZIEL = Path(r"D:\Trading-Sicherung")
|
|||||||
SICHERUNG_ERSTER_TAG: date | None = None
|
SICHERUNG_ERSTER_TAG: date | None = None
|
||||||
# So viele Kalendertage vor heute fragt jeder Lauf, dazu heute. Das Fenster liegt in der Aufbewahrung
|
# So viele Kalendertage vor heute fragt jeder Lauf, dazu heute. Das Fenster liegt in der Aufbewahrung
|
||||||
# des Hosts: Er haelt die neuesten zehn Sicherungstage, nur werktags gesichert, also gut zwei Kalenderwochen.
|
# des Hosts: Er haelt die neuesten zehn Sicherungstage, nur werktags gesichert, also gut zwei Kalenderwochen.
|
||||||
# (S3). Die Sicherung entsteht 16:45 New Yorker Zeit, in Berlin meist 22:45, vom 25.10. bis 31.10.2026
|
# Die Sicherung entsteht 16:45 New Yorker Zeit, in Berlin meist 22:45, vom 25.10. bis 31.10.2026
|
||||||
# und vom 14.3. bis 27.3.2027 schon 21:45. Die Aufgabe startet 23:15 Ortszeit (S10); fehlt die
|
# und vom 14.3. bis 27.3.2027 schon 21:45. Die Aufgabe startet 23:15 Ortszeit (S10); fehlt die
|
||||||
# Sicherung von heute trotzdem noch, ist das kein Fehlschlag.
|
# Sicherung von heute trotzdem noch, ist das kein Fehlschlag.
|
||||||
SICHERUNG_NACHHOLEN_TAGE = 10
|
SICHERUNG_NACHHOLEN_TAGE = 10
|
||||||
@@ -119,6 +123,20 @@ SUMMENZEILE = re.compile(r"([0-9a-f]{64}) (\S+)\n")
|
|||||||
TAGFORM = re.compile(r"[0-9]{4}-[0-9]{2}-[0-9]{2}")
|
TAGFORM = re.compile(r"[0-9]{4}-[0-9]{2}-[0-9]{2}")
|
||||||
|
|
||||||
|
|
||||||
|
def tag_gueltig(tag: str) -> bool:
|
||||||
|
"""Ist tag ein Datum JJJJ-MM-TT, das es gibt, nur mit ASCII-Ziffern? Seit Paket NT fuer --tag in main.
|
||||||
|
|
||||||
|
TAGFORM allein liesse 2026-13-05 durch, date.fromisoformat allein seit Python 3.11 auch 20261005.
|
||||||
|
"""
|
||||||
|
if not TAGFORM.fullmatch(tag):
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
date.fromisoformat(tag)
|
||||||
|
except ValueError:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def faellige_tage(jetzt: datetime, ablage: Path, erster_tag: date = ERSTER_TAG, endung: str = "") -> list[str]:
|
def faellige_tage(jetzt: datetime, ablage: Path, erster_tag: date = ERSTER_TAG, endung: str = "") -> list[str]:
|
||||||
tage = []
|
tage = []
|
||||||
for zurueck in range(NACHHOLEN_TAGE, -1, -1):
|
for zurueck in range(NACHHOLEN_TAGE, -1, -1):
|
||||||
@@ -223,16 +241,36 @@ def summe_von(datei: Path) -> str:
|
|||||||
return summe.hexdigest()
|
return summe.hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
class HostNichtErreichbar(Exception):
|
||||||
|
"""ssh selbst scheiterte (Exit 255) oder ein Befehl der Sicherung lief in sein Zeitlimit (seit Paket NT).
|
||||||
|
|
||||||
|
Beim Archiv macht hole_sicherung daraus den Fehlschlag dieser einen Instanz.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
def ssh_lauf(befehl: str, timeout, stdout=None) -> subprocess.CompletedProcess:
|
def ssh_lauf(befehl: str, timeout, stdout=None) -> subprocess.CompletedProcess:
|
||||||
"""Ein Befehl der Sicherung beim Host. Mit stdout (offene Datei) geht die Ausgabe dorthin.
|
"""Ein Befehl der Sicherung beim Host. Mit stdout (offene Datei) geht die Ausgabe dorthin.
|
||||||
|
|
||||||
Eigene Funktion, damit die Angriffsfaelle Zeitlimit und Dauer jedes Befehls sehen.
|
Eigene Funktion, damit die Angriffsfaelle Zeitlimit und Dauer jedes Befehls sehen. Seit Paket NT
|
||||||
|
(Quorum 4, G1): abendkontrolle-lesen.sh endet mit 0 bis 4. Exit 255 meldet ssh selbst: keine
|
||||||
|
Verbindung oder Anmeldung, aber auch ein Befehl, den auf dem Host ein Signal beendet. Er und ein
|
||||||
|
abgelaufenes Zeitlimit heissen: Der Docker-Host ist nicht erreichbar (HostNichtErreichbar).
|
||||||
"""
|
"""
|
||||||
|
try:
|
||||||
if stdout is None:
|
if stdout is None:
|
||||||
return subprocess.run(ssh_aufruf(befehl), capture_output=True, timeout=timeout, creationflags=KEIN_FENSTER)
|
lauf = subprocess.run(
|
||||||
return subprocess.run(
|
ssh_aufruf(befehl), capture_output=True, timeout=timeout, creationflags=KEIN_FENSTER,
|
||||||
ssh_aufruf(befehl), stdout=stdout, stderr=subprocess.PIPE, timeout=timeout, creationflags=KEIN_FENSTER,
|
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
lauf = subprocess.run(
|
||||||
|
ssh_aufruf(befehl), stdout=stdout, stderr=subprocess.PIPE, timeout=timeout,
|
||||||
|
creationflags=KEIN_FENSTER,
|
||||||
|
)
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
raise HostNichtErreichbar(f"{befehl}: Zeitlimit {timeout} s abgelaufen") from None
|
||||||
|
if lauf.returncode == 255:
|
||||||
|
raise HostNichtErreichbar(f"{befehl}: {fehlerzeile(lauf)}")
|
||||||
|
return lauf
|
||||||
|
|
||||||
|
|
||||||
def summe_auf_d(ordner: Path, instanz: str) -> str | None:
|
def summe_auf_d(ordner: Path, instanz: str) -> str | None:
|
||||||
@@ -265,10 +303,8 @@ def sicherungstage(jetzt: datetime, tag: str | None = None) -> list[str]:
|
|||||||
if erster is None:
|
if erster is None:
|
||||||
return []
|
return []
|
||||||
if tag is not None:
|
if tag is not None:
|
||||||
# Der Tag wird Teil eines Pfads und eines Befehls auf dem Host: nur JJJJ-MM-TT.
|
# Der Tag wird Teil eines Pfads und eines Befehls auf dem Host. Seine Form (JJJJ-MM-TT, ein Datum,
|
||||||
if not TAGFORM.fullmatch(tag):
|
# das es gibt) prueft seit Paket NT main gleich nach dem Zerlegen der Argumente (tag_gueltig).
|
||||||
raise ValueError(f"--tag {tag!r} ist kein Datum JJJJ-MM-TT")
|
|
||||||
date.fromisoformat(tag)
|
|
||||||
return [tag]
|
return [tag]
|
||||||
tage = []
|
tage = []
|
||||||
for zurueck in range(SICHERUNG_NACHHOLEN_TAGE, -1, -1):
|
for zurueck in range(SICHERUNG_NACHHOLEN_TAGE, -1, -1):
|
||||||
@@ -337,7 +373,14 @@ def hole_sicherung(tag: str, instanz: str, ziel: Path, summe: str) -> str:
|
|||||||
summendatei = ordner / f"{instanz}.tar.sha256"
|
summendatei = ordner / f"{instanz}.tar.sha256"
|
||||||
try:
|
try:
|
||||||
with teil.open("wb") as strom:
|
with teil.open("wb") as strom:
|
||||||
|
try:
|
||||||
lauf = ssh_lauf(f"sicherung {name}", SICHERUNG_TIMEOUT_S, strom)
|
lauf = ssh_lauf(f"sicherung {name}", SICHERUNG_TIMEOUT_S, strom)
|
||||||
|
except HostNichtErreichbar as abbruch:
|
||||||
|
# Seit Paket NT (Plankritik, Befund 1): Mitten im Archiv heissen Zeitlimit und Exit 255 nicht,
|
||||||
|
# dass der Host fehlt; er hat die Summe gerade beantwortet. Nur diese Instanz scheitert, und
|
||||||
|
# ein einzelnes zu langsames Archiv haelt nicht jeden Lauf an derselben Stelle an. Ist der
|
||||||
|
# Host wirklich weg, merkt es die naechste Frage nach einer Summe.
|
||||||
|
raise RuntimeError(f"Archiv nicht geholt ({abbruch})") from None
|
||||||
if lauf.returncode != 0:
|
if lauf.returncode != 0:
|
||||||
raise RuntimeError(f"Archiv nicht geholt ({fehlerzeile(lauf)})")
|
raise RuntimeError(f"Archiv nicht geholt ({fehlerzeile(lauf)})")
|
||||||
if summe_von(teil) != summe:
|
if summe_von(teil) != summe:
|
||||||
@@ -393,6 +436,24 @@ def hole_sicherungen(jetzt: datetime, vault: Path, ablage: Path, ziel: Path, ins
|
|||||||
if geholt:
|
if geholt:
|
||||||
getan = True
|
getan = True
|
||||||
logge(ablage, f"sicherung {kandidat}-{instanz}: abgelegt und bestaetigt")
|
logge(ablage, f"sicherung {kandidat}-{instanz}: abgelegt und bestaetigt")
|
||||||
|
except HostNichtErreichbar as ausnahme:
|
||||||
|
# Seit Paket NT (Quorum 4, G1): Ohne Host scheiterte sonst jeder Tag des Fensters mit eigener
|
||||||
|
# Zeile, auch in neu angelegten Notizen von Wochenenden. Die Sicherung hoert hier auf: eine
|
||||||
|
# Zeile in der Notiz von heute, Exit 1. Fertige Tage behalten ihre Zeilen; was der
|
||||||
|
# angefangene Tag schon hat, nennt diese Zeile. Auf D: entsteht danach nichts mehr.
|
||||||
|
logge(ablage, f"sicherung {kandidat}-{instanz}: Docker-Host nicht erreichbar ({ausnahme}), Sicherung abgebrochen")
|
||||||
|
zeile = (
|
||||||
|
f"- {jetzt:%H:%M} Uhr — {SICHERUNG_KOPF}: **gescheitert** #needs-review. Docker-Host nicht "
|
||||||
|
f"erreichbar ({str(ausnahme)[:200]}), Sicherung abgebrochen."
|
||||||
|
)
|
||||||
|
if stuecke:
|
||||||
|
zeile += f" Bis dahin am {kandidat}: {'; '.join(stuecke)}."
|
||||||
|
zeile += " Der nächste Lauf versucht es erneut."
|
||||||
|
try:
|
||||||
|
schreibe_notiz(vault, jetzt.date().isoformat(), zeile)
|
||||||
|
except Exception as zweite:
|
||||||
|
logge(ablage, f"sicherung: Notiz nicht geschrieben {zweite!r}")
|
||||||
|
return 1
|
||||||
except Exception as ausnahme:
|
except Exception as ausnahme:
|
||||||
gescheitert = True
|
gescheitert = True
|
||||||
stuecke.append(f"{instanz} gescheitert ({type(ausnahme).__name__}: {str(ausnahme)[:200]})")
|
stuecke.append(f"{instanz} gescheitert ({type(ausnahme).__name__}: {str(ausnahme)[:200]})")
|
||||||
@@ -547,6 +608,13 @@ def main(argv=None) -> int:
|
|||||||
help="Ordner fuer die Sicherungen statt SICHERUNG_ZIEL (Angriffsfaelle)",
|
help="Ordner fuer die Sicherungen statt SICHERUNG_ZIEL (Angriffsfaelle)",
|
||||||
)
|
)
|
||||||
argumente = zerleger.parse_args(argv)
|
argumente = zerleger.parse_args(argv)
|
||||||
|
# Seit Paket NT (Quorum 4): --tag gleich hier pruefen. Der Tag wird Name der Notiz, Pfad unter
|
||||||
|
# SICHERUNG_ZIEL und Teil eines Befehls auf dem Host; vorher legte ein Tag ausser der Form
|
||||||
|
# Inbox/<tag>.md an, mit ../ auch ausserhalb von Inbox/. Leer zaehlt als ausser der Form. Die
|
||||||
|
# Ausgabe ist ASCII (!a), damit kein Zeichen an der Kodierung von stderr scheitert.
|
||||||
|
if argumente.tag is not None and not tag_gueltig(argumente.tag):
|
||||||
|
print(f"abendkontrolle: --tag {argumente.tag!a} ist kein Datum JJJJ-MM-TT", file=sys.stderr)
|
||||||
|
return 2
|
||||||
# Seit Paket E9: Das Zeitbudget der Sicherung zaehlt ab hier, also mit der Zeit fuer die
|
# Seit Paket E9: Das Zeitbudget der Sicherung zaehlt ab hier, also mit der Zeit fuer die
|
||||||
# Ergebnisse (bis zu zwoelf Abfragen mit je SSH_TIMEOUT_S; Plankritik Runde 2, Befund 1).
|
# Ergebnisse (bis zu zwoelf Abfragen mit je SSH_TIMEOUT_S; Plankritik Runde 2, Befund 1).
|
||||||
frist = monotonic() + SICHERUNG_BUDGET_S
|
frist = monotonic() + SICHERUNG_BUDGET_S
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ gegen eine Host-Ablage im Wegwerf-Ordner; ANGRIFF_SICHERUNG stoert sie gezielt.
|
|||||||
lauf() setzt SICHERUNG_ERSTER_TAG auf None und SICHERUNG_ZIEL in den Wegwerf-Ordner;
|
lauf() setzt SICHERUNG_ERSTER_TAG auf None und SICHERUNG_ZIEL in den Wegwerf-Ordner;
|
||||||
sicherungslauf() lenkt mit --sicherung-ziel um, und das Ziel der Konstante muss dort leer bleiben.
|
sicherungslauf() lenkt mit --sicherung-ziel um, und das Ziel der Konstante muss dort leer bleiben.
|
||||||
Nichts landet unter D:\\Trading-Sicherung.
|
Nichts landet unter D:\\Trading-Sicherung.
|
||||||
|
Seit Paket NT (Quorum 4): ANGRIFF_HOST_WEG=<n> laesst die Attrappe jeden Aufruf nach den ersten n mit
|
||||||
|
Exit 255 enden wie ssh ohne Host; ANGRIFF_SICHERUNG=langsam haelt jeden Sicherungsbefehl 5 s auf,
|
||||||
|
ANGRIFF_SICHERUNG=archiv_unlesbar endet beim Archiv wie abendkontrolle-lesen.sh ohne Leserecht.
|
||||||
Aufruf: python abendkontrolle_dockerhost_angriffe.py [--mutanten]
|
Aufruf: python abendkontrolle_dockerhost_angriffe.py [--mutanten]
|
||||||
Exit 0 ohne Abweichung, 2 mit Abweichung. --mutanten faehrt MUTANTEN auf Kopien von
|
Exit 0 ohne Abweichung, 2 mit Abweichung. --mutanten faehrt MUTANTEN auf Kopien von
|
||||||
abendkontrolle.py und MUTANTEN_AUFGABE auf Kopien von abendkontrolle_einrichten.ps1: Jeder genannte
|
abendkontrolle.py und MUTANTEN_AUFGABE auf Kopien von abendkontrolle_einrichten.ps1: Jeder genannte
|
||||||
@@ -47,6 +50,13 @@ import sys
|
|||||||
|
|
||||||
with open(os.environ["ANGRIFF_PROTOKOLL"], "a", encoding="utf-8") as datei:
|
with open(os.environ["ANGRIFF_PROTOKOLL"], "a", encoding="utf-8") as datei:
|
||||||
datei.write(json.dumps(sys.argv[1:]) + "\n")
|
datei.write(json.dumps(sys.argv[1:]) + "\n")
|
||||||
|
# Seit Paket NT: Nach ANGRIFF_HOST_WEG Aufrufen ist der Host weg; ssh selbst endet dann mit 255.
|
||||||
|
weg = os.environ.get("ANGRIFF_HOST_WEG", "")
|
||||||
|
if weg:
|
||||||
|
with open(os.environ["ANGRIFF_PROTOKOLL"], encoding="utf-8") as datei:
|
||||||
|
if len(datei.read().splitlines()) > int(weg):
|
||||||
|
sys.stderr.write("ssh: connect to host docker-host port 2498: Connection timed out\n")
|
||||||
|
sys.exit(255)
|
||||||
befehl = sys.argv[-1]
|
befehl = sys.argv[-1]
|
||||||
if befehl.startswith("sicherung"):
|
if befehl.startswith("sicherung"):
|
||||||
# Seit Paket E9: die Lesebefehle der Sicherung, so streng wie abendkontrolle-lesen.sh (Task 724),
|
# Seit Paket E9: die Lesebefehle der Sicherung, so streng wie abendkontrolle-lesen.sh (Task 724),
|
||||||
@@ -68,6 +78,11 @@ if befehl.startswith("sicherung"):
|
|||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
name = lesen.group(2) if lesen else bestaetigen.group(1)
|
name = lesen.group(2) if lesen else bestaetigen.group(1)
|
||||||
gestoert = stoerung if not nur or name.endswith("-" + nur) else ""
|
gestoert = stoerung if not nur or name.endswith("-" + nur) else ""
|
||||||
|
if gestoert == "langsam":
|
||||||
|
# Seit Paket NT: laenger als das Zeitlimit, das der Fall in seiner Kopie setzt.
|
||||||
|
import time
|
||||||
|
|
||||||
|
time.sleep(5)
|
||||||
archiv = os.path.join(host, name + ".tar")
|
archiv = os.path.join(host, name + ".tar")
|
||||||
summendatei = archiv + ".sha256"
|
summendatei = archiv + ".sha256"
|
||||||
gemeint = archiv if lesen and lesen.group(1) == "sicherung" else summendatei
|
gemeint = archiv if lesen and lesen.group(1) == "sicherung" else summendatei
|
||||||
@@ -84,6 +99,10 @@ if befehl.startswith("sicherung"):
|
|||||||
sys.stdout.buffer.write(inhalt)
|
sys.stdout.buffer.write(inhalt)
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
if lesen:
|
if lesen:
|
||||||
|
if gestoert == "archiv_unlesbar":
|
||||||
|
# Seit Paket NT: wie abendkontrolle-lesen.sh bei einem Archiv ohne Leserecht (Exit 1).
|
||||||
|
sys.stderr.write("abendkontrolle-lesen: " + name + ".tar liegt auf dem Docker-Host, laesst sich aber nicht lesen\n")
|
||||||
|
sys.exit(1)
|
||||||
if gestoert == "kaputt":
|
if gestoert == "kaputt":
|
||||||
inhalt = inhalt[:-1] + bytes([inhalt[-1] ^ 1])
|
inhalt = inhalt[:-1] + bytes([inhalt[-1] ^ 1])
|
||||||
if gestoert == "abbruch":
|
if gestoert == "abbruch":
|
||||||
@@ -168,6 +187,7 @@ def lauf(art, *argumente, modul=None):
|
|||||||
modul.SICHERUNG_ZIEL = wurzel / "sicherung-ziel"
|
modul.SICHERUNG_ZIEL = wurzel / "sicherung-ziel"
|
||||||
os.environ.pop("ANGRIFF_HOST", None)
|
os.environ.pop("ANGRIFF_HOST", None)
|
||||||
os.environ.pop("ANGRIFF_SICHERUNG", None)
|
os.environ.pop("ANGRIFF_SICHERUNG", None)
|
||||||
|
os.environ.pop("ANGRIFF_HOST_WEG", None)
|
||||||
unerwartet = []
|
unerwartet = []
|
||||||
if not getattr(modul.fahre_wsl, "attrappe", False):
|
if not getattr(modul.fahre_wsl, "attrappe", False):
|
||||||
|
|
||||||
@@ -410,7 +430,7 @@ def namen(ordner):
|
|||||||
return sorted(p.name for p in ordner.iterdir())
|
return sorted(p.name for p in ordner.iterdir())
|
||||||
|
|
||||||
|
|
||||||
def sicherungslauf(wurzel, *argumente, modul=None, stoerung="", erster_tag=date(2026, 10, 5), schalter=True):
|
def sicherungslauf(wurzel, *argumente, modul=None, stoerung="", erster_tag=date(2026, 10, 5), schalter=True, host_weg=""):
|
||||||
"""Ein Lauf von main() mit Quelle dockerhost und Sicherung, alles unter wurzel.
|
"""Ein Lauf von main() mit Quelle dockerhost und Sicherung, alles unter wurzel.
|
||||||
|
|
||||||
Vault, Host-Ablage (host_ablage) und Ziel bleiben stehen, damit ein zweiter Lauf daran
|
Vault, Host-Ablage (host_ablage) und Ziel bleiben stehen, damit ein zweiter Lauf daran
|
||||||
@@ -446,6 +466,8 @@ def sicherungslauf(wurzel, *argumente, modul=None, stoerung="", erster_tag=date(
|
|||||||
os.environ["ANGRIFF_ART"] = "gut"
|
os.environ["ANGRIFF_ART"] = "gut"
|
||||||
os.environ["ANGRIFF_HOST"] = str(wurzel / "host")
|
os.environ["ANGRIFF_HOST"] = str(wurzel / "host")
|
||||||
os.environ["ANGRIFF_SICHERUNG"] = stoerung
|
os.environ["ANGRIFF_SICHERUNG"] = stoerung
|
||||||
|
# Seit Paket NT: "" heisst, der Host bleibt erreichbar.
|
||||||
|
os.environ["ANGRIFF_HOST_WEG"] = host_weg
|
||||||
vorne = ["--vault", str(vault), "--quelle", "dockerhost"]
|
vorne = ["--vault", str(vault), "--quelle", "dockerhost"]
|
||||||
if schalter:
|
if schalter:
|
||||||
vorne += ["--sicherung-ziel", str(wurzel / "ziel")]
|
vorne += ["--sicherung-ziel", str(wurzel / "ziel")]
|
||||||
@@ -529,6 +551,9 @@ def eine_falsche_summe_wird_nie_abgelegt_und_nie_bestaetigt():
|
|||||||
|
|
||||||
@fall
|
@fall
|
||||||
def eine_abgebrochene_uebertragung_hinterlaesst_nichts():
|
def eine_abgebrochene_uebertragung_hinterlaesst_nichts():
|
||||||
|
# Seit Paket NT (Plankritik, Befund 1): Exit 255 mitten im Archiv heisst nicht, dass der Host fehlt;
|
||||||
|
# er hat die Summe gerade beantwortet. Nur die kaputte Instanz scheitert, mit dem Befehl im Grund,
|
||||||
|
# die andere wird geholt. Keine Nachbardatei bleibt, die kaputte Instanz ist nicht bestaetigt.
|
||||||
for kaputt, heil in (("alpaca", "ibkr"), ("ibkr", "alpaca")):
|
for kaputt, heil in (("alpaca", "ibkr"), ("ibkr", "alpaca")):
|
||||||
with tempfile.TemporaryDirectory() as ordner:
|
with tempfile.TemporaryDirectory() as ordner:
|
||||||
wurzel = Path(ordner)
|
wurzel = Path(ordner)
|
||||||
@@ -541,8 +566,13 @@ def eine_abgebrochene_uebertragung_hinterlaesst_nichts():
|
|||||||
assert namen(wurzel / "ziel" / TAG) == [f"{heil}.tar", f"{heil}.tar.sha256"], namen(wurzel / "ziel" / TAG)
|
assert namen(wurzel / "ziel" / TAG) == [f"{heil}.tar", f"{heil}.tar.sha256"], namen(wurzel / "ziel" / TAG)
|
||||||
assert namen(host / "bestaetigt") == [f"{TAG}-{heil}"], namen(host / "bestaetigt")
|
assert namen(host / "bestaetigt") == [f"{TAG}-{heil}"], namen(host / "bestaetigt")
|
||||||
assert not any(b.startswith(f"sicherung-bestaetigt {TAG}-{kaputt} ") for b in befehle), befehle
|
assert not any(b.startswith(f"sicherung-bestaetigt {TAG}-{kaputt} ") for b in befehle), befehle
|
||||||
assert f"{kaputt} gescheitert (RuntimeError: Archiv nicht geholt (Exit 255;" in zeile, zeile
|
assert (
|
||||||
assert "#needs-review" in zeile, zeile
|
f"{kaputt} gescheitert (RuntimeError: Archiv nicht geholt (sicherung {TAG}-{kaputt}: Exit 255; "
|
||||||
|
"Connection reset by peer))"
|
||||||
|
) in zeile, zeile
|
||||||
|
assert f"{heil} abgelegt und bestätigt" in zeile, zeile
|
||||||
|
assert "**gescheitert** #needs-review" in zeile, zeile
|
||||||
|
assert "Docker-Host nicht erreichbar" not in zeile, zeile
|
||||||
|
|
||||||
|
|
||||||
@fall
|
@fall
|
||||||
@@ -929,16 +959,23 @@ def ohne_schalter_gilt_das_ziel_der_konstante():
|
|||||||
|
|
||||||
|
|
||||||
@fall
|
@fall
|
||||||
def ein_tag_ausser_der_form_holt_keine_sicherung():
|
def ein_tag_ausser_der_form_endet_mit_zwei_ohne_notiz_und_ohne_ssh():
|
||||||
for tag in ("2026-10-5", "2026-13-05", "20261005", "../2026-10-05"):
|
# Seit Paket NT (Quorum 4): main prueft --tag gleich nach dem Zerlegen. Vorher lief die
|
||||||
|
# Abendkontrolle mit dem falschen Tag los und legte Inbox/<tag>.md an, mit ../ auch ausserhalb von
|
||||||
|
# Inbox/; seit E9 hielt nur die Sicherung an (Exit 1). Jetzt endet der ganze Lauf vorher mit Exit 2.
|
||||||
|
for tag in ("2026-10-5", "2026-13-05", "2026-02-30", "20261005", "x026-10-05", "2026-10-0x", "../2026-10-05", ""):
|
||||||
with tempfile.TemporaryDirectory() as ordner:
|
with tempfile.TemporaryDirectory() as ordner:
|
||||||
wurzel = Path(ordner)
|
wurzel = Path(ordner)
|
||||||
host_ablage(wurzel)
|
host_ablage(wurzel)
|
||||||
code, notiz, befehle, _ = sicherungslauf(wurzel, "--tag", tag, "--jetzt", "2026-10-05T23:30")
|
fehler = io.StringIO()
|
||||||
assert code == 1, (tag, code)
|
with contextlib.redirect_stderr(fehler):
|
||||||
assert befehle == [], (tag, befehle)
|
code, notiz, _, _ = sicherungslauf(wurzel, "--tag", tag, "--jetzt", "2026-10-05T23:30")
|
||||||
|
assert code == 2, (tag, code)
|
||||||
|
assert fehler.getvalue() == f"abendkontrolle: --tag {tag!a} ist kein Datum JJJJ-MM-TT\n", (tag, fehler.getvalue())
|
||||||
|
assert namen(wurzel / "vault") == ["Inbox"], (tag, namen(wurzel / "vault"))
|
||||||
|
assert namen(wurzel / "vault" / "Inbox") == [], (tag, notiz)
|
||||||
|
assert not (wurzel / "ssh.jsonl").exists(), tag
|
||||||
assert not (wurzel / "ziel").exists(), tag
|
assert not (wurzel / "ziel").exists(), tag
|
||||||
assert sicherungszeilen(notiz) == [], (tag, notiz)
|
|
||||||
|
|
||||||
|
|
||||||
@fall
|
@fall
|
||||||
@@ -958,6 +995,176 @@ def der_abendausloeser_steht_in_ortszeit_ohne_versatz():
|
|||||||
assert ausloeser[0] < zeilen.index(zeile) < eintragen[0], (ausloeser, zeilen.index(zeile), eintragen)
|
assert ausloeser[0] < zeilen.index(zeile) < eintragen[0], (ausloeser, zeilen.index(zeile), eintragen)
|
||||||
|
|
||||||
|
|
||||||
|
# --- Seit Paket NT (Quorum 4): Host nicht erreichbar, --tag frueh, unlesbare Dateien --------------
|
||||||
|
|
||||||
|
HOST_WEG = "ssh: connect to host docker-host port 2498: Connection timed out"
|
||||||
|
|
||||||
|
|
||||||
|
def ergebnisse_liegen(modul, wurzel):
|
||||||
|
"""Die Ergebnisse beider Teile liegen fuer 2026-09-20 bis 2026-10-12: Der Lauf faehrt nur die Sicherung."""
|
||||||
|
ablage = wurzel / "vault" / modul.ABLAGE_TEIL
|
||||||
|
ablage.mkdir(parents=True, exist_ok=True)
|
||||||
|
for tag in [f"2026-09-{t}" for t in range(20, 31)] + [f"2026-10-{t:02d}" for t in range(1, 13)]:
|
||||||
|
(ablage / f"{tag}.json").write_text("{}", encoding="utf-8")
|
||||||
|
(ablage / f"{tag}-ibkr.json").write_text("{}", encoding="utf-8")
|
||||||
|
return ablage
|
||||||
|
|
||||||
|
|
||||||
|
@fall
|
||||||
|
def ein_unlesbares_archiv_auf_dem_host_scheitert_nur_fuer_seine_instanz():
|
||||||
|
# Paket NT (Quorum 4, G2): abendkontrolle-lesen.sh endet bei einem Archiv ohne Leserecht mit Exit 1
|
||||||
|
# und eigener Zeile. Das ist kein Ausfall des Hosts: nur diese Instanz scheitert, die andere wird
|
||||||
|
# geholt. Seit ssh_lauf Exit 255 als HostNichtErreichbar meldet, erreicht ein Abbruch mitten im
|
||||||
|
# Archiv die Pruefung des Exit-Codes nicht mehr; den Mutanten "Exit des Archivs egal" traegt
|
||||||
|
# deshalb dieser Fall (vorher eine_abgebrochene_uebertragung_hinterlaesst_nichts).
|
||||||
|
for kaputt, heil in (("alpaca", "ibkr"), ("ibkr", "alpaca")):
|
||||||
|
with tempfile.TemporaryDirectory() as ordner:
|
||||||
|
wurzel = Path(ordner)
|
||||||
|
host = host_ablage(wurzel)
|
||||||
|
code, notiz, _, _ = sicherungslauf(
|
||||||
|
wurzel, "--tag", TAG, "--jetzt", "2026-10-05T23:30", stoerung=f"archiv_unlesbar:{kaputt}"
|
||||||
|
)
|
||||||
|
zeile = einzige_zeile(notiz)
|
||||||
|
assert code == 1, code
|
||||||
|
assert namen(wurzel / "ziel" / TAG) == [f"{heil}.tar", f"{heil}.tar.sha256"], namen(wurzel / "ziel" / TAG)
|
||||||
|
assert namen(host / "bestaetigt") == [f"{TAG}-{heil}"], namen(host / "bestaetigt")
|
||||||
|
assert (
|
||||||
|
f"{kaputt} gescheitert (RuntimeError: Archiv nicht geholt (Exit 1; abendkontrolle-lesen: "
|
||||||
|
f"{TAG}-{kaputt}.tar liegt auf dem Docker-Host, laesst sich aber nicht lesen)"
|
||||||
|
) in zeile, zeile
|
||||||
|
assert f"{heil} abgelegt und bestätigt" in zeile, zeile
|
||||||
|
assert "Docker-Host nicht erreichbar" not in zeile, zeile
|
||||||
|
|
||||||
|
|
||||||
|
@fall
|
||||||
|
def ein_unerreichbarer_host_gibt_eine_zeile_von_heute_und_keine_weitere_notiz():
|
||||||
|
# Paket NT (Quorum 4, G1): Vorher scheiterte bei einem Host ohne Verbindung jeder Tag des Fensters
|
||||||
|
# mit eigener Zeile, und schreibe_notiz legte die fehlenden Notizen an, auch fuer Samstag und
|
||||||
|
# Sonntag. Jetzt endet die Sicherung beim ersten Exit 255 mit genau einer Zeile von heute.
|
||||||
|
with tempfile.TemporaryDirectory() as ordner:
|
||||||
|
wurzel = Path(ordner)
|
||||||
|
modul = lade()
|
||||||
|
ablage = ergebnisse_liegen(modul, wurzel)
|
||||||
|
code, notiz, befehle, _ = sicherungslauf(
|
||||||
|
wurzel, "--jetzt", "2026-10-12T23:30", modul=modul, erster_tag=date(2026, 9, 1), host_weg="0"
|
||||||
|
)
|
||||||
|
assert code == 1, (code, notiz)
|
||||||
|
assert namen(wurzel / "vault" / "Inbox") == ["2026-10-12.md"], namen(wurzel / "vault" / "Inbox")
|
||||||
|
assert einzige_zeile(notiz) == (
|
||||||
|
f"- 23:30 Uhr — {KOPF}: **gescheitert** #needs-review. Docker-Host nicht erreichbar "
|
||||||
|
f"(sicherung-summe 2026-10-02-alpaca: Exit 255; {HOST_WEG}), Sicherung abgebrochen. "
|
||||||
|
"Der nächste Lauf versucht es erneut."
|
||||||
|
), notiz
|
||||||
|
assert befehle == ["sicherung-summe 2026-10-02-alpaca"], befehle
|
||||||
|
assert not (wurzel / "ziel").exists()
|
||||||
|
log = (ablage / "lauf.log").read_text(encoding="utf-8")
|
||||||
|
assert "sicherung 2026-10-02-alpaca: Docker-Host nicht erreichbar (" in log, log
|
||||||
|
|
||||||
|
|
||||||
|
@fall
|
||||||
|
def ein_host_der_mitten_im_lauf_wegfaellt_laesst_die_zeilen_davor_stehen():
|
||||||
|
# Paket NT (Quorum 4, G1): Fertige Tage behalten ihre Zeilen in ihren Notizen; was ein angefangener
|
||||||
|
# Tag schon hat, nennt die eine Zeile von heute. Je Tag mit Sicherung sechs Befehle (Summe, Archiv,
|
||||||
|
# Bestaetigung je Instanz); nach weg Befehlen ist der Host weg. Am Samstag 2026-10-03 liegt keine
|
||||||
|
# Sicherung, gefragt wird trotzdem.
|
||||||
|
for weg, notizen, zuletzt, bis_dahin in (
|
||||||
|
("12", ["2026-10-01.md", "2026-10-02.md", "2026-10-05.md"], "sicherung-summe 2026-10-03-alpaca", ""),
|
||||||
|
("9", ["2026-10-01.md", "2026-10-05.md"], "sicherung-summe 2026-10-02-ibkr",
|
||||||
|
" Bis dahin am 2026-10-02: alpaca abgelegt und bestätigt ("),
|
||||||
|
):
|
||||||
|
with tempfile.TemporaryDirectory() as ordner:
|
||||||
|
wurzel = Path(ordner)
|
||||||
|
host_ablage(wurzel, tage=("2026-10-01", "2026-10-02", "2026-10-05"))
|
||||||
|
modul = lade()
|
||||||
|
ergebnisse_liegen(modul, wurzel)
|
||||||
|
code, notiz, befehle, _ = sicherungslauf(
|
||||||
|
wurzel, "--jetzt", "2026-10-05T23:30", modul=modul, erster_tag=date(2026, 10, 1), host_weg=weg
|
||||||
|
)
|
||||||
|
zeilen = sicherungszeilen(notiz)
|
||||||
|
assert code == 1, (weg, code)
|
||||||
|
assert namen(wurzel / "vault" / "Inbox") == notizen, (weg, namen(wurzel / "vault" / "Inbox"))
|
||||||
|
assert befehle[-1] == zuletzt and len(befehle) == int(weg) + 1, (weg, befehle)
|
||||||
|
assert namen(wurzel / "ziel") == ["2026-10-01", "2026-10-02"], (weg, namen(wurzel / "ziel"))
|
||||||
|
assert len(zeilen) == len(notizen), (weg, zeilen)
|
||||||
|
for zeile in zeilen[:-1]:
|
||||||
|
assert "alpaca abgelegt und bestätigt" in zeile and "ibkr abgelegt und bestätigt" in zeile, zeile
|
||||||
|
assert zeilen[-1].startswith(
|
||||||
|
f"- 23:30 Uhr — {KOPF}: **gescheitert** #needs-review. Docker-Host nicht erreichbar "
|
||||||
|
f"({zuletzt}: Exit 255; {HOST_WEG}), Sicherung abgebrochen."
|
||||||
|
), zeilen[-1]
|
||||||
|
if bis_dahin:
|
||||||
|
assert bis_dahin in zeilen[-1], zeilen[-1]
|
||||||
|
else:
|
||||||
|
assert "Bis dahin" not in zeilen[-1], zeilen[-1]
|
||||||
|
assert zeilen[-1].endswith(" Der nächste Lauf versucht es erneut."), zeilen[-1]
|
||||||
|
|
||||||
|
|
||||||
|
@fall
|
||||||
|
def ein_zeitlimit_beim_host_zaehlt_wie_ein_unerreichbarer_host():
|
||||||
|
# Paket NT (Quorum 4, G1): Laeuft die Frage nach der Summe in ihr Zeitlimit (SSH_TIMEOUT_S), gilt der
|
||||||
|
# Host als nicht erreichbar. In der Kopie ist das Zeitlimit 1 s, die Attrappe braucht je
|
||||||
|
# Sicherungsbefehl 5 s. Das Zeitlimit beim Archiv selbst: naechster Fall.
|
||||||
|
with tempfile.TemporaryDirectory() as ordner:
|
||||||
|
wurzel = Path(ordner)
|
||||||
|
host_ablage(wurzel)
|
||||||
|
modul = lade()
|
||||||
|
ergebnisse_liegen(modul, wurzel)
|
||||||
|
modul.SSH_TIMEOUT_S = 1
|
||||||
|
code, notiz, befehle, _ = sicherungslauf(wurzel, "--jetzt", "2026-10-05T23:30", modul=modul, stoerung="langsam")
|
||||||
|
assert code == 1, (code, notiz)
|
||||||
|
assert namen(wurzel / "vault" / "Inbox") == ["2026-10-05.md"], namen(wurzel / "vault" / "Inbox")
|
||||||
|
assert befehle == ["sicherung-summe 2026-10-05-alpaca"], befehle
|
||||||
|
assert einzige_zeile(notiz) == (
|
||||||
|
f"- 23:30 Uhr — {KOPF}: **gescheitert** #needs-review. Docker-Host nicht erreichbar "
|
||||||
|
"(sicherung-summe 2026-10-05-alpaca: Zeitlimit 1 s abgelaufen), Sicherung abgebrochen. "
|
||||||
|
"Der nächste Lauf versucht es erneut."
|
||||||
|
), notiz
|
||||||
|
assert not (wurzel / "ziel").exists()
|
||||||
|
|
||||||
|
|
||||||
|
@fall
|
||||||
|
def ein_zeitlimit_beim_archiv_laesst_nur_diese_instanz_scheitern():
|
||||||
|
# Plankritik NT, Befund 1: Laeuft nur das Archiv in sein Zeitlimit (SICHERUNG_TIMEOUT_S), ist der Host
|
||||||
|
# erreichbar; er hat die Summe gerade beantwortet. Die Instanz scheitert mit eigenem Grund, die
|
||||||
|
# andere wird geholt, der naechste Tag auch. In der Kopie ist das Zeitlimit des Archivs 1 s, die
|
||||||
|
# Attrappe braucht fuer jeden Befehl von alpaca 5 s; die Summe hat weiter SSH_TIMEOUT_S.
|
||||||
|
tage = ("2026-10-01", "2026-10-02")
|
||||||
|
with tempfile.TemporaryDirectory() as ordner:
|
||||||
|
wurzel = Path(ordner)
|
||||||
|
host = host_ablage(wurzel, tage=tage)
|
||||||
|
modul = lade()
|
||||||
|
ergebnisse_liegen(modul, wurzel)
|
||||||
|
modul.SICHERUNG_TIMEOUT_S = 1
|
||||||
|
code, notiz, befehle, _ = sicherungslauf(
|
||||||
|
wurzel, "--jetzt", "2026-10-02T23:30", modul=modul, erster_tag=date(2026, 10, 1), stoerung="langsam:alpaca"
|
||||||
|
)
|
||||||
|
zeilen = sicherungszeilen(notiz)
|
||||||
|
assert code == 1, (code, notiz)
|
||||||
|
assert namen(wurzel / "vault" / "Inbox") == ["2026-10-01.md", "2026-10-02.md"], namen(wurzel / "vault" / "Inbox")
|
||||||
|
assert namen(host / "bestaetigt") == [f"{t}-ibkr" for t in tage], namen(host / "bestaetigt")
|
||||||
|
assert [b for b in befehle if b.startswith("sicherung ")] == [
|
||||||
|
f"sicherung {t}-{i}" for t in tage for i in ("alpaca", "ibkr")
|
||||||
|
], befehle
|
||||||
|
assert len(zeilen) == 2, zeilen
|
||||||
|
for tag, zeile in zip(tage, zeilen):
|
||||||
|
assert namen(wurzel / "ziel" / tag) == ["ibkr.tar", "ibkr.tar.sha256"], (tag, namen(wurzel / "ziel" / tag))
|
||||||
|
assert (
|
||||||
|
"**gescheitert** #needs-review. alpaca gescheitert (RuntimeError: Archiv nicht geholt "
|
||||||
|
f"(sicherung {tag}-alpaca: Zeitlimit 1 s abgelaufen)); ibkr abgelegt und bestätigt ("
|
||||||
|
) in zeile, zeile
|
||||||
|
assert "Docker-Host nicht erreichbar" not in zeile, zeile
|
||||||
|
|
||||||
|
|
||||||
|
@fall
|
||||||
|
def kein_kommentar_beginnt_mit_s3():
|
||||||
|
# Paket NT (Quorum 4): Die Kommentarzeile ueber SICHERUNG_NACHHOLEN_TAGE begann mit "(S3).", einem
|
||||||
|
# Verweis ohne Ziel. Erst die neue Zeile zusichern, dann die alte ausschliessen.
|
||||||
|
zeilen = QUELLE.read_text(encoding="utf-8").splitlines()
|
||||||
|
neu = "# Die Sicherung entsteht 16:45 New Yorker Zeit, in Berlin meist 22:45, vom 25.10. bis 31.10.2026"
|
||||||
|
assert zeilen.count(neu) == 1, f"neue Kommentarzeile {zeilen.count(neu)}-mal"
|
||||||
|
assert [z for z in zeilen if z.startswith("# (S3)")] == [], "Kommentar beginnt mit (S3)."
|
||||||
|
|
||||||
|
|
||||||
# Mutanten fuer --mutanten: Name, Text in abendkontrolle.py (genau einmal), Ersatz und die Faelle,
|
# Mutanten fuer --mutanten: Name, Text in abendkontrolle.py (genau einmal), Ersatz und die Faelle,
|
||||||
# die davon rot werden muessen. MUTANTEN_AUFGABE (unten) tut dasselbe fuer abendkontrolle_einrichten.ps1.
|
# die davon rot werden muessen. MUTANTEN_AUFGABE (unten) tut dasselbe fuer abendkontrolle_einrichten.ps1.
|
||||||
# Ein Mutant, der das Verhalten nicht aendert, gehoert nicht hierher
|
# Ein Mutant, der das Verhalten nicht aendert, gehoert nicht hierher
|
||||||
@@ -968,7 +1175,7 @@ MUTANTEN = [
|
|||||||
("Exit des Archivs egal",
|
("Exit des Archivs egal",
|
||||||
' if lauf.returncode != 0:\n raise RuntimeError(f"Archiv nicht geholt',
|
' if lauf.returncode != 0:\n raise RuntimeError(f"Archiv nicht geholt',
|
||||||
' if False:\n raise RuntimeError(f"Archiv nicht geholt',
|
' if False:\n raise RuntimeError(f"Archiv nicht geholt',
|
||||||
["eine_abgebrochene_uebertragung_hinterlaesst_nichts"]),
|
["ein_unlesbares_archiv_auf_dem_host_scheitert_nur_fuer_seine_instanz"]),
|
||||||
("Nachbardatei bleibt", " teil.unlink(missing_ok=True)", " pass",
|
("Nachbardatei bleibt", " teil.unlink(missing_ok=True)", " pass",
|
||||||
["eine_falsche_summe_wird_nie_abgelegt_und_nie_bestaetigt", "eine_abgebrochene_uebertragung_hinterlaesst_nichts"]),
|
["eine_falsche_summe_wird_nie_abgelegt_und_nie_bestaetigt", "eine_abgebrochene_uebertragung_hinterlaesst_nichts"]),
|
||||||
("Bestaetigung ungeprueft",
|
("Bestaetigung ungeprueft",
|
||||||
@@ -1032,11 +1239,62 @@ MUTANTEN = [
|
|||||||
["ohne_schalter_gilt_das_ziel_der_konstante"]),
|
["ohne_schalter_gilt_das_ziel_der_konstante"]),
|
||||||
("Ziel der Konstante", r'SICHERUNG_ZIEL = Path(r"D:\Trading-Sicherung")',
|
("Ziel der Konstante", r'SICHERUNG_ZIEL = Path(r"D:\Trading-Sicherung")',
|
||||||
r'SICHERUNG_ZIEL = Path(r"D:\Trading-Sicherung-alt")', ["ohne_schalter_gilt_das_ziel_der_konstante"]),
|
r'SICHERUNG_ZIEL = Path(r"D:\Trading-Sicherung-alt")', ["ohne_schalter_gilt_das_ziel_der_konstante"]),
|
||||||
("Tag ungeprueft",
|
# Seit Paket NT (Quorum 4): Host nicht erreichbar, --tag frueh geprueft, Kommentar ohne (S3).
|
||||||
' if not TAGFORM.fullmatch(tag):\n raise ValueError(f"--tag {tag!r} ist kein Datum JJJJ-MM-TT")\n'
|
("E729-01 Host weg: Exit 255 wie jeder andere", " if lauf.returncode == 255:", " if False:",
|
||||||
" date.fromisoformat(tag)",
|
["ein_unerreichbarer_host_gibt_eine_zeile_von_heute_und_keine_weitere_notiz",
|
||||||
" pass", ["ein_tag_ausser_der_form_holt_keine_sicherung"]),
|
"eine_abgebrochene_uebertragung_hinterlaesst_nichts"]),
|
||||||
("Datum ungeprueft", " date.fromisoformat(tag)", " pass", ["ein_tag_ausser_der_form_holt_keine_sicherung"]),
|
("E729-02 Host weg: Zeitlimit wie jeder Fehler", " except subprocess.TimeoutExpired:",
|
||||||
|
" except ZeroDivisionError:", ["ein_zeitlimit_beim_host_zaehlt_wie_ein_unerreichbarer_host"]),
|
||||||
|
("E729-03 Host weg reisst nur die Instanz ab", " except HostNichtErreichbar as ausnahme:",
|
||||||
|
" except ZeroDivisionError as ausnahme:",
|
||||||
|
["ein_unerreichbarer_host_gibt_eine_zeile_von_heute_und_keine_weitere_notiz",
|
||||||
|
"ein_host_der_mitten_im_lauf_wegfaellt_laesst_die_zeilen_davor_stehen"]),
|
||||||
|
("E729-04 Host weg: Zeile in der Notiz des Tags",
|
||||||
|
" schreibe_notiz(vault, jetzt.date().isoformat(), zeile)",
|
||||||
|
" schreibe_notiz(vault, kandidat, zeile)",
|
||||||
|
["ein_unerreichbarer_host_gibt_eine_zeile_von_heute_und_keine_weitere_notiz"]),
|
||||||
|
("E729-05 Host weg mit Exit 0",
|
||||||
|
' logge(ablage, f"sicherung: Notiz nicht geschrieben {zweite!r}")\n return 1',
|
||||||
|
' logge(ablage, f"sicherung: Notiz nicht geschrieben {zweite!r}")\n return 0',
|
||||||
|
["ein_unerreichbarer_host_gibt_eine_zeile_von_heute_und_keine_weitere_notiz"]),
|
||||||
|
("E729-06 Host weg: weiter mit der naechsten Instanz",
|
||||||
|
' logge(ablage, f"sicherung: Notiz nicht geschrieben {zweite!r}")\n return 1',
|
||||||
|
' logge(ablage, f"sicherung: Notiz nicht geschrieben {zweite!r}")\n continue',
|
||||||
|
["ein_unerreichbarer_host_gibt_eine_zeile_von_heute_und_keine_weitere_notiz"]),
|
||||||
|
("E729-07 Host weg: nichts vom angefangenen Tag", " if stuecke:\n zeile +=",
|
||||||
|
" if False:\n zeile +=",
|
||||||
|
["ein_host_der_mitten_im_lauf_wegfaellt_laesst_die_zeilen_davor_stehen"]),
|
||||||
|
("E729-08 Host weg ohne lauf.log",
|
||||||
|
' logge(ablage, f"sicherung {kandidat}-{instanz}: Docker-Host nicht erreichbar ({ausnahme}), '
|
||||||
|
'Sicherung abgebrochen")',
|
||||||
|
" pass", ["ein_unerreichbarer_host_gibt_eine_zeile_von_heute_und_keine_weitere_notiz"]),
|
||||||
|
("E729-09 Tag spaet geprueft", " if argumente.tag is not None and not tag_gueltig(argumente.tag):",
|
||||||
|
" if False:", ["ein_tag_ausser_der_form_endet_mit_zwei_ohne_notiz_und_ohne_ssh"]),
|
||||||
|
("E729-10 leerer Tag gilt als keiner", " if argumente.tag is not None and not tag_gueltig(argumente.tag):",
|
||||||
|
" if argumente.tag and not tag_gueltig(argumente.tag):",
|
||||||
|
["ein_tag_ausser_der_form_endet_mit_zwei_ohne_notiz_und_ohne_ssh"]),
|
||||||
|
("E729-11 falscher Tag mit Exit 1",
|
||||||
|
' print(f"abendkontrolle: --tag {argumente.tag!a} ist kein Datum JJJJ-MM-TT", file=sys.stderr)\n'
|
||||||
|
" return 2",
|
||||||
|
' print(f"abendkontrolle: --tag {argumente.tag!a} ist kein Datum JJJJ-MM-TT", file=sys.stderr)\n'
|
||||||
|
" return 1",
|
||||||
|
["ein_tag_ausser_der_form_endet_mit_zwei_ohne_notiz_und_ohne_ssh"]),
|
||||||
|
("E729-12 falscher Tag ohne Meldung",
|
||||||
|
' print(f"abendkontrolle: --tag {argumente.tag!a} ist kein Datum JJJJ-MM-TT", file=sys.stderr)',
|
||||||
|
" pass", ["ein_tag_ausser_der_form_endet_mit_zwei_ohne_notiz_und_ohne_ssh"]),
|
||||||
|
("E729-13 Form des Tags ungeprueft", " if not TAGFORM.fullmatch(tag):\n return False",
|
||||||
|
" if False:\n return False", ["ein_tag_ausser_der_form_endet_mit_zwei_ohne_notiz_und_ohne_ssh"]),
|
||||||
|
("E729-14 Datum des Tags ungeprueft", " date.fromisoformat(tag)\n except ValueError:",
|
||||||
|
" pass\n except ValueError:", ["ein_tag_ausser_der_form_endet_mit_zwei_ohne_notiz_und_ohne_ssh"]),
|
||||||
|
("E729-15 Verweis S3 zurueck",
|
||||||
|
"# Die Sicherung entsteht 16:45 New Yorker Zeit, in Berlin meist 22:45, vom 25.10. bis 31.10.2026",
|
||||||
|
"# (S3). Die Sicherung entsteht 16:45 New Yorker Zeit, in Berlin meist 22:45, vom 25.10. bis 31.10.2026",
|
||||||
|
["kein_kommentar_beginnt_mit_s3"]),
|
||||||
|
# Seit der Plankritik NT, Befund 1: Ein Abbruch im Archiv laesst nur die Instanz scheitern.
|
||||||
|
("E729-25 Abbruch im Archiv beendet die Sicherung", " except HostNichtErreichbar as abbruch:",
|
||||||
|
" except ZeroDivisionError as abbruch:",
|
||||||
|
["ein_zeitlimit_beim_archiv_laesst_nur_diese_instanz_scheitern",
|
||||||
|
"eine_abgebrochene_uebertragung_hinterlaesst_nichts"]),
|
||||||
("Name der Summendatei beim Host egal", ' if treffer is None or treffer.group(2) != f"{name}.tar":',
|
("Name der Summendatei beim Host egal", ' if treffer is None or treffer.group(2) != f"{name}.tar":',
|
||||||
" if treffer is None:", ["eine_summendatei_des_hosts_in_falscher_form_holt_kein_archiv"]),
|
" if treffer is None:", ["eine_summendatei_des_hosts_in_falscher_form_holt_kein_archiv"]),
|
||||||
("Grossbuchstaben in der Summe", r'SUMMENZEILE = re.compile(r"([0-9a-f]{64}) (\S+)\n")',
|
("Grossbuchstaben in der Summe", r'SUMMENZEILE = re.compile(r"([0-9a-f]{64}) (\S+)\n")',
|
||||||
|
|||||||
@@ -87,7 +87,8 @@ Container /abend/sicherung.json); die Ablage selbst sieht das Werkzeug nicht. Di
|
|||||||
nach dem Teil in einem eigenen Schritt, auch an einem Tag ohne Handel und nach einem Abbruch.
|
nach dem Teil in einem eigenen Schritt, auch an einem Tag ohne Handel und nach einem Abbruch.
|
||||||
E1. Ist der Stand lesbar? Projektvorgabe (Plan E9, Task 726): Datei fehlt, kein JSON, kein
|
E1. Ist der Stand lesbar? Projektvorgabe (Plan E9, Task 726): Datei fehlt, kein JSON, kein
|
||||||
Objekt, ohne erster_tag oder eintraege -> Befund. Ein Schluessel
|
Objekt, ohne erster_tag oder eintraege -> Befund. Ein Schluessel
|
||||||
fehler (Ablage fehlt, nicht lesbar, erster-tag kaputt) -> Befund.
|
fehler (Ablage fehlt, nicht lesbar, erster-tag kaputt, seit Paket NT
|
||||||
|
auch eine unlesbare Summendatei oder Bestaetigung) -> Befund.
|
||||||
E2. Ab wann wird geprueft? Projektvorgabe (S6, S8): erster_tag aus erster-tag der Ablage;
|
E2. Ab wann wird geprueft? Projektvorgabe (S6, S8): erster_tag aus erster-tag der Ablage;
|
||||||
null, kein Datum oder ein Tag nach --tag -> Befund, sonst
|
null, kein Datum oder ein Tag nach --tag -> Befund, sonst
|
||||||
schwiege die Pruefung auf Dauer; nur ein voriger Handelstag vor
|
schwiege die Pruefung auf Dauer; nur ein voriger Handelstag vor
|
||||||
|
|||||||
Reference in new issue
Block a user