Readme à jour, et quelques modifications sur les noms de variables.

This commit is contained in:
Pierre-Elliott Bécue 2015-03-10 21:06:16 +01:00
parent f228493399
commit 4bc4cb7abe
8 changed files with 152 additions and 102 deletions

View file

@ -17,14 +17,14 @@ from gestion.trigger.host import record_parser, chaining
@record_parser(lc_ldap.attributs.macAddress.ldap_name, lc_ldap.attributs.ipHostNumber.ldap_name) @record_parser(lc_ldap.attributs.macAddress.ldap_name, lc_ldap.attributs.ipHostNumber.ldap_name)
@chaining(1) @chaining(1)
def dhcp(ob_id, body, diff): def dhcp(ob_id, operations, diff, more):
"""Computes mac_ip data to send from body and diff """Computes mac_ip data to send from operations and diff
The dict contains lists of tuples, so we can iterate on them The dict contains lists of tuples, so we can iterate on them
in the service.""" in the service."""
macs = tuple([body[i].get(lc_ldap.attributs.macAddress.ldap_name, [''])[0] for i in xrange(0, 2)]) macs = tuple([operations[i].get(lc_ldap.attributs.macAddress.ldap_name, [''])[0] for i in xrange(0, 2)])
ips = tuple([body[i].get(lc_ldap.attributs.ipHostNumber.ldap_name, [''])[0] for i in xrange(0, 2)]) ips = tuple([operations[i].get(lc_ldap.attributs.ipHostNumber.ldap_name, [''])[0] for i in xrange(0, 2)])
hostnames = tuple([body[i].get(lc_ldap.attributs.host.ldap_name, [''])[0] for i in xrange(0, 2)]) hostnames = tuple([operations[i].get(lc_ldap.attributs.host.ldap_name, [''])[0] for i in xrange(0, 2)])
# Régénération du DHCP : # Régénération du DHCP :
if not macs[0]: if not macs[0]:

View file

@ -15,14 +15,14 @@ from gestion.trigger.host import record_parser, chaining
@record_parser(lc_ldap.attributs.macAddress.ldap_name, lc_ldap.attributs.ipHostNumber.ldap_name) @record_parser(lc_ldap.attributs.macAddress.ldap_name, lc_ldap.attributs.ipHostNumber.ldap_name)
@chaining(0) @chaining(0)
def send_mac_ip(ob_id, body, diff): def send_mac_ip(ob_id, operations, diff, more):
"""Computes mac_ip data to send from body and diff """Computes mac_ip data to send from operations and diff
Body is a couple of two dicts (before, after) operations is a couple of two dicts (before, after)
""" """
macs = tuple([body[i].get(lc_ldap.attributs.macAddress.ldap_name, [''])[0] for i in xrange(0, 2)]) macs = tuple([operations[i].get(lc_ldap.attributs.macAddress.ldap_name, [''])[0] for i in xrange(0, 2)])
ips = tuple([body[i].get(lc_ldap.attributs.ipHostNumber.ldap_name, [''])[0] for i in xrange(0, 2)]) ips = tuple([operations[i].get(lc_ldap.attributs.ipHostNumber.ldap_name, [''])[0] for i in xrange(0, 2)])
# Mise à jour du parefeu mac_ip # Mise à jour du parefeu mac_ip
if not macs[0]: if not macs[0]:

View file

@ -15,7 +15,7 @@ from gestion.trigger.host import record_parser, chaining
@record_parser(lc_ldap.attributs.macAddress.ldap_name, lc_ldap.attributs.ipHostNumber.ldap_name) @record_parser(lc_ldap.attributs.macAddress.ldap_name, lc_ldap.attributs.ipHostNumber.ldap_name)
@chaining(0) @chaining(0)
def secours(ob_id, body, diff): def secours(ob_id, body, diff, more):
"""Computes mac_ip data to send from body and diff """Computes mac_ip data to send from body and diff
Body is a couple of two dicts (before, after) Body is a couple of two dicts (before, after)

View file

@ -19,79 +19,111 @@ un dico contenant les attributs avant modif par le binding, en troisième entré
dico contenant les attributs après modif, en quatrième entrée des données additionnelles dico contenant les attributs après modif, en quatrième entrée des données additionnelles
(inchangées durant tout le processing). (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 Documentation succincte de trigger
================================== ==================================
Tous les fichiers sont renseignés depuis /usr/scripts. Tous les fichiers sont renseignés depuis /usr/scripts.
* gestion/trigger/trigger.py est un fichier python qui importe un consumer de
la librairie cmb. Il marche de manière synchrone, c'est-à-dire qu'il attend et
traîte les messages un par un. Dans gestion/config/trigger.py, il y a la liste
des services que chaque hôte gère. Ainsi, gestion/trigger/trigger.py sait, en
fonction de l'hôte sur lequel il se trouve, comment il doit se comporter, et ce
qu'il doit importer. Par exemple, sur l'hôte dhcp, le seul service présent est
dhcp, et donc trigger va aller chercher gestion/trigger/service/dhcp.py, et
travailler avec.
* gestion/trigger/trigger.py importe des services, qui sont dans le dossier
services, et eux importent une méthode depuis gestion/trigger/host.py, qui leur
permet d'enregistrer des triggers. Cette méthode permet d'aller puiser dans une
factory portant le nom TriggerFactory les références vers les services utiles.
Cela permet ensuite de les régénérer à la volée.
* Le dossier gestion/trigger/services contient la liste des services existants * gestion/trigger/trigger.py est un script python, dont le rôle est de
pour trigger. Le fonctionnement des services sera détaillé ci-après. 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.
Fonctionnement des services 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).
Un service est un fichier dans le dossier gestion/trigger/services. Il contient * gestion/trigger/services/ contient l'ensemble des services existants. Les
une fonction décorée avec record_service. C'est une fonction qui sera appelée quand services chargés sont listés dans gestion/config/trigger.py pour chaque hôte.
trigger recevra une demande sur un serveur fournissant ledit service. 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).
Pour que civet sache si un service doit être régénéré, et donc qu'il lui envoie * gestion/trigger/parsers/ contient l'ensemble des parseurs existants. Il
un message, il faut définir un parser. Ces parsers sont contenus dans faut **nécessairement** un fichier .py par service. Comme les fichiers dans
gestion/trigger/parsers/, et portent le nom du service associé. Ils contiennent gestion/trigger/services, ces fichiers .py doivent porter le nom des services
au moins une fonction décorée avec record_parser (dont les arguments sont des auxquels ils font référence. Ils peuvent contenir plusieurs fonctions
attributs ldap à surveiller). Quand civet reçoit des modifs des bindings, il regarde décorées avec la fonction record_parser contenue dans gestion/trigger/host.py
pour chaque attribut ayant changé s'ils sont surveillés par des parsers, et le cas record_parser prend une infinité d'arguments, qui sont les attributs LDAP
échéant demande la régénération des services associés. 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 Ajouter un nouveau service
========================== ==========================
Pour ajouter un service, il faut créer un fichier adapté dans trigger/services/, Pour ajouter un service, il faut créer un fichier adapté dans trigger/services/,
et un dans trigger/parsers/. Il faut écrire des fonctions adaptées (le nom est libre), et un dans trigger/parsers/. Le nom des fichiers doit être celui des services.
par exemple, pour un parser : 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) @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): def send_mac_ip(ob_id, body, diff):
}}} }}}
ob_id est un hash (??) ob_id est le hash des modifications body est le tuple composé des dicos des
body est le message reçu par civet sans transformation. diff est le diff calculé attributs de l'objet LDAP modifié, celui avant, et celui après modifs. Diff est
à la volée. Le nom de la fonction n'est pas important. Le décorateur prend les le diff calculé à la volée sur les données. Les données peuvent être redontantes
noms d'attributs à surveiller en paramètre. La fonction doit retourner un tuple parfois. Les parseurs doivent *impérativement* retourner un tuple à deux
dont le premier élément est le nom du service à régénérer (par exemple, "dhcp"), élements, le premier est le nom du service à régénérer (qui matche donc le nom
et le second les choses que le service devra lire et gérer pour se régénérer. 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 : Pour un service, voici un exemple :
{{{ {{{
@record_service @record_service() #équivalent à @record_service(ack=True) équivalent à
def dhcp(body=None): def dhcp(ob_id, operations=None):
}}} }}}
on devrait appeler "body" autrement, pour éviter de confusionner avec le body operations contient la liste des operations (généralement, un dico, avec "add",
obtenu à l'étape précédente -- Daniel "update", et "delete" et des trucs à ajouter, mettre à jour ou retirer… Le nom
body contient le "body" construit dans un parseur. La fonction est décorée, et de la fonction doit correspondre au nom du service (ici, dhcp).
son nom est stocké dans la TriggerFactory. Comme souligné précédemment, le nom
de la fonction est important, au même titre que le nom des fichiers dans
trigger/parsers et triggers/services.
Il faut ensuite référencer le service dans config/trigger.py pour les serveurs 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. Lors des tests, il ne 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. faut pas hésiter à passer trigger en debug dans le fichier config/trigger.py.
Utilises testing.sh (en rajoutant une variable d'env pour ça), stp. -- Daniel ###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 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 paresseuses possibles d'un point de vue évaluation. Ainsi, civet qui ne fait
@ -106,21 +138,41 @@ 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 soient dispatchés sur l'ensemble des queues
trigger-*-nomduservice. trigger-*-nomduservice.
Un service spécial Roadmap d'une modif
================== ===================
Le service event est celui qui utilise les parseurs pour savoir quels services Imaginons qu'on modifie la mac d'une machine dans gest_crans.
doivent être régénérés. Quand il reçoit le body, il fait un calcul des différences
entre body[1] et body[2] (les deux dicos), et fournit ces différences aux parseurs,
qui lui rendent des messages à envoyer.
L'intérêt est d'assurer une indépendance maximale entre binding ldap et la before est le dico des données de la machine avant modif, et after celui des
librairie trigger : le binding doit juste envoyer avec clef de routage données de la machine après modif. Le hash (appelé ob_id ensuite) est calculé en
trigger.event les modifs qu'il fait, et c'est la librairie elle-même qui gère tenant compte du timestamp actuel, et des données dans ces deux dicos. Un
les envois en son sein. 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.
Cela permet aussi d'avoir des définitions de services précises d'un point de vue On envoie alors (ob_id, before, after, more) avec la clef de routage
spécification, et une portabilité plus que correcte de trigger d'un binding vers trigger.event, ce qui signifie que le seul service qui recevra ce message est
un autre. (les seules données ldap qui l'intéressent sont les noms des event, sur civet.
attributs, définis dans le schéma de la base ldap, il faut donc que le binding
fournisse ses données avec les mêmes noms) 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…

View file

@ -42,31 +42,31 @@ else:
ldap_conn = None ldap_conn = None
@record_service() @record_service()
def dhcp(ob_id, body=None): def dhcp(ob_id, operations=None):
"""Regenerates dhcp service taking body into account. """Regenerates dhcp service taking operations into account.
""" """
# http://satyajit.ranjeev.in/2012/01/12/python--dangerous-default-value-as-argument.html # http://satyajit.ranjeev.in/2012/01/12/python--dangerous-default-value-as-argument.html
# dict are referenced. # dict are referenced.
if body is None: if operations is None:
body = {} operations = {}
if body and isinstance(body, dict): if operations and isinstance(operations, dict):
for (mac, ip, name) in body.get("add", []): for (mac, ip, name) in operations.get("add", []):
logger.info("Updating DHCP db by adding %s, %s, %s", mac, ip, name) logger.info("Updating DHCP db by adding %s, %s, %s", mac, ip, name)
# XXX - Uncommend this when we need to start prod # XXX - Uncommend this when we need to start prod
# add_dhcp_host(mac, ip, name) # add_dhcp_host(mac, ip, name)
for (mac, ip) in body.get("delete", []): for (mac, ip) in operations.get("delete", []):
logger.info("Updating DHCP db by deleting %s, %s", mac, ip) logger.info("Updating DHCP db by deleting %s, %s", mac, ip)
# XXX - Uncommend this when we need to start prod # XXX - Uncommend this when we need to start prod
# delete_dhcp_host(mac, ip) # delete_dhcp_host(mac, ip)
for (rmac, rip, mac, ip, name) in body.get("update", []): for (rmac, rip, mac, ip, name) in operations.get("update", []):
logger.info("Updating DHCP db by modifying %s, %s to %s, %s, %s", rmac, rip, mac, ip, name) logger.info("Updating DHCP db by modifying %s, %s to %s, %s, %s", rmac, rip, mac, ip, name)
# XXX - Uncommend this when we need to start prod # XXX - Uncommend this when we need to start prod
# delete_dhcp_host(rmac, rip) # delete_dhcp_host(rmac, rip)
# add_dhcp_host(mac, ip, name) # add_dhcp_host(mac, ip, name)
elif body == True: elif operations == True:
hosts = {} hosts = {}
host_template = """ host_template = """
host %(nom)s { host %(nom)s {

View file

@ -261,13 +261,13 @@ def event(ob_id, before, after, more):
# Compute the whole list of messages. This returns a list of 2-tuples. We remove None messages, which # Compute the whole list of messages. This returns a list of 2-tuples. We remove None messages, which
# should not occcur... But, whatever. # should not occcur... But, whatever.
msgs_to_send = [msg for msg in [function(ob_id, (before, after), diff) for function in functions] if msg is not None] msgs_to_send = [msg for msg in [function(ob_id, (before, after), diff, more) for function in functions] if msg is not None]
LOGGER.debug("[%r] in service event, messages are %r.", ob_id, msgs_to_send) LOGGER.debug("[%r] in service event, messages are %r.", ob_id, msgs_to_send)
for msg in msgs_to_send: for msg in msgs_to_send:
service_name, body, pos = msg[0], msg[1], msg[2] service_name, operations, pos = msg[0], msg[1], msg[2]
LOGGER.info("[%r] Adding %r on the EventTracker", ob_id, (pos, service_name, body)) LOGGER.info("[%r] Adding %r on the EventTracker", ob_id, (pos, service_name, operations))
EventTracker.record_event_to_chain(ob_id, pos, service_name, body) EventTracker.record_event_to_chain(ob_id, pos, service_name, operations)
# Sends the first wave on the way. # Sends the first wave on the way.
todo = EventTracker.get_off_record(ob_id) todo = EventTracker.get_off_record(ob_id)
@ -276,8 +276,8 @@ def event(ob_id, before, after, more):
# XXX - uncomment this when in production # XXX - uncomment this when in production
trigger_send(*msg) trigger_send(*msg)
def trigger_send(ob_id, routing_key, body): def trigger_send(ob_id, routing_key, operations):
"""Sends a message via civet/trigger""" """Sends a message via civet/trigger"""
body = tuple([ob_id] + [body]) msg = tuple([ob_id] + [operations])
PRODUCER.send_message("trigger.%s" % (routing_key,), body) PRODUCER.send_message("trigger.%s" % (routing_key,), msg)

View file

@ -42,29 +42,29 @@ def fwrecord(fun):
FwFactory.register(fun.func_name, fun) FwFactory.register(fun.func_name, fun)
@record_service() @record_service()
def firewall(ob_id, body=()): def firewall(ob_id, operations=()):
"""Regens the specific service """Regens the specific service
""" """
if len(body) != 2: if len(operations) != 2:
logger.warning("Received body %r, this format is incorrect, discarding.", body) logger.warning("Received operations %r, this format is incorrect, discarding.", operations)
return return
(service, data) = body (service, data) = operations
logger.info("Calling service %s for data %r", service, data) logger.info("Calling service %s for data %r", service, data)
# XXX - Uncomment when in prod # XXX - Uncomment when in prod
#FwFactory.get(service)(data) #FwFactory.get(service)(data)
@fwrecord @fwrecord
def mac_ip(body): def mac_ip(operations):
host_fw = firewall4.firewall() host_fw = firewall4.firewall()
if body and isinstance(body, dict): if operations and isinstance(operations, dict):
for (mac, ip) in body.get("add", []): for (mac, ip) in operations.get("add", []):
logger.info("Adding mac_ip %s,%s", mac, ip) logger.info("Adding mac_ip %s,%s", mac, ip)
host_fw.mac_ip_append(mac, ip) host_fw.mac_ip_append(mac, ip)
for (mac, ip) in body.get("delete", []): for (mac, ip) in operations.get("delete", []):
logger.info("Removing mac_ip %s,%s", mac, ip) logger.info("Removing mac_ip %s,%s", mac, ip)
host_fw.mac_ip_remove(mac, ip) host_fw.mac_ip_remove(mac, ip)
for (rmac, rip, mac, ip) in body.get("update", []): for (rmac, rip, mac, ip) in operations.get("update", []):
logger.info("Updating mac_ip %s,%s with %s,%s", rmac, rip, mac, ip) logger.info("Updating mac_ip %s,%s with %s,%s", rmac, rip, mac, ip)
host_fw.mac_ip_remove(rmac, rip) host_fw.mac_ip_remove(rmac, rip)
host_fw.mac_ip_append(mac, ip) host_fw.mac_ip_append(mac, ip)

View file

@ -24,14 +24,12 @@ from gestion.trigger.host import record_service
import gestion.trigger.firewall4.firewall4 as firewall4 import gestion.trigger.firewall4.firewall4 as firewall4
@record_service() @record_service()
def secours(ob_id, body=()): def secours(ob_id, operations=()):
"""Regens the specific service """Regens the specific service
""" """
if len(body) != 2: if len(operations) != 2:
logger.warning("Received body %r, this format is incorrect, discarding.", body) logger.warning("Received operations %r, this format is incorrect, discarding.", operations)
return return
(service, data) = body (service, data) = operations
logger.info("Calling service %s for data %r", service, data) logger.info("Calling service %s for data %r", service, data)
# XXX - Uncomment when in prod
#FwFactory.get(service)(data)