Suchen
Schließen Sie dieses Suchfeld.

Engineering bei Qumulo: REST API

Geschrieben von:

Als wir mit dem Aufbau unseres Dateisystems begannen, wussten wir, dass wir das Dateisystem mit Befehlszeilentools, Benutzeroberfläche und automatisierten Tests steuern und überprüfen wollten. Verwendung einer REST API Diese Funktionalität verfügbar zu machen, war eine natürliche Lösung, und wir erkannten, dass unsere Kunden sie wahrscheinlich auch wünschen würden, wenn wir diese Funktionalität in Qumulo haben wollten. Daher haben wir uns von Anfang an dafür entschieden, unsere REST-API öffentlich zu machen.

In diesem Beitrag untersuchen wir die Grundsätze unserer API, die Herausforderungen bei REST und wie wir die API zusammen mit unserem Dateisystem kontinuierlich weiterentwickeln.

REST API-Grundsätze

Representational State Transfer (REST) ​​ist ein weit verbreiteter Architekturstil, mit dem Sie vermutlich bereits vertraut sind. Beim Definieren einer neuen API mit REST müssen Sie viele Entscheidungen treffen. Zuerst müssen Sie entscheiden, welche Art von Funktionalität in Ihrer API enthalten ist. Kontrollebene (Systemkonfiguration und Statistik)? Datenebene (Dateien und Metadaten, die im Dateisystem gespeichert sind)? Wir haben uns für beides entschieden, plus reine interne Endpunkte, um die Funktionsentwicklung zu unterstützen. Alles, was in einem Qumulo-Cluster möglich ist, kann über die REST-API ausgeführt werden.

Als nächstes betrachteten wir den Inhalt der Antworten. Wenn Sie Dateisystemprotokolle wie SMB oder NFS zum Lesen von Metadaten verwenden, erhalten Sie die Interpretation des Dateisystemstatus durch dieses Protokoll, und es kann in seinen Ausdrucksmöglichkeiten eingeschränkt sein. Im Gegensatz dazu liefert unsere REST-API Ground Truth – Kunden müssen die zurückgegebenen Daten nicht interpretieren und wir geben alle verfügbaren Informationen zurück. Wenn wir die Fähigkeiten des Dateisystems erweitern (wie das Speichern aggregierter Metadaten), erweitern wir unsere Endpunkte, um diese Fähigkeiten bereitzustellen.

Inspiriert vom REST API Design Rulebook klassifizieren wir jeden unserer Endpunkte als einen dieser Ressourcenarchetypen:

  • Dokument: ein Datenbankeintrag mit Feldern und Links zu verwandten Ressourcen
  • Sammlung: ein Verzeichnis von Ressourcen (normalerweise Dokumentressourcen)
  • Controller: eine ausführbare Funktion (wie „send-mail“)
  • Speichern: ein vom Client verwaltetes Ressourcen-Repository (im Allgemeinen nicht in unserer API verwendet)

Bei der Strukturierung unserer URIs wollten wir es Entwicklern oder Administratoren einfach machen, gegen unsere Endpunkte zu skripten oder Tools wie cURL zu verwenden. Wir wollten auch sicherstellen, dass Clients nicht versehentlich brechen, wenn sich der Vertrag eines Endpunkts ändert. Dies führte dazu, dass wir mehr Inhalt in die URI einfügen, z. B. die Versionsnummer, und explizite Verträge gegenüber impliziten bevorzugen. So lesen Sie beispielsweise ein Verzeichnis:

/ v1 /Dateien/ 2F% /Einträge/ ?Limit=1000

/ v1: Die Version des Endpunkts steht immer an erster Stelle
/Dateien/: das Dokument, die Sammlung oder der Controller, auf den bzw. auf den bzw. auf den bzw. auf der bzw. der es zuzugreifen ist. In diesem Fall ist /files/ eine Sammlung.
2F%: die ID eines Dokuments in der Dateisammlung. In diesem Fall das Stammverzeichnis des Dateisystems.
/Einträge/: die Aktion, die für die angegebene Datei/den angegebenen Ordner ausgeführt werden soll.
?Limit=1000: schließlich optionale Abfrageparameter für die Aktion.

Bei diesem Design ist der einzige HTTP-Header, den wir benötigen, die Autorisierung für Bearer-Token im OAuth2-Stil:

curl -k -X GET -H "Authorization: Bearer <token>" https://server:8000/v1/file-system</var/www/wordpress>

Es ist erwähnenswert, dass wir nicht versucht haben, vorhandene REST-APIs des Dateisystems nachzuahmen. Wir wollten, dass unsere API speziell auf die Fähigkeiten unseres Dateisystems zugeschnitten ist und dem Benutzer maximale Kontrolle über das System gibt. Wenn wir irgendwann in der Zukunft Clients unterstützen möchten, die S3, WebDAV oder was auch immer kommunizieren, werden wir neue Ports für diese Protokolle hinzufügen und sie von unserer Kern-REST-API getrennt halten.

Herausforderungen mit REST

Viele unserer Konfigurationsendpunkte weisen ein unkompliziertes Verhalten auf: Sie verwenden GET</var/www/wordpress> to retrieve a document (e.g. GET /v1/users/123</var/www/wordpress>), and you use SET</var/www/wordpress> or PATCH</var/www/wordpress> to update the document. The requests take effect immediately, so that when you receive a 200 OK</var/www/wordpress> response, you know the change has been made.

Aber REST ist weder zustandsbehaftet noch transaktional, was sich bei unsachgemäßer Betrachtung auf die Benutzererfahrung auswirken kann. Nehmen wir an, ein Administrator bearbeitet eine Dateifreigabe im Cluster mithilfe der integrierten Benutzeroberfläche. Zwischen dem Abrufen der Dateifreigabedetails durch die Benutzeroberfläche und dem Speichern der Änderungen durch den Administrator kann ein anderer Benutzer oder Prozess diese Dateifreigabe ändern. Standardmäßig gewinnt in unserer API der letzte Autor, sodass der Administrator diese Änderungen unabsichtlich blockieren würde. Das ist nicht die Benutzererfahrung, die wir wollen, also nutzen wir sie ETag</var/www/wordpress> and If-Match</var/www/wordpress> HTTP headers for all of our documents to prevent accidental overwrites. When the UI retrieves a document, it reads the ETag</var/www/wordpress> response header (entity tag, or essentially a hashcode) and stores that. Later, when updating that same document, the UI sends an If-Match</var/www/wordpress> request header, which tells the cluster to only perform the action if the document is the same as we expect. If the document changed, we’ll get back a 12 Precondition Failed</var/www/wordpress> response, which allows us to build a better experience for the user.

Auch lang andauernde Aktionen bedürfen besonderer Berücksichtigung. Um die Antwortzeiten unserer REST-API vorhersehbar zu halten, verarbeiten wir Anfragen mit kurzer Laufzeit synchron und Anfragen mit langer Laufzeit asynchron. Wir klassifizieren jeden Endpunkt in unserer API als kurz- oder langfristig, damit Kunden wissen, welche Art von Antwort sie verarbeiten müssen, um die Komplexität zu reduzieren. Alle GET, PUT</var/www/wordpress>, and PATCH</var/www/wordpress> operations on documents and collections are short-running requests, returning 200 OK</var/www/wordpress> when successfully processed. In contrast, we always POST to a controller endpoint for long-running requests, which return 202 Accepted</var/www/wordpress> with a URI to poll for completion status. For example, when joining a cluster to Active Directory, the client invokes the controller like this:

PREISANFRAGE (Request) POST /v1/ad/join
Anfrage Körper {
„Domäne“: „ad.server.com“,
„Benutzer“: „Domänenbenutzer“,
„password“: „domain-password“,
...
}

Wenn die Anfrage gültig ist, antwortet der Controller:

 

PREISANFRAGE (Request) POST /v1/ad/join
202 akzeptiert {
„monitor_uri“: „/v1/ad/monitor“
}

Der Kunde kann dann wiederholt ausstellen GET /v1/ad/monitor</var/www/wordpress> calls while waiting for the join action to succeed or fail.

REST-API-Entwicklung

Um sicherzustellen, dass unsere REST-API mit den Fähigkeiten unseres Dateisystems Schritt hält, werden die Endpunkte automatisch aus Code generiert. Das bedeutet, dass das Dateisystem, die API und die API-Dokumentation immer synchron sind. Unser Build-System verhindert, dass wir versehentlich Änderungen an internen Datenstrukturen vornehmen, die zu REST-API-Änderungen führen würden, die API-Clients beschädigen. Und indem wir unsere API-Dokumentation in Code umwandeln, bleibt sie mit dem Code auf dem neuesten Stand.

Zwei Jahre nach Beginn der Entwicklung unserer REST-API stellten wir fest, dass wir ein Problem hatten: Die API war organisch gewachsen, da verschiedene Entwicklerteams Funktionen hinzufügten, was zu Inkonsistenzen zwischen Endpunkten und einer fragwürdigen Hierarchie führte, die es schwierig machte, Funktionen zu entdecken. Um dieses Problem anzugehen, haben wir zwei Dinge getan: Wir sind über eine Reihe von Releases auf einen neuen API-Namespace migriert, um Konsistenz- und Auffindbarkeitsprobleme zu beheben, und wir haben eine API-Roadmap für die Qumulo-Ingenieure erstellt, die es der API ermöglicht, sich weiterzuentwickeln und konsistent zu bleiben. Ein Beispiel für eine Verbesserung des API-Namespace war die Konsolidierung aller Echtzeitanalysen-bezogene Funktionalität unter /v1/analytics. Zuvor war diese Funktionalität über den gesamten Namespace verstreut, und als wir von Kunden hörten, dass sie diese Funktionen nicht finden konnten, wussten wir, dass dies ein Bereich mit Verbesserungsbedarf war.

Nachdem wir unsere /v1-API nun gefestigt haben, können einzelne Endpunkte die Version ändern, wenn eine bahnbrechende Änderung erforderlich ist. (Breaking Changes umfassen Dinge wie das Hinzufügen neuer Pflichtfelder zu Anfragen oder das Ändern der Semantik der von uns zurückgegebenen Daten.) Auch mit dieser Bestimmung sind Breaking Changes der letzte Ausweg. Wir sind bestrebt, Möglichkeiten zu finden, Antwortdaten zu erweitern oder optionale Felder einzuführen, ohne bestehende API-Clients zu beeinträchtigen.

In diesem Beitrag haben wir die Grundsätze der REST-API von Qumulo untersucht, wie wir einige Herausforderungen mit REST bewältigt haben und unseren Ansatz zur Weiterentwicklung der API in Verbindung mit dem Produkt.

Verwandte Artikel

Nach oben scrollen