13 Commits (993b529584abd80d375566ae8ec42867ab35fe90)
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
19a770e1eb |
Kuendigung B1, Fix-Welle: der Platz wird frei, und das Loeschen ist vollzogen
K1 — abgebaute Instanzen zaehlten weiter als belegend. `scopeOccupyingHost` filtert jetzt `torn_down_at`: eine abgebaute Instanz behaelt `ended`, `vmid` und `disk_gb` als Nachweis, `status != 'failed'` war fuer sie also wahr. Der Knoten wurde physisch leer und die Buchhaltung blieb voll — die naechste bezahlte Bestellung derselben Groesse waere geparkt und verworfen worden. K2 — `deleteVm()` gibt die UPID zurueck, der Abbau wartet den qmdestroy-Auftrag ab und schreibt `torn_down_at` erst danach. Ein 200 heisst nur, dass Proxmox den Auftrag angenommen hat; scheitert die Zerstoerung danach, stand bisher eine laufende Maschine mit einem Datensatz da, der "abgebaut" sagte — und niemand sah sie je wieder an. Die Attrappe bildet die Asynchronitaet ab (`destroyedVmids`, `destroyExitStatus`, `destroyHangs`). Die Fristen des Auftrags wandern mit: Sichern 900 s, Zerstoeren 300 s, Summe unveraendert 1800 s unter der Uhr des Arbeiters. K3 — vor `shutdownVm()` steht derselbe `vmStatus()`-Riegel wie im Nachbarschritt. Eine wegen offener Zahlung gesperrte Cloud und jeder Wiederholungslauf nach einem Teilfehlschlag treffen einen gestoppten Gast; der Wurf haette die Instanz unheilbar gemacht und den echten Grund am Datensatz ueberschrieben. Die Attrappe weist eine Bitte gegen eine stehende Maschine jetzt ab, und ein Test faehrt erstmals einen zweiten Lauf nach einem Fehlschlag durch. W2 — der rote Kasten "Abbau haengt" filtert `status = 'ended'`. Eine wiederbelebte Instanz waere sonst fuer immer darin stehengeblieben. Neun Pruefungen im Mahnwesen lassen ihre Cloud jetzt laufen, bevor sie gesperrt wird — die geschaerfte Attrappe legt offen, dass `SuspendInstance` denselben fehlenden Riegel hat (Folgepunkt im Bericht). Zu jedem der vier Punkte eine Zusicherung, die ohne den Fix rot ist; die Rotproben stehen im Bericht. |
|
|
|
df73e558e9 |
Kuendigung B1, Tasks 4+5: der Zeitplan-Griff und die Sichtbarkeit
Gekuendigte Kundenmaschinen liefen bisher fuer immer weiter. Task 3 hat den Abbau gebaut; hier kommen der Griff, der ihn faehrt, und der Ort, an dem man sieht, was passiert ist. Die Wartezeit-Frage, entschieden: ein Auftrag je Instanz auf der provisioning-Warteschlange. Das ist keine Abwaegung — nur queue-provisioning steht im Netz-Namensraum des vpn-hub, der scheduler-Container nicht. Ein Befehl, der selbst mit Proxmox spraeche, haette gar keine Route zu einem Host. Die Fristen stehen ausdruecklich ineinander: die Aktion bekommt 600+1200 = 1800 s, der Auftrag hat $timeout 2100 s, retry_after der Verbindung ist 2400 s. Nur die unterste Uhr hinterlaesst einen lesbaren Grund am Datensatz; die mittlere toetet den Arbeiterprozess stumm, die oberste startet einen ZWEITEN Abbau gegen eine Maschine mitten im vzdump. $tries=1, weil ein sofortiger zweiter Versuch am Herunterfahren einer gesperrten VM scheitern und den richtigen Grund ueberschreiben wuerde. Die Staffelung ist als Pruefung festgenagelt. Der Preis — zwanzig statt sechzig Minuten fuers Sichern — steht im Kopfkommentar ausgeschrieben. Dazu zwei Entscheidungen, nach denen niemand gefragt hat: eine Obergrenze je Lauf, weil ueber dieselbe serielle Warteschlange bezahlte Bestellungen laufen; und eine Reihenfolge, die einen Dauerfall die uebrigen nicht aushungern laesst. Zeitplan taeglich um 05:30 — der Abbau hat keinen Moment, auf den es ankommt, aber er darf nicht ins naechtliche vzdump-Fenster um 02:00 fallen. Sichtbarkeit: zwei Kaesten in der Konsole. „Abbau haengt" (rot, ganz oben) — eine Instanz mit gefuelltem teardown_error steht unbegrenzt und belegt weiter einen Platz. Und „Archiviert und abgebaut" (unter der Liste) mit archive_volid im Klartext. Beide sortieren absteigend und beide haben ein Ende: der Fehler raeumt sich beim naechsten erfolgreichen Lauf selbst ab, das Archiv faellt nach zwoelf Monaten heraus. Der Folgepunkt vom Export-Kasten also nicht noch einmal. Suite 2929 gruen. 25 neue Pruefungen, vier Mutationsproben rot gesehen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|
|
|
51ec6ebed2 |
Kuendigung B1, Task 3 — Fix-Runde: der Nachtrag braucht ein Lebenszeichen
Vier Befunde aus der Pruefung. Der schwerste: der Nachtrags-Zweig konnte eine LAUFENDE Kundenmaschine als abgebaut verbuchen. vmExists() ist ->get(...)->successful() ohne ->throw(), und Proxmox antwortet auf die Konfiguration einer nicht vorhandenen VM mit 500 — demselben Code wie ein hakender Knoten. "Gibt es nicht" und "ich konnte nicht fragen" sahen damit gleich aus, und der Zustand entsteht im Regelbetrieb: ein Lauf sichert, scheitert am Loeschen, und der naechste findet archive_volid gesetzt und einen Knoten, der nichts beantwortet. Die Ablagenpruefung steht deshalb jetzt VOR dem Zweig: nodeStorage() ruft ->throw(), laeuft sie durch, hat der Knoten geantwortet, und erst dann ist ein "nein" aus vmExists() ein Befund statt einer Vermutung. Drei Ausnahmen lagen ausserhalb des try und haetten im Zeitplan die uebrigen Instanzen mitgerissen: das Anlegen der Sperre (im Betrieb Redis), das Vermerken des Grundes im catch, und die Freigabe im finally — die sogar am Erfolgsfall vorbei. Alle drei abgesichert. Die Sperrfrist war mit backupWaitSeconds + 600 knapper als der laengste Lauf (Herunterfahren UND Sichern) und konnte kurz vor dem Loeschen auslaufen. Jetzt shutdownWaitSeconds + backupWaitSeconds + Puffer — und sie hat endlich eine eigene Pruefung. Ein einzelnes Archiv ohne Zeitpunkt bleibt gueltig, mehrere nicht: sie lassen sich nicht ordnen, und vermerkt wuerde womoeglich die Sicherung von vorgestern. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|
|
|
24a0f64128 |
Kuendigung B1, Task 3: archivieren, nachsehen, und erst dann abbauen
Gekuendigte Kundenmaschinen wurden nie abgebaut. EndInstanceService nimmt der Instanz am Laufzeitende die Adresse weg und laesst die Maschine ausdruecklich stehen; danach kam nichts mehr, und sie belegte fuer immer einen Platz auf dem Host. ArchiveAndTearDown ist der Schritt danach: vierzehn Tage nach dem Ende wird die Maschine archiviert und geloescht. Die eine Regel: niemals loeschen, bevor das Archiv nachweislich existiert. Ein vzdump kann mit einer Auftragskennung enden und trotzdem nichts hinterlassen — volle Ablage, abgebrochener Lauf, ein Fehler im Gast. Deshalb steht zwischen Sichern und Loeschen eine echte Nachschau auf der Ablage (backupsFor), und der juengste Eintrag muss nach dem Beginn dieses Laufes entstanden sein: eine naechtliche Sicherung von gestern ist kein Archiv, dem der letzte Tag fehlen darf. Beide Proxmox-Aufrufe liefern nur eine Kennung, keine Zusage, also wird auf den Auftrag gewartet und danach nachgesehen — beim Herunterfahren, ob der Gast wirklich steht, beim Sichern, ob die Datei liegt. Unmittelbar vor dem Loeschen wird der Datensatz neu gelesen: eine Instanz, die in der Zwischenzeit wiederbelebt wurde, wird nicht geloescht. Keine Ausnahme entkommt — ein Fehlschlag ist false und ein Grund in teardown_error, weil dieser Ablauf spaeter im Zeitplan ueber viele Instanzen laeuft. Die Ablage kommt aus einer Einstellung (provisioning.proxmox.archive_storage, Vorgabe local wie bei den naechtlichen Sicherungen) UND wird gegen die Ablagen des Knotens geprueft. Fehlt sie oder nimmt sie keine Sicherungen auf, bricht der Abbau ab, bevor eine Kundenmaschine dafuer heruntergefahren wurde. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|
|
|
91d3ce4064 |
Vier Spalten zur Verfolgung von Archivierung und Abbau von Instanzen
archive_volid: speichert den Ort des Archivs, ohne den es nach 12 Monaten nicht wiederzufinden ist. archived_at: speichert den Zeitpunkt der Archivierung, daraus berechnet sich die 12-Monats-Frist. torn_down_at: speichert den Zeitpunkt der Maschinenlöschung, getrennt von archived_at, damit der Zwischenstand sichtbar bleibt, wenn Archivierung erfolgreich war aber Löschung noch nicht versucht oder gescheitert ist. teardown_error: speichert die Fehlermeldung beim Löschen in Klartext, damit ein Betreiber ohne Log-Suche reagieren kann. Vier Tests zeigen, dass die Felder auf null stehen, in Carbon casten und dass der Zwischenstand (archiviert, nicht abgebaut) ein gültiger Zustand ist. Eine Mutation-Prüfung bestätigt, dass die Tests greifen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|
|
|
b3ddc44256 |
Zweite Fix-Welle: kein „beendet" ueber einem laufenden Aufbau
tests / pest (push) Has been cancelled
Details
tests / assets (push) Has been cancelled
Details
tests / release (push) Has been cancelled
Details
Zwei Nachwirkungen der ersten Fix-Welle (
|
|
|
|
c9fef59983 |
Fix-Welle: die Kuendigung sagt jedem nur das, was fuer ihn stimmt
Vier Befunde aus dem Gesamt-Review, und alle vier hatten dieselbe Wurzel:
`export_wish` fuehrt drei Zustaende, und jede Stelle, die den Kunden
ansprach, kannte nur zwei.
K1 — Der Streifen im Dashboard trug einen Schalter, und `(bool) null` ist
`false`. Wer vor dieser Ausrollung gekuendigt hat, las unter dem Streifen
„Kein Export gewuenscht" — als waere das seine eigene Antwort. Jetzt stehen
dort dieselben zwei Auswahlfelder wie im Kuendigungsdialog: gleiche Frage,
gleiche Form, und ein unbeantworteter Zustand markiert schlicht keines von
beiden. Der Satz daneben fragt dann, statt zu behaupten, und sagt, was
passiert, wenn die Frage offen bleibt.
K2 — Die Vertragsseite versprach jedem den Export, auch dem, den der Dialog
eine Sekunde vorher mit einem bewussten „Nein" genau dorthin umgeleitet
hatte. Drei Fassungen statt einer, an `export_wish` gebunden. Die Ja-Fassung
verspricht dabei nicht mehr den Export selbst, sondern dass der Wunsch
vermerkt ist und sich jemand meldet — den Export gibt es nicht, und ein
gebundenes, aber weiterhin unhaltbares Versprechen haette den Fehler nur
verschoben.
Dazu der Zustand danach: eine `ended`-Instanz holt Dashboard::render() nicht
mehr, und der Kunde fiel in denselben Zweig wie jemand, der noch nie etwas
bestellt hat — „Ihre Cloud wird eingerichtet." samt „Paket buchen", am Tag,
an dem ihm die Adresse eingezogen wurde. Der Fall hat jetzt seinen eigenen
Kasten, mit dem Datum, an dem das Paket endete.
W1 — Die Erinnerungsmail behauptete im Praesens, wir bereiteten bereits einen
Export vor. Der Satz sagt jetzt, was stimmt. Und die Antwort hatte in der
ganzen Konsole keinen einzigen Leser: ein „Ja" landete in einer Spalte, die
niemand je zu Gesicht bekam. Ueber der Instanzliste steht deshalb ein
Abschnitt „Datenexport bestellt" — wer, und bis wann. Nicht als Plakette in
der Zeile, weil die Liste geblaettert ist und ein alter Eintrag auf Seite acht
saesse; nicht auf der Uebersicht, weil ein Hinweis, den nichts je wieder
abraeumen kann, Moebel waere.
W3 — Die einzige Pruefung zur Anzeige der Antwort konnte nicht fehlschlagen:
`x-ui.switch` rendert beide Woerter und ueberlaesst dem CSS die Auswahl, also
war `assertSee('Kein Export gewuenscht')` bei true, bei false UND bei null
gruen. Nachgewiesen mit einer Wegwerf-Pruefung gegen den alten Streifen:
dreimal derselbe Satz, dreimal gruen. Jetzt drei Pruefungen, je eine pro
Zustand, am `checked`-Attribut der Auswahlfelder.
Zu jedem der vier Punkte wurde der Fix kurz zurueckgedreht und die neue
Zusicherung rot gesehen; die Ergebnisse stehen im Bericht.
Ganze Suite: 2819 gruen, 2 rot — beide fremd. ReadinessPageTest scheitert an
`server.private_key` aus der parallel laufenden Terminal-Arbeit im selben
Baum; HostStepTest ist auf main vorbestehend rot (install-agent.sh traegt
CONTRACT=3, update.sh HOST_STEP_NEEDS=2, beide unveraendert).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
|
|
|
cfd1481797 |
Ein Streifen im Dashboard sagt, wie lange der Zugang noch steht
Eine Mail sieben Tage vorher kann im Postfach untergehen; die eigene Uebersicht oeffnet der Kunde ohnehin. Der Streifen nennt das Datum, die Restzeit und was danach geschieht — dass der Zugang zur Cloud endet, nicht dass irgendetwas geloescht wird. Das ist die Frist, die ihn betrifft. Und er traegt die Antwort zum Export: wer liest, dass die Zeit laeuft, will es sich im selben Atemzug anders ueberlegen koennen (Aufgabe 4) — bis zum Laufzeitende, danach nicht mehr, geprueft direkt an der Methode und nicht nur am Schalter. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|
|
|
5eef03d267 |
Fix-Runde: Markierung im selben Versuch wie der Versand, zwei Randpruefungen
tests / pest (push) Has been cancelled
Details
tests / assets (push) Has been cancelled
Details
tests / release (push) Has been cancelled
Details
$instance->update(['export_reminded_at' => now()]) lag ausserhalb des try/catch, das nur den Mailversand absicherte. Ein Fehlschlag dieses einen Schreibzugriffs haette die Marke leer gelassen (morgen eine zweite Mail fuer dieselbe Instanz) UND den ganzen Lauf abgebrochen, statt nur diese eine Instanz zu ueberspringen. Jetzt beides in derselben Absicherung. Dazu zwei Pruefungen, die der Rand "Ende schon vorbei" bislang nicht hatte: keine Erinnerung mehr, wenn die Frist schon abgelaufen ist (kein akademischer Fall - EndInstanceService laesst einen DNS-Fehler bewusst durch, eine Instanz kann also real tagelang mit abgelaufener Frist stehenbleiben), und die obere Einschlussgrenze bei genau sieben Tagen, damit beide Raender der "hoechstens sieben Tage"-Regel durch je eine eigene Pruefung belegt sind. Ausserdem: die Selbst-Herunterladen-Adresse selbst wird jetzt im Rendertest geprueft, nicht nur Datum und Sprachschluessel. |
|
|
|
e83b1d886a |
Eine Warnung, bevor die eigene Cloud den Kunden aussperrt
Ab dem Laufzeitende zieht EndInstanceService die Adresse ein — der Kunde kommt
ab dem Moment nicht mehr an seine Nextcloud. Wer selbst etwas herunterladen
will, muss es VORHER tun, und das wusste bisher niemand.
Die Auswahl ist bewusst "hoechstens sieben Tage" und nicht "genau sieben Tage":
ein Gleichheitsvergleich verfehlt jede Instanz, die zwischen zwei Laeufen
durchrutscht, und der Preis dafuer waere, dass jemand ausgesperrt wird, ohne es
gewusst zu haben.
Zusaetzlich zum Zettel: lang/{de,en}/mail_pace.php bekommen einen Eintrag fuer
ServiceEndingSoonMail, weil MailPacePageTest fuer jede Klasse in MailLane::all()
einen Anzeigenamen verlangt — ohne ihn waere die Suite rot.
|
|
|
|
ae8c8680bb |
Fix-Runde: echte Umlaute, und das Versprechen sagt "auf Wunsch"
tests / pest (push) Has been cancelled
Details
tests / assets (push) Has been cancelled
Details
tests / release (push) Has been cancelled
Details
Befund 1: neuer deutscher Text (Kommentare, ein Testname) benutzte ae/oe/ue/ss statt ä/ö/ü/ß — der Bestand schreibt mit echten Umlauten, das Original wird hier nachgezogen. Betroffen: die drei Kommentare in ConfirmCancelPackage.php, der Blade-Kommentar, sowie Kopfkommentare und ein Testname in CancelAsksAboutExportTest.php. Der Funktionsname kuendbareInstanz() und die Test-Fixtures (E-Mail/Subdomain "kuendigt") bleiben ASCII — ersterer ist wörtlich aus dem Zettel übernommen, letztere sind technische Werte wie jede andere Test-Subdomain im Bestand (acme, berger). Befund 2 (Entscheidung des Betreibers): cancel_point_export versprach den Datenexport unbedingt, direkt über einer Frage, die ihn an ein Ja knüpft. "auf Wunsch" eingefügt, in beiden Sprachdateien — der Satz sagt jetzt, was der Dialog tatsächlich tut, ohne sonst etwas am Text zu ändern. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|
|
|
7d041db5e5 |
Beim Kuendigen wird gefragt, ob der Kunde seine Daten will
Der Angelpunkt des ganzen Vorhabens: wer keinen Export braucht, loest keine
Arbeit aus und wartet auf nichts. Zwei Auswahlfelder und keine Checkbox — eine
Checkbox kennt keinen dritten Zustand, und "nicht angekreuzt" waere von "nein"
nicht zu unterscheiden.
Die Frage ist nicht ueberspringbar (#[Validate('required|boolean')], vor Stripe
geprueft wie jede andere Vorbedingung hier). Das macht drei bestehende
Erfolgspfad-Pruefungen zu ConfirmCancelPackage neu pflichtig in einem Feld, das
sie vorher nicht kannten — PackageCancellationTest, SettingsTest und
EndInstanceServiceTest setzen deshalb jetzt zusaetzlich exportWish, ohne dass
sich an ihren eigentlichen Zusicherungen etwas aendert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
|
|
|
5dd75dac86 |
Die Instanz merkt sich, was der Kunde zu seinen Daten gesagt hat
tests / pest (push) Has been cancelled
Details
tests / assets (push) Has been cancelled
Details
tests / release (push) Has been cancelled
Details
Drei Zustaende, nicht zwei: `null` heisst "noch nicht gefragt". Jede Instanz, die vor diesem Bau gekuendigt wurde, hat die Frage nie gesehen — ihr ein "nein" zu unterstellen hiesse, still fuer Menschen zu antworten, die niemand gefragt hat. |