From 4bc4cb7abecfcfc42a5cd04ebae3df4e15530d29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre-Elliott=20B=C3=A9cue?= Date: Tue, 10 Mar 2015 21:06:16 +0100 Subject: [PATCH] =?UTF-8?q?Readme=20=C3=A0=20jour,=20et=20quelques=20modif?= =?UTF-8?q?ications=20sur=20les=20noms=20de=20variables.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gestion/trigger/parsers/dhcp.py | 10 +- gestion/trigger/parsers/firewall.py | 10 +- gestion/trigger/parsers/secours.py | 2 +- gestion/trigger/readme.fr | 172 +++++++++++++++++---------- gestion/trigger/services/dhcp.py | 18 +-- gestion/trigger/services/event.py | 14 +-- gestion/trigger/services/firewall.py | 18 +-- gestion/trigger/services/secours.py | 10 +- 8 files changed, 152 insertions(+), 102 deletions(-) diff --git a/gestion/trigger/parsers/dhcp.py b/gestion/trigger/parsers/dhcp.py index 7ebce849..0ddcbd34 100644 --- a/gestion/trigger/parsers/dhcp.py +++ b/gestion/trigger/parsers/dhcp.py @@ -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) @chaining(1) -def dhcp(ob_id, body, diff): - """Computes mac_ip data to send from body and diff +def dhcp(ob_id, operations, diff, more): + """Computes mac_ip data to send from operations and diff The dict contains lists of tuples, so we can iterate on them in the service.""" - macs = tuple([body[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)]) - hostnames = tuple([body[i].get(lc_ldap.attributs.host.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([operations[i].get(lc_ldap.attributs.ipHostNumber.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 : if not macs[0]: diff --git a/gestion/trigger/parsers/firewall.py b/gestion/trigger/parsers/firewall.py index bfa9529c..a94d136d 100644 --- a/gestion/trigger/parsers/firewall.py +++ b/gestion/trigger/parsers/firewall.py @@ -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) @chaining(0) -def send_mac_ip(ob_id, body, diff): - """Computes mac_ip data to send from body and diff +def send_mac_ip(ob_id, operations, diff, more): + """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)]) - ips = tuple([body[i].get(lc_ldap.attributs.ipHostNumber.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([operations[i].get(lc_ldap.attributs.ipHostNumber.ldap_name, [''])[0] for i in xrange(0, 2)]) # Mise à jour du parefeu mac_ip if not macs[0]: diff --git a/gestion/trigger/parsers/secours.py b/gestion/trigger/parsers/secours.py index 88fb1625..e3a49f25 100644 --- a/gestion/trigger/parsers/secours.py +++ b/gestion/trigger/parsers/secours.py @@ -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) @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 Body is a couple of two dicts (before, after) diff --git a/gestion/trigger/readme.fr b/gestion/trigger/readme.fr index 74bdd62b..d5f2295c 100644 --- a/gestion/trigger/readme.fr +++ b/gestion/trigger/readme.fr @@ -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 (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 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 - pour trigger. Le fonctionnement des services sera détaillé ci-après. + * 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. -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 -une fonction décorée avec record_service. C'est une fonction qui sera appelée quand -trigger recevra une demande sur un serveur fournissant ledit service. + * 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). -Pour que civet sache si un service doit être régénéré, et donc qu'il lui envoie -un message, il faut définir un parser. Ces parsers sont contenus dans -gestion/trigger/parsers/, et portent le nom du service associé. Ils contiennent -au moins une fonction décorée avec record_parser (dont les arguments sont des -attributs ldap à surveiller). Quand civet reçoit des modifs des bindings, il regarde -pour chaque attribut ayant changé s'ils sont surveillés par des parsers, et le cas -échéant demande la régénération des services associé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/. Il faut écrire des fonctions adaptées (le nom est libre), -par exemple, pour un parser : +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 un hash (??) -body est le message reçu par civet sans transformation. diff est le diff calculé -à la volée. Le nom de la fonction n'est pas important. Le décorateur prend les -noms d'attributs à surveiller en paramètre. La fonction doit retourner un tuple -dont le premier élément est le nom du service à régénérer (par exemple, "dhcp"), -et le second les choses que le service devra lire et gérer pour se régénérer. +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 -def dhcp(body=None): +@record_service() #équivalent à @record_service(ack=True) équivalent à +def dhcp(ob_id, operations=None): }}} -on devrait appeler "body" autrement, pour éviter de confusionner avec le body -obtenu à l'étape précédente -- Daniel -body contient le "body" construit dans un parseur. La fonction est décorée, et -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. +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. 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. -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 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. -Un service spécial -================== +Roadmap d'une modif +=================== -Le service event est celui qui utilise les parseurs pour savoir quels services -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. +Imaginons qu'on modifie la mac d'une machine dans gest_crans. -L'intérêt est d'assurer une indépendance maximale entre binding ldap et la -librairie trigger : le binding doit juste envoyer avec clef de routage -trigger.event les modifs qu'il fait, et c'est la librairie elle-même qui gère -les envois en son sein. +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. -Cela permet aussi d'avoir des définitions de services précises d'un point de vue -spécification, et une portabilité plus que correcte de trigger d'un binding vers -un autre. (les seules données ldap qui l'intéressent sont les noms des -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) +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… diff --git a/gestion/trigger/services/dhcp.py b/gestion/trigger/services/dhcp.py index 850a1ad8..195a4da2 100644 --- a/gestion/trigger/services/dhcp.py +++ b/gestion/trigger/services/dhcp.py @@ -42,31 +42,31 @@ else: ldap_conn = None @record_service() -def dhcp(ob_id, body=None): - """Regenerates dhcp service taking body into account. +def dhcp(ob_id, operations=None): + """Regenerates dhcp service taking operations into account. """ # http://satyajit.ranjeev.in/2012/01/12/python--dangerous-default-value-as-argument.html # dict are referenced. - if body is None: - body = {} + if operations is None: + operations = {} - if body and isinstance(body, dict): - for (mac, ip, name) in body.get("add", []): + if operations and isinstance(operations, dict): + for (mac, ip, name) in operations.get("add", []): logger.info("Updating DHCP db by adding %s, %s, %s", mac, ip, name) # XXX - Uncommend this when we need to start prod # 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) # XXX - Uncommend this when we need to start prod # 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) # XXX - Uncommend this when we need to start prod # delete_dhcp_host(rmac, rip) # add_dhcp_host(mac, ip, name) - elif body == True: + elif operations == True: hosts = {} host_template = """ host %(nom)s { diff --git a/gestion/trigger/services/event.py b/gestion/trigger/services/event.py index 7fb123c6..48a55494 100644 --- a/gestion/trigger/services/event.py +++ b/gestion/trigger/services/event.py @@ -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 # 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) for msg in msgs_to_send: - service_name, body, pos = msg[0], msg[1], msg[2] - LOGGER.info("[%r] Adding %r on the EventTracker", ob_id, (pos, service_name, body)) - EventTracker.record_event_to_chain(ob_id, pos, service_name, body) + service_name, operations, pos = msg[0], msg[1], msg[2] + LOGGER.info("[%r] Adding %r on the EventTracker", ob_id, (pos, service_name, operations)) + EventTracker.record_event_to_chain(ob_id, pos, service_name, operations) # Sends the first wave on the way. todo = EventTracker.get_off_record(ob_id) @@ -276,8 +276,8 @@ def event(ob_id, before, after, more): # XXX - uncomment this when in production 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""" - body = tuple([ob_id] + [body]) - PRODUCER.send_message("trigger.%s" % (routing_key,), body) + msg = tuple([ob_id] + [operations]) + PRODUCER.send_message("trigger.%s" % (routing_key,), msg) diff --git a/gestion/trigger/services/firewall.py b/gestion/trigger/services/firewall.py index ed84e11e..c5a41d3e 100644 --- a/gestion/trigger/services/firewall.py +++ b/gestion/trigger/services/firewall.py @@ -42,29 +42,29 @@ def fwrecord(fun): FwFactory.register(fun.func_name, fun) @record_service() -def firewall(ob_id, body=()): +def firewall(ob_id, operations=()): """Regens the specific service """ - if len(body) != 2: - logger.warning("Received body %r, this format is incorrect, discarding.", body) + if len(operations) != 2: + logger.warning("Received operations %r, this format is incorrect, discarding.", operations) return - (service, data) = body + (service, data) = operations logger.info("Calling service %s for data %r", service, data) # XXX - Uncomment when in prod #FwFactory.get(service)(data) @fwrecord -def mac_ip(body): +def mac_ip(operations): host_fw = firewall4.firewall() - if body and isinstance(body, dict): - for (mac, ip) in body.get("add", []): + if operations and isinstance(operations, dict): + for (mac, ip) in operations.get("add", []): logger.info("Adding mac_ip %s,%s", 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) 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) host_fw.mac_ip_remove(rmac, rip) host_fw.mac_ip_append(mac, ip) diff --git a/gestion/trigger/services/secours.py b/gestion/trigger/services/secours.py index 3739bf51..c1eada94 100644 --- a/gestion/trigger/services/secours.py +++ b/gestion/trigger/services/secours.py @@ -24,14 +24,12 @@ from gestion.trigger.host import record_service import gestion.trigger.firewall4.firewall4 as firewall4 @record_service() -def secours(ob_id, body=()): +def secours(ob_id, operations=()): """Regens the specific service """ - if len(body) != 2: - logger.warning("Received body %r, this format is incorrect, discarding.", body) + if len(operations) != 2: + logger.warning("Received operations %r, this format is incorrect, discarding.", operations) return - (service, data) = body + (service, data) = operations logger.info("Calling service %s for data %r", service, data) - # XXX - Uncomment when in prod - #FwFactory.get(service)(data)