Auteur : PEB Date : 09/03/2015 Licence : GPLv3 What the fuck is happening? =========================== Trigger est une sorte de librairie de remplacement de generate et des services dans la base LDAP, qui fonctionnent avec bien trop de délai. Trigger est le fruit d'une longue et intelligente (quelle modestie) réflexion, et donc nous allons ici décrire son fonctionnement. Mise à jour LDAP : the fuck is happening? ========================================= Le binding envoit un tuple contenant en première entrée un hash, en deuxième entrée un dico contenant les attributs avant modif par le binding, en troisième entrée un dico contenant les attributs après modif, en quatrième entrée des données additionnelles (inchangées durant tout le processing). Le hash est utilisé pour marquer de façon « unique » un changement dans la base LDAP (la résultante d'un .save()), et permet dans la suite de se repérer dans les différentes étapes de régénération des services. Documentation succincte de trigger ================================== Tous les fichiers sont renseignés depuis /usr/scripts. * gestion/trigger/trigger.py est un script python, dont le rôle est de régénérer ponctuellement des services spécifiques, ou de tourner comme démon et de recevoir les changements effectués dans la base LDAP. Ce script se comporte de façon différente sur chaque serveur, en fonction des services qui tournent sur ceux-ci. Qu'il tourne en mode démon ou bien qu'il soit appelé ponctuellement, il commence par importer les services définis pour le serveur courant dans gestion/config/trigger.py. Ensuite, selon le type de commande qu'on lui a passé, soit il se met en écoute, soit il régénère un, ou tous les services (--service régénère le service, --all les régénère tous, --daemon ignore les autres arguments et place le programme en écoute). * gestion/trigger/services/ contient l'ensemble des services existants. Les services chargés sont listés dans gestion/config/trigger.py pour chaque hôte. Chacun des fichiers .py dans ce dossier contient une méthode décorée par la fonction record_service qui est contenue dans gestion/trigger/host.py. Cette fonction prend en argument un booléen, la variable ack, qui spécifie sur une fois le service exécuté, un ack doit être envoyé vers le serveur RabbitMQ (avec la clef de routage trigger.ack). La méthode décorée doit porter le nom du service, qui est déterminé dans les parseurs (voir ci-après). * gestion/trigger/parsers/ contient l'ensemble des parseurs existants. Il faut **nécessairement** un fichier .py par service. Comme les fichiers dans gestion/trigger/services, ces fichiers .py doivent porter le nom des services auxquels ils font référence. Ils peuvent contenir plusieurs fonctions décorées avec la fonction record_parser contenue dans gestion/trigger/host.py record_parser prend une infinité d'arguments, qui sont les attributs LDAP dont la modification doit appeler les fonctions décorées. Ces fonctions peuvent aussi être décorées avec la méthode chaining du fichier gestion/trigger/host.py (il faut d'abord décorer par chaining). chaining prend un unique argument, à savoir la position dans l'ordre de régénération pour une modif LDAP donnée. Typiquement, si on crée un home à un utilisateur, on veut d'abord appeler le service qui crée le home physiquement, puis celui qui envoit un mail de bienvenue à l'adhérent. Le second dépendant du premier, il faut mettre un indice inférieur au premier. Une fois qu'on a listé les trucs à régénérer et qu'on a créé une relation d'ordre pour les opérations dépendantes, la liste des choses à faire est stockée dans EventTracker (une Factory définie dans gestion/trigger/services/event.py) via le hash des modifs, et elle est dépilée dans l'ordre des indices (de 0 vers ...). Les messages pour un indice donné sont envoyés, puis on attend que des retours soient envoyés (les services chaînés doivent donc obligatoirement envoyer des acks, mais en fait, on évitera de mettre False pour d'autres services que event et ack). Une fois tous les acks reçus, on exécute les opérations de l'indice suivant s'il y en a et ainsi de suite jusqu'à avoir parcouru toute la liste. Ajouter un nouveau service ========================== Pour ajouter un service, il faut créer un fichier adapté dans trigger/services/, et un dans trigger/parsers/. Le nom des fichiers doit être celui des services. Il faut écrire des fonctions adaptées (le nom est libre), par exemple, pour un parser : {{{ @record_parser(lc_ldap.attributs.macAddress.ldap_name, lc_ldap.attributs.ipHostNumber.ldap_name) @chaining(0) # chaining en dessous du record_parser. def send_mac_ip(ob_id, body, diff): }}} ob_id est le hash des modifications body est le tuple composé des dicos des attributs de l'objet LDAP modifié, celui avant, et celui après modifs. Diff est le diff calculé à la volée sur les données. Les données peuvent être redontantes parfois. Les parseurs doivent *impérativement* retourner un tuple à deux élements, le premier est le nom du service à régénérer (qui matche donc le nom du fichier, ou celui de gestion/trigger/services/nom_du_service.py, ou selui de la méthode décorée avec record_service), le second est l'ensemble des opérations à faire. Chaque parseur ne peut préparer l'appel que d'une fonction. record_parser modifie la sortie des parsers en (nom_service, operations, position) en tenant compte de la position mentionnée dans chaining. Si chaining n'est pas appelé, la position par défaut est 0. Ainsi, send_mac_ip décorée en l'état retournerait (nom_service, operations, 1). Le troisième élément du tuple est enlevé par le service event quand il récupère les retours des parseurs. Pour un service, voici un exemple : {{{ @record_service() #équivalent à @record_service(ack=True) équivalent à def dhcp(ob_id, operations=None): }}} operations contient la liste des operations (généralement, un dico, avec "add", "update", et "delete" et des trucs à ajouter, mettre à jour ou retirer… Le nom de la fonction doit correspondre au nom du service (ici, dhcp). Il faut ensuite référencer le service dans config/trigger.py pour les serveurs où il est important, et relancer trigger sur ces machines (au moins civet pour le parseur, et les serveurs concernés par le service). Lors des tests, il ne faut pas hésiter à passer trigger en debug dans le fichier config/trigger.py. ###Utiliser testing.sh (en rajoutant une variable d'env pour ça), stp. -- Daniel Parmi les choses importantes, l'idéal est d'avoir des dépendances les plus paresseuses possibles d'un point de vue évaluation. Ainsi, civet qui ne fait qu'importer le fichier et utiliser les fonctions d'analyse listées dans changes_trigger peut éviter de jouer avec ce qui ne le concerne pas. Enfin, si vous avez des questions, posez-les avant, pas après. Pour chaque service, une "file d'attente" rabbitmq est créée ayant le nom trigger-nomdel'hôte-nomduservice, et une clef de routage du type trigger.nomduservice est mise en place pour que les messages envoyés vers trigger.nomduservice soient dispatchés sur l'ensemble des queues trigger-*-nomduservice. Roadmap d'une modif =================== Imaginons qu'on modifie la mac d'une machine dans gest_crans. before est le dico des données de la machine avant modif, et after celui des données de la machine après modif. Le hash (appelé ob_id ensuite) est calculé en tenant compte du timestamp actuel, et des données dans ces deux dicos. Un quatrième élément est généré : un dico (appelé more) contenant des données additionnelles, non contenues dans la base LDAP, éventuellement utiles pour la régénération. On envoie alors (ob_id, before, after, more) avec la clef de routage trigger.event, ce qui signifie que le seul service qui recevra ce message est event, sur civet. Event calcule alors un diff, qui ici ne reviendra qu'avec une mac ayant changé. Il se présente sous la forme d'un dico, avec pour clef les attributs ayant changé, et pour valeur un tuple avec la liste des attributs avant, et la liste des attributs après. Ce diff, ainsi que le couple (before, after), ob_id et more sont passé à l'ensemble des parseurs réagissant à l'une des clefs du dico diff. Ces parseurs retournent alors des tuples contenant le nom du service à régénérer, son argument operations, et une posittion. On construit alors EventTracker.event_chain[ob_id][position][nom_service] à qui on donne la valeur operations. Event lance alors la première salve de messages, à savoir ceux en position 0. Chaque message est envoyé avec comme clef de routage le nom du service à régénérer, et comme contenu la suite d'opérations à faire. Sur les serveurs concernés, les messages sont reçus, et exécutés. Enfin, un ack est envoyé si ack vaut true dans le décorateur. Ce ack permet au service ack de marquer les opérations comme faites. Une fois toutes les opérations de niveau 0 validées, ack envoie la pile des messages de niveau 1, et rebelotte…