Hermes
Cette section contient une brève présentation d’Hermes et de ses fonctionnalités, puis définit ses concepts clés et détaille le fonctionnement de ses principaux processus.
Cette section contient une brève présentation d’Hermes et de ses fonctionnalités, puis définit ses concepts clés et détaille le fonctionnement de ses principaux processus.
Hermes est un outil de capture des changements de données (CDC) depuis n’importe quelles sources vers n’importe quelles cibles.
Le serveur Hermes interrogera régulièrement les sources de données et générera un différentiel entre le nouvel ensemble de données et le précédent issu du cache. Chaque différence sera convertie en un message d’événement et envoyée à un bus de messages (e.g. Kafka, RabbitMQ…).
Les clients recevront et traiteront chaque message d’événement pour propager les données vers leurs cibles respectives.
flowchart LR subgraph Datasources["Sources de données"] direction LR RefOracle RefPostgreSQL RefLDAP RefEtc end subgraph Hermes-server direction LR hermes-server end subgraph External_dependencies["Dépendences externes"] direction LR MessageBus end subgraph Hermes-clients direction LR hermes-client-ldap hermes-client-aspypsrp-ad hermes-client-webservice hermes-client-etc["..."] end subgraph Targets["Cibles"] direction LR LDAP ActiveDirectory webservice etc end RefOracle[(Oracle)]-->|Données|hermes-server RefPostgreSQL[(PostgreSQL)]-->|Données|hermes-server RefLDAP[(LDAP)]-->|Données|hermes-server RefEtc[(...)]-->|Données|hermes-server hermes-server-->|Événements|MessageBus(("Bus de message")) MessageBus-->|Événements|hermes-client-ldap MessageBus-->|Événements|hermes-client-aspypsrp-ad MessageBus-->|Événements|hermes-client-webservice MessageBus-->|Événements|hermes-client-etc hermes-client-ldap-->|Met à jour|LDAP[(LDAP)] hermes-client-aspypsrp-ad-->|Met à jour|ActiveDirectory[(Active Directory)] hermes-client-webservice-->|Met à jour|webservice[(Web service <i>nom</i>)] hermes-client-etc-->|Met à jour|etc[("...")] classDef external fill:#fafafa,stroke-dasharray: 5 5 class Datasources,External_dependencies,Targets external
last_updated
)Une source depuis laquelle le serveur va collecter des données. Cela peut être tout ce qui contient des données : base de données, annuaire LDAP, service Web, fichier plat…
Un plugin Hermes chargé de collecter des données à partir d’un type de source de données spécifique et de les fournir au serveur.
application hermes-server : interroge les sources de données à intervalles réguliers et convertit tous les changements entre les données récentes et les données précédentes en événements qui seront envoyés sur un bus de messages par un plugin producteur de bus de messages.
Service externe comme Apache Kafka ou RabbitMQ qui collecte les événements du serveur et les fournit aux clients dans le même ordre que celui où ils ont été émis.
Un plugin Hermes exécuté par le serveur, chargé d’émettre des événements sur un type de bus de messages spécifique.
Un plugin Hermes exécuté par les clients, chargé de consommer des événements depuis un type de bus de messages spécifique.
application hermes-client : consomme les événements du bus de messages via le plugin consommateur de bus de messages et appelle les méthodes appropriées implémentées sur le plugin client pour propager les changements de données sur la cible.
Si activée dans la configuration, le client ne supprimera pas immédiatement les données mais les stockera dans la corbeille pendant le nombre de jours configuré. Si les données sont ajoutées à nouveau durant ce délai, le client les restaurera depuis la corbeille. Sinon, une fois la limite de rétention de la corbeille atteinte, les données seront supprimées.
Selon l’implémentation choisie sur le plugin client, cela peut permettre de nombreux scénarios, e.g. désactiver un compte ou le garder actif pendant une période de grâce.
Lorsqu’une exception est levée lors du traitement d’un événement sur le plugin client, l’événement est stocké dans une file d’erreurs. Tous les événements suivants concernant les mêmes objets de données ne seront pas traités mais stockés dans la file d’erreurs jusqu’à ce que le premier soit traité avec succès. Le traitement des événements dans la file d’attente d’erreurs est relancé périodiquement.
Parfois, un événement peut être stocké dans la file d’erreurs en raison d’un problème de données (e.g. un nom de groupe avec un point terminal génèrera une erreur sur Active Directory). Si le point terminal est ensuite supprimé du nom de groupe sur la source de données, l’événement modified
sera stocké dans la file d’erreurs et ne sera pas traité tant que le précédent n’aura pas été traité, ce qui ne pourra jamais se produire sans procéder à une opération risquée et non-souhaitable : l’édition manuelle du fichier cache du client.
L’auto remédiation résout ce type de problèmes en fusionnant les événements d’un même objet dans la file d’erreurs. Elle n’est pas activée par défaut, car elle peut perturber l’ordre de traitement normal des événements.
Un plugin Hermes exécuté par le client, chargé d’implémenter des méthodes simples de traitement d’événements visant à propager les changements de données vers un type de cible spécifique.
Un plugin Hermes exécuté par le serveur ou le client qui sera utilisable comme un nouveau filtre Jinja, permettant de modifier des données.
Un client ne peut pas commencer à traiter de nouveaux événements en toute sécurité sans disposer de l’ensemble du jeu de données complet préalable. Ainsi, le serveur est capable d’envoyer une séquence d’événements spécifique appelée initsync
qui contiendra le modèle de données serveur et l’ensemble de données. Un client déjà initialisé l’ignorera silencieusement, mais un client non initialisé le traitera pour initialiser sa cible en ajoutant toutes les entrées fournies par la séquence initsync, puis commencera ensuite à traiter normalement les événements suivants.
Comme il existe des différences entre eux, veuillez consulter modèle de données serveur et modèle de données client.
Également nommé “type d’objet”. Un type de données avec son mapping d’attributs qui sera géré par Hermes.
L’attribut type de données qui permet de distinguer une entrée d’une autre. Il doit évidemment être unique.
Configuration des types de données que le serveur doit gérer, avec leurs mapping d’attributs respectifs. Le nom de l’attribut distant est le nom de l’attribut utilisé sur la source de données.
Le modèle de données du serveur est construit en spécifiant les éléments suivants :
Définit le comportement si un même attribut obtient des valeurs différentes sur différentes sources de données.
Permet de déclarer des contraintes pour garantir la cohérence des données lors de la fusion des données, lorsque le serveur interroge plusieurs sources de données.
Permet de déclarer des clés étrangères dans un type de données, que les clients utiliseront pour appliquer leur politique de clés étrangères. Voir Clés étrangères pour plus de détails.
Permet de déclarer des contraintes entre plusieurs types de données pour assurer la cohérence des données.
Attributs du modèle de données qui seront uniquement stockés dans le cache, mais ne seront ni envoyés dans les événements, ni utilisés lors de la comparaison avec le cache.
Attributs du modèle de données qui contiendront des données sensibles, comme des mots de passe, et ne doivent jamais être stockés dans le cache ni affichés dans les journaux. Ils seront tout de même envoyés aux clients, à moins qu’ils ne soient également définis comme attributs locaux.
Comme ces attributs ne sont pas mis en cache, ils seront considérés comme ajoutés CHAQUE fois que le serveur interrogera les sources de données.
Attributs du modèle de données qui ne seront pas envoyés dans les événements, mis en cache ni utilisés lors de la comparaison avec le cache, mais qui pourront être utilisés dans les templates Jinja.
Configuration des types de données que le client doit gérer, avec leur mapping d’attributs. Le nom de l’attribut distant est le nom de l’attribut utilisé sur le modèle de données du serveur.
Si vous vous demandez pourquoi ce mapping est nécessaire, voici pourquoi :
Le modèle de données client est construit en spécifiant les éléments suivants :
hermesType
Également appelé “attrsmapping”. Mapping (clé/valeur) qui associe le nom de l’attribut interne (en tant que clé) à celui distant (en tant que valeur). Le distant peut être un modèle Jinja pour transformer des données avec des filtres Jinja et des plugins d’attribut.
Explications sur le fonctionnement ou la structure de certains composants clés.
Explications sur le fonctionnement ou la structure de certains composants clés d’hermes-server.
hermes-server
commit_one
du modèle de données le cas échéantcommit_all
du modèle de données le cas échéantupdateInterval
et recommence à partir de l’étape 3.
si l’application n’a pas été invitée à s’arrêterSi une exception est levée à l’étape 2.
, cette étape est recommencée jusqu’à ce qu’elle réussisse.
Si une exception est levée aux étapes 3.
à 7.
, le cache est enregistré sur le disque et le serveur recommence à partir de l’étape 3.
.
Hermes-server peut gérer plusieurs types de données, avec des liens (clés étrangères) entre eux, et peut leur appliquer des contraintes d’intégrité.
Illustrons cela avec un cas d’utilisation typique d’utilisateurs / groupes / membres de groupes.
classDiagram direction BT GroupsMembers <-- Users GroupsMembers <-- Groups class Users{ user_id ... } class Groups{ group_id ... } class GroupsMembers{ user_id group_id integrity() _SELF.user_id in Users_pkeys and _SELF.group_id in Groups_pkeys }
Dans ce scénario, les entrées dans GroupsMembers
qui ont un user_id
qui n’existe pas dans Users
, ou un group_id
qui n’existe pas dans Groups
seront ignorées silencieusement.
Pour plus de détails, veuillez consulter integrity_constraints dans la configuration d’hermes-server.
Dans un scénario multi-sources, Hermes peut aggréger les entrées provenant de plusieurs sources comme si elles provenaient d’une seule, et éventuellement appliquer des contraintes d’agrégation pour garantir la cohérence des données.
Prenons un cas d’utilisation, avec un ensemble de données universitaires utilisées par Hermes pour gérer les comptes utilisateurs. Les données du personnel et des étudiants sont stockées sur deux sources de données distinctes. Hermes pourra fusionner les deux sources de données dans un seul type Users
virtuel, tout en s’assurant qu’il n’y ait pas de collision entre les clés primaires.
Ici, nous avons deux sources de données distinctes pour un même type de données.
classDiagram direction BT Users <|-- Users_employee Users <|-- Users_students class Users{ user_id login mail merge_constraints() s.user_id mustNotExist in e.user_id } class Users_students{ s.user_id s.login s.mail } class Users_employee{ e.user_id e.login e.mail }
Dans ce scénario, les entrées dans Users_students
qui ont un user_id
qui existe dans Users_employee
seront ignorées silencieusement.
Mais les entrées dans Users_employee
qui ont un user_id
qui existe dans Users_students
seront toujours traitées.
Pour plus de détails, veuillez consulter pkey_merge_constraint et merge_constraints dans la configuration d’hermes-server.
Dans un scénario multi-sources, Hermes peut recomposer des entrées provenant de plusieurs sources en fusionnant leurs données et en définissant éventuellement des contraintes de fusion pour assurer la cohérence des données.
Prenons un cas d’utilisation, où Hermes gère des comptes utilisateurs. Les données principales et le nom du profil wifi sont stockés sur deux sources de données distinctes. Hermes pourra agréger les deux sources de données dans un seul objet Users
virtuel, tout en s’assurant que les clés primaires de la seconde source existent dans la première.
Ici, nous avons deux sources de données distinctes pour une même entrée.
classDiagram direction BT Users <|-- Users_main Users <|-- Users_auxiliary class Users{ user_id login mail wifi_profile merge_constraints() a.user_id mustAlreadyExist in m.user_id } class Users_auxiliary{ a.user_id a.wifi_profile } class Users_main{ m.user_id m.login m.mail }
Dans ce scénario, les entrées dans Users_auxiliary
qui ont un user_id
qui n’existe pas dans Users_main
seront ignorées silencieusement.
Mais les entrées dans Users_main
qui ont un user_id
qui n’existe pas dans Users_auxiliary
seront traitées, et donc l’entrée Users
résultante n’aura pas de valeur wifi_profile
.
Pour plus de détails, veuillez consulter pkey_merge_constraint et merge_constraints dans la configuration d’hermes-server.
Un événement appartient toujours à l’une de ces catégories :
base
: événement standard, peut être de type :
dataschema
: propage le nouveau schéma de données aux clients, après une mise à jour du modèle de données du serveuradded
: une nouvelle entrée a été ajoutée au type de données spécifié, avec des attributs et des valeurs spécifiésremoved
: l’entrée de la clé primaire spécifiée a été supprimée du type de données spécifiémodified
: l’entrée de la clé primaire spécifiée a été modifiée. Contient uniquement les attributs ajoutés, modifiés et supprimés avec leurs nouvelles valeursinitsync
: indique que l’événement fait partie d’une séquence initsync, peut être de type :
init-start
: début d’une séquence initsync, contient également le schéma de données actueladded
: une nouvelle entrée a été ajoutée au type de données spécifié, avec les attributs et les valeurs spécifiés. Lorsque le serveur envoie le contenu de son cache pour initialiser les clients, les entrées ne peuvent qu’être ajoutéesinit-stop
: fin d’une séquence initsyncContient l’état du serveur :
lastUpdate: datetime.datetime | None
Date et heure de la dernière mise à jour.
errors: dict[str, dict[str, dict[str, Any]]]
Dictionnaire contenant les erreurs actuelles, pour pouvoir notifier de tout changement.
exception: str | None
Chaîne contenant la dernière trace d’exception.
Contient le schéma de données, construit depuis le modèle de données. Ce fichier cache permet au serveur de traiter l’étape 2.
de Workflow.
Un fichier par type de données déclaré dans Datamodel, contenant le cache des données de ce type de données, sous forme de liste de dictionnaires. Chaque dictionnaire de la liste représente une entrée.
Explications sur le fonctionnement ou la structure de certains composants clés d’hermes-client.
hermes-client
5.
7.
n’a jamais été atteinte) :
errorQueue_retryInterval
est écoulé depuis la dernière tentative, réessaye de traiter les événements présents dans la file d’erreurstrashbin_purgeInterval
est écoulé depuis la dernière tentative, purge les objets expirés de la corbeilleonSave
lorsqu’elle existe dans le plugin client5.
si l’application n’a pas été invitée à s’arrêterSi une exception est levée à l’étape 6.1.1
, elle sera considérée comme une erreur fatale, notifiée et le client s’arrêtera.
Si une exception est levée aux étapes 5.
à 6.
, elle est notifiée, son événement est ajouté à la file d’erreurs et le client redémarre à partir de l’étape 7.
.
Comme le modèle de données sur le serveur diffère de celui sur le client, les clients doivent convertir les événements distants reçus sur le bus de messages en événements locaux. Si l’événement local résultant est vide (le type de données ou les attributs modifiés dans l’événement distant ne sont pas définis sur le modèle de données client), l’événement est ignoré.
Lors de la mise à jour du modèle de données client, le client peut générer des événements locaux qui n’ont pas d’événement distant correspondant, i.e. pour mettre à jour une valeur d’attribut calculée avec un modèle Jinja qui vient d’être modifié.
flowchart TB subgraph Hermes-client direction TB datamodelUpdate[["mise à jour du modèle de données"]] remoteevent["Événement distant"] localevent["Événement local"] eventHandler(["Méthode de gestion de l'événement du plugin client"]) end datamodelUpdate-->|génère|localevent MessageBus["Bus de message"]-->|fournit|remoteevent remoteevent-->|convertit en|localevent localevent-->|passe à la bonne|eventHandler eventHandler-->|traite|Target["Cible"] classDef external fill:#fafafa,stroke-dasharray: 5 5 class MessageBus,Target external
Il arrive que les objets soient liés entre eux par des clés étrangères. Lorsqu’une erreur survient sur un objet dont la clé primaire fait référence à celle d’un ou plusieurs autres objets “parents”, il peut être souhaitable d’interrompre le traitement de tout ou partie des événements de ces objets parents jusqu’à ce que ce premier événement ait été correctement traité. Cela peut être réalisé en ajoutant les événements des objets parents à la file d’attente des erreurs au lieu de tenter de les traiter.
La première chose à faire est de déclarer les clés étrangères via hermes-server.datamodel.data-type-name.foreignkeys dans la configuration d’hermes-server. Le serveur ne fera rien d’autre avec ces clés étrangères que de les propager aux clients.
Ensuite, il faut définir la politique à appliquer aux clients via hermes-client.foreignkeys_policy dans chaque configuration hermes-client. Il y en a trois possibles :
disabled
: Aucun événement, la politique est désactivée. Probablement pas pertinent dans la plupart des cas, mais pourrait peut-être être utile à quelqu’un un jour.on_remove_event
: Uniquement sur les événements removed. Devrait suffire dans la plupart des cas.on_every_event
: Sur tous les types d’événements (added, modified, removed). Pour assurer une cohérence parfaite quoi qu’il arrive.Parfois, un événement peut être stocké dans la file d’erreurs en raison d’un problème de données (e.g. un nom de groupe avec un point terminal génère une erreur sur Active Directory). Si le point terminal est ensuite supprimé du nom de groupe sur la source de données, l’événement modified sera stocké dans la file d’erreurs et ne sera pas traité tant que le précédent n’aura pas été traité, ce qui ne pourra jamais se produire sans procéder à une opération risquée et non-souhaitable : l’édition manuelle du fichier cache du client.
L’auto remédiation résout ce type de problèmes en fusionnant les événements d’un même objet dans la file d’erreurs. Elle n’est pas activée par défaut, car elle peut perturber l’ordre de traitement normal des événements.
Prenons comme exemple un groupe créé avec un nom invalide. Comme son nom est invalide, le traitement de sa création échouera et l’événement sera stocké dans la file d’erreurs comme ceci :
flowchart TB subgraph errorqueue [File d'erreurs] direction TB ev1 end ev1["`**événement 1** *eventtype*: added *objType*: ADGroup *objpkey*: 42 *objattrs*: { grp_pkey: 42 name: 'NomInvalide.' desc: 'Demo group' }`"] classDef leftalign text-align:left class ev1 leftalign
Comme l’erreur a été notifiée, quelqu’un corrige le nom du groupe dans la source de données. Cette modification entraîne un événement modified correspondant. Cet événement modified ne sera pas traité, mais ajouté à la file d’erreurs puisque son objet a déjà un événement dans la file d’erreurs.
flowchart TB subgraph errorqueuebis [avec auto remédiation] direction TB ev1bis end subgraph errorqueue [Sans auto remédiation] direction TB ev1 ev2 end ev1["`**événement 1** *eventtype*: added *objType*: ADGroup *objpkey*: 42 *objattrs*: { grp_pkey: 42 name: 'NomInvalide.' desc: 'Demo group' }`"] ev2["`**événement 2** *eventtype*: modified *objType*: ADGroup *objpkey*: 42 *objattrs*: { modified: { name: 'NomValide' } }`"] ev1bis["`**événement 1** *eventtype*: added *objType*: ADGroup *objpkey*: 42 *objattrs*: { grp_pkey: 42 name: 'NomValide' desc: 'Demo group' }`"] classDef leftalign text-align:left class ev1,ev2,ev1bis leftalign
Contient l’état du client :
queueErrors: dict[str, str]
Dictionnaire contenant l’ensemble des messages d’erreurs des objets en file d’erreurs, pour pouvoir notifier de tout changement.
datamodelWarnings: dict[str, dict[str, dict[str, Any]]]
Dictionnaire contenant les avertissements actuels du modèle de données, pour pouvoir notifier de tout changement.
exception: str | None
Chaîne contenant la dernière trace d’exception.
initstartoffset: Any | None
Contient l’offset du premier message de la séquence initSync sur le bus de messages.
initstopoffset: Any | None
Contient l’offset du dernier message de la séquence initSync sur le bus de messages.
nextoffset: Any | None
Contient l’offset du prochain message à traiter sur le bus de messages.
Cache de la configuration précédente, utilisé pour pouvoir construire le modèle de données précédent et pour générer les expressions Jinja avec les plugins d’attributs.
Cache du dernier schéma de données, reçu d’hermes-server.
Cache de la file d’erreurs.
Un fichier par type de données distantes, contenant toutes les entrées distantes, telles qu’elles ont été traitées par le client sans générer d’erreur.
Lorsque la file d’erreurs est vide, doit avoir le même contenu que RemoteDataType_complete__.json
Un fichier par type de données distantes, contenant toutes les entrées distantes, telles qu’elles sont sur le serveur.
Lorsque la file d’erreurs est vide, doit avoir le même contenu que RemoteDataType.json
Uniquement si la corbeille est activée. Un fichier par type de données distant, contenant toutes les entrées distantes qui se trouvent dans la corbeille, telles qu’elles ont été traitées par le client sans générer d’erreur.
Lorsque la file d’erreurs est vide, doit avoir le même contenu que trashbin_RemoteDataType_complete__.json
Uniquement si la corbeille est activée. Un fichier par type de données distant, contenant toutes les entrées distantes qui se trouvent dans la corbeille, telles qu’elles sont sur le serveur.
Lorsque la file d’erreurs est vide, doit avoir le même contenu que trashbin_RemoteDataType.json
Un fichier par type de données local, contenant toutes les entrées locales, telles qu’elles ont été traitées par le client sans générer d’erreur.
Lorsque la file d’erreurs est vide, doit avoir le même contenu que __LocalDataType_complete__.json
Un fichier par type de données local, contenant toutes les entrées locales, telles qu’elles sont sur le serveur.
Lorsque la file d’erreurs est vide, doit avoir le même contenu que __LocalDataType.json
Uniquement si la corbeille est activée. Un fichier par type de données local, contenant toutes les entrées locales qui se trouvent dans la corbeille, telles qu’elles ont été traitées par le client sans générer d’erreur.
Lorsque la file d’erreurs est vide, doit avoir le même contenu que __trashbin_LocalDataType_complete__.json
Uniquement si la corbeille est activée. Un fichier par type de données local, contenant toutes les entrées locales qui se trouvent dans la corbeille, telles qu’elles sont sur le serveur.
Lorsque la file d’erreurs est vide, doit avoir le même contenu que __trashbin_LocalDataType.json