Aller au contenu principal

Échouer en se fermant, jamais de façon destructive : au cœur du cycle de vie des licences Zaris

· 11 minutes de lecture
Clustron Team
Ingénierie des systèmes distribués

Le cycle de vie des licences Zaris

Les licences sont la partie d'un produit à laquelle la plupart des ingénieurs ne veulent jamais penser, et cela se voit. Le schéma habituel est une bombe à retardement : une date est vérifiée, et quand elle passe, le logiciel vous punit — il lève une exception au démarrage, il corrompt ou efface l'état local pour vous empêcher de le « voler », ou il plante simplement et emporte vos données avec lui. Cette conception traite vos propres opérateurs en adversaires, et elle échoue exactement au moment où vous pouvez le moins vous le permettre : en production, après que l'horloge a silencieusement basculé.

Zaris adopte la posture inverse. La vérification de licence est l'interrupteur le plus critique pour la sûreté d'un store de données distribué, alors elle est construite comme tel : une unique fonction de politique pure et déterministe, un chemin d'application gracieux et entièrement réversible qui ne détruit jamais un octet, et une horloge par machine qui reprend à l'instant où vous activez une clé. Cet article parcourt le fonctionnement réel de ce cycle de vie dans le code livré — car l'ingénierie intéressante ici réside dans ce qu'il refuse de vous faire subir.

Le cycle de vie, en machine à états​

Une machine jamais licenciée traverse un petit ensemble explicite d'étapes. Un essai automatique de 30 jours démarre à la première exécution, sans clé ni inscription. Lorsqu'il s'écoule, le produit ne s'arrête pas — il entre dans une fenêtre de grâce où la console web est verrouillée mais où le plan de données continue de servir. Ce n'est qu'après que la fenêtre de grâce s'est écoulée elle aussi que le produit cesse gracieusement de servir, vos données étant préservées. Activer une licence valide à tout moment court-circuite directement vers Licensed.

Les deux valeurs par défaut sont toutes deux de 30 jours — un essai de base de 30 jours, puis une grâce produit de 30 jours après sa fin — et ce sont toutes deux de simples constantes, surchargables par déploiement :

public const int DefaultBaseTrialDays   = 30;
public const int DefaultProductGraceDays = 30;

Il existe deux autres états quasi-terminaux dans lesquels une clé peut vous placer directement : Licensed (une licence Enterprise valide, tout fonctionne, l'horloge locale est ignorée) et Non-Production (une licence de niveau développement valide, tout fonctionne mais est signalé comme non destiné à la production).

La politique est une fonction pure​

La tentation dans le code de licence est de disséminer des vérifications if (DateTime.Now > expiry) à travers la console, le service de gestion et l'hôte de nœud — puis de les voir se contredire, parce que l'un a lu l'horloge une seconde plus tard, l'un a mis en cache un fichier périmé, l'un a oublié la fenêtre de grâce. Zaris a exactement un seul endroit qui décide, et c'est une fonction pure sans E/S, sans lecture d'horloge et sans appels au SDK :

public static LicenseVerdict Evaluate(
DateTime nowUtc,
PaidLicenseStatus paid, // resolved Keyright license (or None)
TrialStatus trial, // the local machine-bound trial marker
int baseTrialDays = DefaultBaseTrialDays,
int productGraceDays = DefaultProductGraceDays)

Tout ce dont dépend le verdict est un argument. La console web, le service de gestion et chaque hôte de nœud résolvent leurs propres entrées — l'heure courante, le statut de licence payante, le marqueur d'essai local — et les font passer par cette même fonction, ce qui garantit un verdict identique partout. Et comme la logique de transition ne touche rien d'externe, chaque arête de la machine à états est exhaustivement testable unitairement sans simuler un système de fichiers ni une horloge : vous passez un nowUtc et vous affirmez l'étape.

La sortie est un unique enregistrement qui porte à la fois le message destiné à l'humain et les deux booléens qui pilotent l'application :

public sealed record LicenseVerdict(
LicenseStage Stage,
bool IsConsoleGated, // -> the console returns HTTP 402
bool IsProductStopped, // -> the data plane refuses to serve
bool IsPaid, bool IsTrial,
int DaysRemaining, DateTime? ExpiryUtc,
string StatusBadge, string Message);

Échouer en se fermant — mais « fermé » signifie l'essai, pas un plantage​

Lorsque le résolveur de licence payante ne peut pas joindre le service émetteur, ou qu'un fichier de bail est illisible, ou que quoi que ce soit se passe mal lors de la résolution de la licence Keyright, le service ne lève pas d'exception et ne suppose pas optimistiquement que vous êtes licencié. Il se replie sur l'horloge d'essai locale :

try { paid = _paid.Resolve(); }
catch { paid = PaidLicenseStatus.None; } // fail-closed to the trial clock

« Échouer en se fermant » ici n'est délibérément pas « échouer en s'arrêtant ». Un hoquet réseau transitoire pendant la résolution de licence vous dépose sur le marqueur d'essai hors ligne propre à la machine, qui fait autorité et n'a besoin d'aucun réseau. Si vous êtes encore dans votre fenêtre d'essai ou de grâce, rien ne change pour votre cluster en cours d'exécution. Seul le passage réel du temps — et non une panne — vous fait avancer vers un arrêt.

Deux points d'application, aucun des deux destructif​

Le verdict expose deux interrupteurs indépendants, car la console et le plan de données sont bloqués à des moments différents, à dessein.

Le verrou de console (IsConsoleGated). Dès l'instant où l'essai s'écoule, les routes d'administration et l'API /v1 de la console web renvoient 402 Payment Required avec un message indiquant comment la réactiver. C'est un middleware situé devant la console — il ne touche pas du tout au plan de données :

if (sw.IsGated) {
context.Response.StatusCode = StatusCodes.Status402PaymentRequired;
// ... reason + support URL ...
}

Point crucial, pendant cette étape le produit continue de tourner. Vos clients continuent de lire et d'écrire ; la réplication, la coordination et le plan de données sont intacts. Ce que vous perdez est la surface de gestion, avec des avertissements bruyants vous disant que la fenêtre s'est fermée — un coup de pouce sûr en production, pas une panne.

Le verrou du plan de données (IsProductStopped). Ce n'est qu'après que la fenêtre de grâce s'est écoulée que le serveur commence à refuser les opérations de données. Il le fait en renvoyant un statut d'erreur — jamais en supprimant des données, jamais en plantant :

if (_license?.IsProductStopped == true && !IsLicenseExempt(request.Command))
return new ZarisResponse {
Success = false,
Status = KvStatus.Unavailable,
Error = _license.ProductStoppedMessage // "...activate a license to resume"
};

Deux détails rendent cela humain plutôt qu'hostile. D'abord, le moteur central n'a aucune dépendance sur l'implémentation des licences — il consulte une minuscule interface ILicenseGate à deux membres (IsProductStopped, ProductStoppedMessage) qui vit dans l'assembly d'abstractions. L'hôte de nœud câble le véritable service adossé à Keyright derrière elle. Le store de données ne sait pas ce qu'est Keyright.

Ensuite, un serveur arrêté n'est pas un serveur mort. Un ensemble spécifique de commandes reste exempté afin qu'un opérateur puisse toujours se connecter, inspecter et récupérer :

private static bool IsLicenseExempt(ZarisCommandType cmd) =>
cmd == ZarisCommandType.Hello
|| cmd == ZarisCommandType.ClusterInfoRequest
|| /* the read-only diagnostic snapshots */ ;

La poignée de main, la sonde cluster-info et les snapshots de diagnostic en lecture seule continuent tous de fonctionner. Vous pouvez vous connecter, voir l'état du cluster, confirmer que les données sont intactes et activer une licence — le tout pendant qu'il est « arrêté ». L'état est préservé sur disque/en mémoire exactement tel qu'il était ; la seule chose retenue est le service des lectures et des écritures, et elle est retenue de façon réversible.

Une horloge par machine​

Un essai qui se réinitialise à chaque réinstallation, ou qui accorde 30 jours neufs par nœud dans un cluster, n'est pas un essai. Zaris impose une machine = un siège : une horloge d'essai unique partagée par chaque processus Zaris sur une machine — manager, nœuds co-localisés, console — quel que soit le compte de service sous lequel ils s'exécutent. Cela est réalisé en plaçant le marqueur d'essai dans un emplacement à l'échelle de la machine (CommonApplicationData / ProgramData) et en le miroitant vers une seconde copie à l'échelle de la machine.

Le marqueur lui-même est conçu pour résister à une altération désinvolte tout en étant honnête sur le fait qu'il ne s'agit pas de DRM :

  • Il est lié à une empreinte machine — un hachage du GUID machine de l'OS (MachineGuid sous Windows, /etc/machine-id sous Linux) plus le nom de la machine. Un fichier de marqueur copié vers une autre machine y est simplement ignoré ; il n'accorde rien à cette machine.
  • Chaque copie est signée par HMAC. Une copie éditée ou falsifiée est détectée et traitée comme altérée.
  • Le démarrage valide le plus ancien l'emporte entre les copies miroitées, et les extensions ne font jamais qu'avancer, de sorte qu'un essai ne peut pas être redémarré ou discrètement rembobiné en supprimant l'un des deux fichiers.
  • Un lastSeen monotone est suivi ; si l'horloge système est reculée au-delà d'une tolérance de 24 heures, cela est également signalé comme une altération — vous ne pouvez pas gagner du temps en reculant l'horloge.

Et un marqueur altéré n'est jamais récompensé. Plutôt que d'accorder silencieusement ou de réinitialiser silencieusement, la politique traite une altération détectée comme le pire cas et passe directement à arrêté :

if (trial.Tampered)
return Stopped(tampered: true); // never reward an altered/rolled-back marker

La limitation honnête, documentée à même le code source : supprimer toutes les copies du marqueur réinitialise bel et bien l'essai. C'est une frontière acceptée du modèle de menace — l'objectif est d'élever la barre contre un contournement désinvolte sur une base de code visible en open source, non de prétendre qu'il s'agit d'une protection anti-copie inviolable.

Les extensions prolongent ; elles ne redémarrent jamais​

Besoin de plus de temps d'évaluation ? Une clé de niveau Trial émise par l'éditeur prolonge la fenêtre — par exemple à 90 jours au total — plutôt que de démarrer un second essai. Lorsqu'une telle clé est observée, son expiration est persistée dans le marqueur, faisant avancer la fin de l'essai :

// only ever forward — never shortens, never resets the base start
var extended = current is { } c && c >= untilUtc ? c : untilUtc;

Persister l'extension compte : cela signifie que la fenêtre plus longue (et sa grâce) survit à l'expiration ultérieure de la clé ou à la mise hors ligne de la machine. Vous n'avez pas besoin de rester connecté au service de licence pour conserver l'extension qui vous a été accordée.

L'activation reprend sans redémarrage​

Comme un produit arrêté est réversible, l'activation doit prendre effet sur un processus vivant. C'est le cas. Le service de licence met en cache son verdict et le réévalue lors d'un rafraîchissement périodique (toutes les cinq minutes par défaut), et une activation explicite force une réévaluation immédiate :

public async Task<LicenseVerdict> ActivateAsync(string licenseKey, ...)
{
await _paid.ActivateAsync(licenseKey.Trim(), ct);
return Refresh(); // new verdict is live at once; the data plane resumes on its own
}

Un cluster arrêté que vous licenciez en pleine panne se remet simplement à servir à l'évaluation suivante — pas de redémarrage progressif, pas de rechargement de données, pas de fenêtre où l'état est à risque.

Il y a trois façons d'introduire une clé, correspondant à la façon dont les gens font réellement tourner leurs systèmes :

  • Set-ZrLicense -Key "LIC-…" depuis le shell d'administration applique la clé sur chaque machine manager de l'espace de travail (chacune lie son propre siège par machine). -Deactivate libère un siège. Get-ZrLicenseStatus affiche l'étape et le badge de chaque manager.
  • ZARIS_SUPPORT_KEY comme variable d'environnement est activée une fois au démarrage — définissez-la, redémarrez, terminé. Elle est idempotente : réactiver une machine déjà liée ne consomme aucun siège supplémentaire.
  • Un token hors ligne pour les déploiements isolés du réseau : un blob signé pré-émis que Keyright vérifie entièrement hors ligne, sans appel maison.

Une poignée de variables d'environnement ajustent le reste pour un déploiement donné : ZARIS_SUPPORT_WINDOW_DAYS et ZARIS_PRODUCT_GRACE_DAYS ajustent les deux fenêtres, ZARIS_DATA_ROOT relocalise le marqueur, et ZARIS_LICENSE_SERVICE_URL pointe vers un service émetteur auto-hébergé.

Pourquoi le construire ainsi​

Il aurait fallu moins de code pour lever une exception sur une date expirée. La machinerie supplémentaire — une fonction de politique pure, un verrou à deux étapes, des commandes de récupération exemptées, un marqueur miroité et inviolable, la reprise sans redémarrage — tout cela existe pour honorer un principe : une décision de licence ne doit jamais vous coûter vos données, et doit toujours être réversible en faisant la chose évidente.

L'essai s'écoule et vous continuez à tourner, avec des avertissements. La grâce s'écoule et le store cesse de servir tout en préservant tout et en restant joignable pour la récupération. Vous activez une clé et il reprend, en direct. Et toute la décision provient d'une fonction déterministe unique avec laquelle chaque composant s'accorde, de sorte qu'aucun recoin du système n'applique silencieusement quelque chose de différent. Les licences sont là où beaucoup de bons logiciels se retournent contre leurs propres utilisateurs ; l'objectif ici était d'en faire le seul endroit où un store distribué est autorisé à dire « non » sans jamais être autorisé à dire « disparu ».