Blog ·
Ein Fähigkeitswort für einen alten Befehl
Fügt eine Runde ein protobuf-Feld hinzu, scheitert ein alter Agent laut. Ändert sie nur die Bedeutung eines Flags, antwortet er plausibel und falsch.
Von Provibr
Unser Agent läuft im eigenen Netz des Kunden und wählt sich nach außen zu uns. Er aktualisiert sich selbst, aber nicht sofort und nicht immer, sodass die Flotte zu jedem Zeitpunkt eine Streuung von Versionen ist und die Plattform wissen muss, was jede einzelne kann.
Sie weiß es, weil der Agent es sagt. Bei jeder Sitzung sendet der Agent eine Liste kurzer Wörter, eines je Fähigkeit, und die Plattform verweigert einen Befehl, dessen Wort fehlt, statt ihn zu senden und zu hoffen.
Diese Runde fügte das dritte solche Wort für Proxmox-Container hinzu, und es ist das erste, bei dem die interessante Frage nicht war, ob man es hinzufügt, sondern was ohne es geschehen wäre.
Warum es die Wörter überhaupt gibt
Der Mechanismus schützt vor einer bestimmten Eigenschaft von protobuf: ein unbekanntes Feld in einem oneof wird verworfen.
Fügt eine Runde einen neuen Befehl als neues Feld hinzu, so parst ein Agent, der älter ist als die Runde, die Nachricht, erkennt die Feldnummer nicht, verwirft sie und hält am Ende einen ProxmoxCommand ohne Befehl darin in der Hand. Dann sagt er genau das. Die Meldung ist ehrlich und vollkommen nutzlos: der Betreiber sieht eine Beschwerde über einen leeren Umschlag, und die Ursache liegt vier Schichten entfernt, in einem Versionsunterschied, den niemand erwähnt hat.
Also verweigert die Plattform zuerst. Fehlt das Wort, verlässt der Befehl nie das Haus, und die Verweigerung nennt die Fähigkeit und die Version des Agenten. Das ist ein schlechteres Ergebnis, als wenn der Befehl funktioniert, und ein viel besseres, als wenn er auf eine Weise scheitert, deren Verfolgung einen Nachmittag kostet.
So oder so ist es ein lauter Fehlschlag. Das erweist sich als wichtig.
Das dritte Wort ist kein neues Feld
Die Änderung dieser Runde war, dass ein Container jetzt seine Hardware zeigen kann. Der Befehl, der das tut, GetHardware, ist nicht neu. Jeder Agent bis zurück zu den frühen Arbeiten am Lebenszyklus virtueller Maschinen kennt die Nachricht und weiß, wie er sie beantwortet.
Geändert hat sich ein Flag daneben, das sagt, auf welche Art von Ressource sich der Bezeichner bezieht. Ein Agent von vor dieser Runde empfängt dieses Flag, kennt es nicht und verwirft es, genau so, wie protobuf es vorschreibt. Dann tut er, was er mit einer Hardwareanfrage immer getan hat: er fragt Proxmox nach der Konfiguration der Maschine auf dem QEMU-Pfad.
Nichts ist leer. Nichts beschwert sich. Der Agent glaubt, dass ihm eine Frage gestellt wurde, die er versteht, und er antwortet.
Das kommt auf zwei Weisen an, und keine davon ist laut. Hat Proxmox unter dem Pfad der virtuellen Maschinen nichts bei diesem Bezeichner, wird dem Kunden gesagt, dass eine Maschine nicht existiert, über einen Container, der vor ihm läuft. Und kommt doch eine Konfiguration zurück und wird sie dem Parser übergeben, der für virtuelle Maschinen gebaut ist, dann ist die Antwort nicht leer, sondern falsch.
Die falsche Antwort ist konkret und sieht in Ordnung aus
Die beiden Konfigurationsformate sind nah genug beieinander, um geparst zu werden, und verschieden genug, um ins Gewicht zu fallen. Eine virtuelle Maschine schreibt ihre erste Netzwerkkarte als Modell und MAC-Adresse, verbunden durch ein Gleichheitszeichen. Ein Container schreibt zuerst einen Schnittstellennamen und dann die MAC in einem eigenen benannten Feld.
Der Parser für virtuelle Maschinen nimmt das erste Schlüssel-Wert-Paar als Modell und Adresse. Bei der Zeile eines Containers meldet er deshalb eine Netzwerkkarte, deren Modell name und deren MAC-Adresse ETH0 ist.
Das ist eine plausibel aussehende Zeile in einer Tabelle. Sie hat ein Modell, sie hat eine Adresse, sie wird ohne Warnung dargestellt. Sie ist der Grund, warum der Containerpfad einen eigenen Parser brauchte, und sie ist genau die Klasse von Defekt, die niemand meldet, weil nichts darin zu melden ist. Jemand sieht auf einen Bildschirm, findet die Modellspalte seltsam und geht weiter.
Ein Befehl, den es schon gibt, ist also eine gefährlichere Art von Änderung als ein neuer Befehl. Der neue scheitert am Umschlag, öffentlich. Dieser scheitert an der Bedeutung, im Verborgenen.
Warum wir nichts geweitet und stattdessen eine Zeile hinzugefügt haben
Die naheliegende Alternative ist, das Wort zu weiten, das es schon gibt. Es gibt ein Wort, das sagt, dass der Agent den Containerpfad überhaupt versteht, und es wäre ein Zeichen Arbeit gewesen, es auch das Neuere bedeuten zu lassen.
Das würde jeden Containerbefehl von jedem Agenten verweigern, der sich noch nicht aktualisiert hat, auch von denen, die heute einwandfrei arbeiten. Eine Flotte, die derzeit Container bedient, würde für eine Fähigkeit dunkel werden, die sie tatsächlich hat, weil wir unter ihr die Bedeutung unseres Wortes geändert haben.
Ein Wort ist ein Versprechen darüber, was gebaut wurde, und das Versprechen muss seine Bedeutung so lange behalten, wie irgendjemand es noch hält. Ein drittes Wort hinzuzufügen kostet eine Konstante auf jeder Seite und verweigert genau die Befehle, die verweigert werden müssen, und sonst nichts. Es legt die Begründung außerdem dorthin, wo ein Mensch sie finden wird, nämlich neben die Konstante und nicht in eine Commit-Nachricht.
Die beiden Seiten sind zwei Konstanten, eine in der Rust-Quelle und eine im geteilten TypeScript, von denen jede die andere nennt. Diese Paarung ist nicht elegant, und jeder Versuch, sie elegant zu machen, endet bei einem Buildschritt zwischen zwei Sprachen, die unabhängig voneinander lesbar sein müssen.
Der Stub sagt absichtlich zwei der drei
Es gibt einen Stub-Agenten, mit dem wir das Gateway ohne Hypervisor bewegen können, und er kündigt die Containerwörter ebenfalls an.
Er kündigt zwei davon an. Er sagt, dass er den Containerpfad kennt, und er sagt, dass er Containerhardware liest, und er beansprucht das dritte bewusst nicht, denn einen Container neu aufzusetzen kann er nicht.
Das ist erwähnenswert, denn ein Stub, der alles beansprucht, ist ein Stub, der jeden Verweigerungspfad unprüfbar macht. Und das Gegenteil zählt genauso: ohne das Hardwarewort hätte das Gateway zu Recht verweigert, und die Prüfung hätte still eine Verweigerung statt einer Antwort gemessen. Ein Prüfgerüst, das über seine eigenen Fähigkeiten lügt, erzeugt grüne Ergebnisse über die falsche Sache.
Was wir aufschreiben würden
- Protobuf verwirft unbekannte Felder still, sodass ein neuer Befehl einen alten Agenten als leerer Umschlag erreicht.
- Verweigern Sie vor dem Senden. Eine Verweigerung, die die Fähigkeit und die Version nennt, schlägt eine Beschwerde aus vier Schichten Tiefe.
- Eine Änderung ohne neues Feld ist die gefährliche. Nichts wird verworfen, also beschwert sich nichts.
- Der Fehlermodus, den Sie wollen, ist laut. Eine plausible Antwort, die falsch ist, wird Ihnen nie gemeldet.
- Zwei Konfigurationsformate, die beide durchgehen, sind schlimmer als zwei, die es nicht tun.
- Weiten Sie nie ein bestehendes Fähigkeitswort. Es ist ein Versprechen, das Agenten halten, die Sie nicht erreichen können.
- Ein zusätzliches Wort kostet eine Konstante auf jeder Seite, und verweigert nur das, was verweigert werden muss.
- Legen Sie die Begründung neben die Konstante, dorthin, wo der Nächste tatsächlich stehen wird.
- Ein Stub muss ablehnen, was er nicht kann, sonst wird jeder Verweigerungspfad unprüfbar.
- Ein Gerüst, das zu viel beansprucht, erzeugt grüne Ergebnisse über die falsche Sache.
Zur Klarheit darüber, was das ist: ein drittes Fähigkeitswort für Proxmox-Container, hinzugefügt neben einer Änderung, die kein neues Protokollfeld eingeführt hat, mit der Begründung an beiden Konstanten festgehalten und dem Stub-Agenten, der zwei der drei ankündigt; die Runde brachte das Container-Testmodul von fünfunddreißig Tests auf neununddreißig, Formatierung und Releaseziel sauber, und endete mit den geteilten Paketen bei 5347 grün, dem Gateway bei 1467, den Stub-Werkzeugen bei 489 und der Web-Suite bei 4593, ohne Fehlschläge, dazu fünfzig Browserprüfungen grün in zwei Sprachen. Nicht bewiesen: nichts davon lief gegen ein echtes Proxmox, das Verhalten eines älteren Agenten bei einem Containerbezeichner ist also aus den beiden Konfigurationsformaten und dem Parser festgestellt und nicht beobachtet, und die Kette im Betrieb wurde gegen unseren eigenen Stub bewegt.
Aus der Plattformarbeit an Provibr. Was geschieht, wenn zwei Runden nach derselben Feldnummer greifen, steht in Zwei Runden, eine Feldnummer, die Runde, die dem Agenten zuerst Container beibrachte, steht in Ein Container wird ersetzt, nicht neu installiert, und wie ein Agent überhaupt entscheidet, ob er einem neuen Build traut, steht in Wie ein signiertes Agenten-Update sich verweigert.