Zum Inhalt springen

Blog ·

Grün, weil es nicht hinkommen konnte

Unser Vertragstest beantwortete den gewöhnlichsten Befehl, den es gibt, mit nicht unterstützt. Jeder Test, der davon abhing, war aus dem falschen Grund grün.

Von Provibr

Wir halten einen Stub-Agenten vor. Er spricht dasselbe Protokoll wie der echte, er läuft neben dem Gateway, und es gibt ihn, damit die Plattformseite jedes Befehls geprüft werden kann, ohne den Hypervisor eines Kunden anzufassen. Er ist ein Vertragstest mit einem Prozess drumherum.

Bis zu dieser Woche beantwortete dieser Stub den gewöhnlichsten Befehl, den es gibt, diese Maschine ausschalten, mit COMMAND_STATUS_UNSUPPORTED.

Niemandem war das aufgefallen, und der Grund, warum es niemandem aufgefallen war, ist der Kern dieses Artikels. Jeder Test, der einen Schaltbefehl an den Stub schickte, war grün. Sie waren grün, weil der Befehl verweigert wurde, die Plattform die Verweigerung richtig behandelte und die Zusicherung am Ende hielt. Die Tests logen nicht. Sie beschrieben einen Pfad, den niemand ausführen wollte.

Was ein fehlender Zweig mit den Tests um ihn herum macht

Die Flow-Maschine hat einen Aktionsknoten: einen Befehl gegen einen Server ausführen und dann je nach Ausgang dem ok-Draht oder dem error-Draht folgen. Diesen Knoten gab es im Produkt seit der Runde, die ihn eingeführt hatte, und er hatte Tests.

Diese Tests konnten immer nur den error-Draht erreichen. Nicht wegen eines Fehlers in ihnen, sondern weil der einzige Agent, der ihnen zur Verfügung stand, jeden Befehl verweigerte, den sie schicken konnten. Ein Test, der zusichert, „der Fehlerzweig wurde genommen“, besteht ohne Weiteres, wenn der Fehler vom Testgerüst hergestellt wurde und nicht von der geprüften Sache.

Das ist die Gestalt, vor der unsere eigenen Notizen warnen, in einem Satz, der seit Monaten im Projekthandbuch steht: ein Test kann grün sein, weil er den guten Pfad nicht erreichen kann. Die Spielart, die uns hier erwischt hat, ist schlimmer als die übliche, denn es geht nicht darum, dass ein Zweig nie geschrieben wurde. Der Zweig war geschrieben, er funktioniert, und die ganze Zeit stand ein bestandener Test daneben.

Der Stub musste Zustand halten und nicht nur antworten

Die fünf Schaltbefehle hinzuzufügen war nicht damit getan, Erfolg zurückzumelden. Die Plattform entscheidet anhand des aktuellen Status eines Servers, was ein Befehl tun darf: Sie können eine laufende Maschine nicht fortsetzen, und Sie können eine nicht starten, die schon hochgefahren ist. Diese Entscheidung liegt in der Plattform, und sie liest eine Spalte.

Ein Agent, der „fertig“ antwortet, ohne etwas zu ändern, lässt diese Spalte, wo sie war. Ein Test kann also genau einen Befehl schicken und steckt dann fest, denn der zweite Befehl wird von der Zustandsmaschine der Plattform verweigert, richtigerweise, auf einem Status, der sich nie bewegt hat.

Der Stub hält deshalb je Maschine einen Schaltzustand und schickt die Zustandsbeobachtung über die Sitzung zurück, so wie es ein echter Agent tut. Das war es, was tatsächlich fehlte. Keine Antwort, eine Folge.

Damit konnte ein Flow, der über eine Liste schleift und je Durchgang zwei Befehle absetzt, zum ersten Mal gegen den Stub laufen. Sechs Befehle, die alle den abgeschlossenen Zustand erreichten, und eine Schleife, die ihren zweiten Durchgang erreichte, weil der erste Durchgang wirklich etwas verändert hatte.

Zwei Dinge, die der Stub bewusst nicht tut

Er verweigert unmögliche Übergänge nicht mit einem erfundenen Fehlercode. Die Grenze zwischen erlaubt und nicht erlaubt liegt in der Plattform, und die Begründungscodes des Agenten sind eine feste Liste, die in den Quelltext des echten Agenten einkompiliert ist. Einen Code zu erfinden, den der echte Agent nicht hat, hieße, einen Vertrag zu prüfen, den es nicht gibt. Ein Stub, der strenger ist als die Sache, für die er einsteht, bringt Ihnen Dinge bei, die nicht stimmen.

Er täuscht auch keine Dauer vor. Ein Herunterfahren auf einem echten Gast wartet darauf, dass das Betriebssystem antwortet, und fällt nach einer Zeitüberschreitung auf einen harten Stopp zurück. Im Stub geschieht alles innerhalb des Takts. Aber die Zeitüberschreitung und das Rückfallkennzeichen werden bis in die Ausgabe durchgereicht, sodass ein Test sehen kann, dass sie angekommen sind, obwohl nichts auf sie gewartet hat. Einen Parameter durchzureichen und ihn einzulösen sind verschiedene Behauptungen, und nur eine davon wird aufgestellt.

Ein zweiter Wecker macht eine Messung zufällig

Die andere Reparatur derselben Runde betrifft das Messen und nicht das Produkt.

Pausierte Läufe werden von einem Worker im Gateway geweckt, der alle zehn Sekunden eine Warteschlange abfragt. Unser Ende-zu-Ende-Skript weckt selbst, im eigenen Prozess, damit es genau zusichern kann, was geschehen ist. Beide sind für sich in Ordnung und zusammen katastrophal: läuft ein Gateway mit eingeschaltetem Wecker gegen dieselbe Entwicklungsdatenbank, frisst es die Zeilen, sobald deren Weckzeit vorbei ist, und das Skript misst, wer von beiden zuerst da war.

Das ist geschehen. Ein Zweig des Skripts scheiterte einmal, ohne dass etwas an ihm falsch war, und das ist die teuerste Art des Scheiterns, denn die natürliche Reaktion ist, nach einem Fehler zu suchen.

Die Behebung ist keine Anmerkung, die sagt „achten Sie darauf, dass kein Worker läuft“. Sie ist eine Funktion, die es misst. Vor allem anderen legt das Skript ein Wegwerf-Warten mit bereits verstrichener Weckzeit an, beobachtet es acht Sekunden lang und prüft, ob jemand anderes es angefasst hat. Wenn ja, verweigert das Skript den Lauf und sagt, warum.

Das ist wenig Code für viel Vertrauen. Eine in einer Anmerkung aufgeschriebene Annahme ist eine Annahme; dieselbe Annahme als Prüfung geschrieben, die zuerst läuft, ist eine Vorbedingung.

Wiederholungen brauchen eine Obergrenze und eine steigende Pause

Als die guten Pfade endlich erreichbar waren, wurden auch die Fehlerpfade richtig gemessen.

Ein Warten, das beim Fortsetzen eine Ausnahme wirft, weil eine Vorlage gelöscht wurde oder es einen Server nicht mehr gibt, würde sonst bei jedem Takt ewig wiederholt, in einer Warteschlangentabelle, die nie leerläuft. Es gibt eine Obergrenze von fünf Versuchen mit einer Pause, die sich verdoppelt: sechzig, einhundertzwanzig, zweihundertvierzig, vierhundertachtzig Sekunden. Diese vier Abstände wurden aus einem echten Lauf abgelesen und nicht aus der Funktion, die sie berechnet. Beim fünften Versuch wird das Warten abgebrochen und der Lauf als gescheitert vermerkt.

Der Gegenbeweis ist der Teil, auf den es ankommt. Mit ausgeschalteter Erschöpfungswache bleibt derselbe Lauf unbegrenzt im wartenden Zustand. Eine Wache ist erst bewiesen, wenn die Lage, die sie verhindert, tatsächlich hergestellt wurde.

Darunter liegt eine zweite Fehlerart. Ein Worker beansprucht ein Warten, indem er es aus dem wartenden Zustand herausnimmt, bevor er zu laufen beginnt, denn die Nebenwirkungen eines Flows geschehen während des Durchlaufs und nicht danach. Stirbt der Prozess, der es beansprucht hat, mitten im Durchlauf, sitzt die Zeile für immer im beanspruchenden Zustand, und die Abfrage, die nach fälligen Wartevorgängen sucht, sieht sie nicht: sie fragt nach wartenden Zeilen. Ein Aufräumlauf stellt Ansprüche zurück, die älter als fünfzehn Minuten sind, und ein einzelner Takt gegen ein wirklich laufendes Gateway gab einen frei, weckte einen und setzte einen fort, ohne Fehler.

Ein paar Kleinigkeiten, die nur durch Ausführen zu finden sind

Eine Verzögerung von drei Sekunden ergibt keinen ungültigen Knoten. Sie macht den ganzen Graphen ungültig, denn das Mindestmaß ist fünf, und der Ausführer gibt überhaupt nichts zurück statt eines Laufs mit einem kaputten Knoten. Das ist richtig und beim ersten Mal auch verwirrend, denn Sie erwarteten einen Fehler über einen Knoten und bekamen Schweigen über einen Graphen.

Die Ports des Gateways sind nach dem Gateway benannt. Setzt man die Variablen ohne dieses Präfix, lauscht es still auf seinen Vorgabewerten, und das sieht genau wie ein Netzwerkproblem aus.

Ein Abbruch schreibt die Knoten, die er nie erreicht hat, als übersprungen ins Journal. Das ist ein bewusster Unterschied dazu, sie wegzulassen: ein Lauf, der früh endete, und ein Lauf, der einen anderen Weg nahm, sehen gleich aus, wenn die nicht erreichten Knoten einfach fehlen.

Und ein Genehmigungsknoten hinterließ verwaiste Zeilen in der Benachrichtigungstabelle, sechzehn davon auf der Entwicklungsdatenbank. Das Aufräumen dafür geschieht je Lauf und über den Entdopplungsschlüssel, nie über einen Musterabgleich auf einem Präfix, denn ein Musterabgleich würde Zeilen erwischen, die zur Runde eines anderen gehören.

Was wir aufschreiben würden

  1. Ein Test, der den guten Pfad nicht erreichen kann, ist aus dem falschen Grund grün. Fragen Sie, was der Test täte, wenn die Funktion gelöscht wäre.
  2. Ein Stub, der alles verweigert, ist kein Stub, sondern eine Wand. Jeder Test dahinter misst die Verweigerung.
  3. Eine Attrappe muss Zustand halten und nicht nur antworten. Wenn die Plattform anhand einer Spalte entscheidet, muss irgendetwas diese Spalte bewegen.
  4. Machen Sie einen Stub nicht strenger als die echte Sache. Ein erfundener Fehlercode prüft einen Vertrag, den niemand umsetzt.
  5. Reichen Sie einen Parameter auch dann durch, wenn Sie ihn nicht einlösen können, damit ein Test belegen kann, dass er angekommen ist.
  6. Zwei Wecker auf einer Datenbank machen die Messung zufällig. Einer von beiden gewinnt, und Sie können nicht sagen, welcher.
  7. Machen Sie aus der Vorbedingung in der Anmerkung eine Prüfung, die zuerst läuft. Dann ist sie eine Tatsache statt einer Hoffnung.
  8. Beweisen Sie eine Wache, indem Sie sie ausschalten. Eine Wiederholungsgrenze, die niemand erreicht hat, ist eine Behauptung.
  9. Eine beanspruchte Zeile ist für die Abfrage unsichtbar, die nach fälliger Arbeit sucht. Irgendetwas muss veraltete Ansprüche zurückstellen.
  10. Nicht erreichte Knoten gehören als übersprungen ins Journal, sonst ist ein früher Ausstieg nicht von einem anderen Weg zu unterscheiden.

Zur Klarheit: sieben zuvor unbewiesene Zweige wurden Ende zu Ende genommen, jeder mit einem roten Gegenbeweis; die Merge-Prüfung lief 16 von 16 Schritten ohne Fehlerzeile durch, die Web-Suite war 4583 grün, Packages, Gateway und Tools zusammen 7287 grün, die eigene Suite des Stub-Agenten 481 grün, das Ende-zu-Ende-Skript 89 grün, der Weckerzweig 13 grün, der Genehmigungsmailzweig 22 grün und die Schleife mit echten Aktionen 18 grün. Nicht bewiesen: es lief nichts gegen ein echtes Proxmox, ein unlesbarer Graph auf der Wartezeile selbst, ein Stapel, der größer ist als die Stapelgröße, und echte Mailzustellung über SMTP.

Aus der Plattformarbeit an Provibr. Wie der Durchlauf lernte anzuhalten und morgen weiterzulaufen, steht in Jeder Selbstaufruf stand in Endposition, und wo die Grenze zwischen einer Pause, die eine Anfrage aushalten kann, und einer, die den Lauf aussetzt, verläuft, steht in Eine Pause, die die Bestellung offen hielt.

Fangen Sie mit einem Host und einem Agenten an

Konto erstellen, den Agenten mit einem einzigen Befehl installieren und Ihren ersten Proxmox-Host verbinden. Die Testphase läuft 14 Tage und fragt nicht nach Zahlungsdaten.