| 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106 | """Hintergrunddienst: loescht die Karte, wenn ihre Zeit um ist.
Laeuft als kode-waechter.service, unabhaengig von jeder CLI-Sitzung - das
CLI darf abstuerzen, das Terminal darf zugehen, die Karte wird trotzdem
geloescht. Drei Faelle:
1. Bereit, aber Frist abgelaufen (Leerlauf bzw. "halten bis").
2. Start ohne Lebenszeichen: die startende Sitzung schreibt alle paar
Sekunden einen Puls; bleibt er PULS_FRIST aus, ist sie weg.
3. Waisen: Karten mit unserem Label, die kein Zustand kennt (Absturz
zwischen Mieten und Eintragen). Erst nach WAISEN_FRIST, damit ein
laufender Start nicht getroffen wird.
Ein unbestaetigtes Loeschen bleibt im Zustand stehen und wird bei jedem
Durchgang wieder versucht.
"""
import logging
import time
from . import anbieter, karte, konfig, protokoll
logging.basicConfig(
level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s",
handlers=[logging.StreamHandler(), logging.FileHandler(konfig.WAECHTER_LOG)]
if konfig.DATEN.exists() else [logging.StreamHandler()])
log = logging.getLogger("kode.waechter")
logging.getLogger("httpx").setLevel(logging.WARNING)
TAKT = 30
WAISEN_TAKT = 300
EXISTENZ_TAKT = 300
def _eigene_karten() -> list[tuple[str, str]] | None:
rp = anbieter.runpod_eigene()
va = anbieter.vast_eigene()
if rp is None or va is None:
return None
return ([("runpod", p["id"]) for p in rp]
+ [("vast", str(i["id"])) for i in va])
def durchgang(waisen: dict[tuple[str, str], float], uhr: dict[str, float]) -> None:
z = karte.lesen()
jetzt = time.time()
if z.get("kennung"):
if z["phase"] != "bereit":
if z.get("puls") and jetzt - z["puls"] > konfig.PULS_FRIST:
ok, text = karte.herunterfahren("Start ohne Lebenszeichen")
log.warning("%s", text)
elif (z.get("fehler") or "").startswith("Löschen nicht bestätigt"):
ok, text = karte.herunterfahren("erneuter Löschversuch")
log.warning("%s", text)
elif jetzt > (karte.frist(z) or jetzt + 1):
ok, text = karte.herunterfahren("Leerlauf")
log.info("%s", text)
elif jetzt - uhr.get("existenz", 0) > EXISTENZ_TAKT:
uhr["existenz"] = jetzt
if not anbieter.existiert(z["anbieter"], z["kennung"]):
log.warning("Karte %s gibt es nicht mehr (von Hand gelöscht?) - Zustand zurückgesetzt",
z["kennung"])
karte._lauf_beenden(z, "von außen gelöscht", True)
karte.aendern(**karte.LEER)
elif z["phase"] != "aus" and z.get("puls") and jetzt - z["puls"] > konfig.PULS_FRIST:
karte.aendern(**karte.LEER) # Start, der nie zu einer Karte kam
if jetzt - uhr.get("waisen", 0) < WAISEN_TAKT:
return
uhr["waisen"] = jetzt
liste = _eigene_karten()
if liste is None:
log.warning("Kartenliste nicht abrufbar - Waisensuche ausgesetzt")
return
bekannt = (z.get("anbieter"), z.get("kennung"))
for k in list(waisen):
if k not in liste:
del waisen[k]
for k in liste:
if k == bekannt:
continue
erst = waisen.setdefault(k, jetzt)
if jetzt - erst > konfig.WAISEN_FRIST:
ok = anbieter.loeschen(*k)
log.warning("Waise %s:%s %s", k[0], k[1], "gelöscht" if ok else "NICHT gelöscht")
protokoll.ereignis("waechter_waise", anbieter=k[0], kennung=k[1], geloescht=ok)
if ok:
del waisen[k]
def main() -> None:
konfig.verzeichnisse()
log.info("Wächter läuft (Label %s, Takt %d s)", konfig.LABEL, TAKT)
waisen: dict[tuple[str, str], float] = {}
uhr: dict[str, float] = {}
while True:
try:
durchgang(waisen, uhr)
except Exception as f: # noqa: BLE001
log.exception("Durchgang gescheitert")
protokoll.fehler("waechter", f)
time.sleep(TAKT)
if __name__ == "__main__":
main() |