178 lines
9.1 KiB
Text
178 lines
9.1 KiB
Text
Auteur : PEB <becue@crans.org>
|
||
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…
|