[trigger] Refactorisation (voir détails) - On passe aux tests

* Pour une plus grande modularité, event a été refactorisé, ce qui
 a impliqué de réécrire le fonctionnement des services.
 * Maintenant, y a plus qu'à tester.
This commit is contained in:
Pierre-Elliott Bécue 2014-07-13 01:48:17 +02:00
parent 3d98882755
commit d29343392b
7 changed files with 283 additions and 130 deletions

View file

@ -7,16 +7,33 @@
# License : GPLv3
# Date : 18/05/2014
"""
This service (event) is designed to receive any modification done on LDAP
database, and to make a correct diff between former and later object in order
to guess which services has to be updated.
"""
import cmb
import cPickle
import gestion.config.trigger as trigger_config
from gestion.trigger.host import record
import cranslib.clogger as clogger
import pika
import importlib
import itertools
# Trigger features
import gestion.config.trigger as trigger_config
from gestion.trigger.host import record, TriggerFactory
from gestion.trigger.services.service import BasicService
# Clogger
import cranslib.clogger as clogger
# lc_ldap
import lc_ldap.attributs
logger = clogger.CLogger("trigger.event", "info")
services = [importlib.import_module("gestion.trigger.services.%s" % (config_service,)) for config_service in trigger_config.all_services]
class Event(cmb.BasicProducer):
"""
Event tracker
@ -49,13 +66,16 @@ class Event(cmb.BasicProducer):
raise
def announce(self, body):
"""Feature to send message without giving routing_key
"""
self.send_message("trigger.event", body)
def diff_o_matic(body=()):
"""Fait un diff exhaustif des deux dicos"""
if not body:
raise("diff_o_matic received %r as an argument, which is unusable." % (body,))
raise ValueError("diff_o_matic received %r as an argument, which is unusable." % (body,))
before = dict(body[1]) or {}
after = dict(body[2]) or {}
@ -95,7 +115,6 @@ def compare_lists(list1, list2):
"""
moins, plus = [], []
llist2 = [a.lower() for a in list2]
for elem in [] + list1:
try:
ind = list2.index(elem.lower())
@ -109,67 +128,63 @@ def compare_lists(list1, list2):
return moins, plus
@record
def event(body=()):
"""Trigger event qui transcrit toute modif ldap en truc exploitable par
trigger. Warning, bootstrap incoming.
body est exceptionnellement un tuple. Pour être précis, un 3-tuple.
Le premier élément est le dn de l'objet LDAP, il est pas indispensable.
Le deuxième est un dico qui recense l'état complet de l'objet modifié avant
validation des modifications.
Le troisième est un dico qui recense l'état complet de l'objet modifié après
modification.
Si l'objet vient d'être créé, le deuxième élément est None.
Si l'objet vient d'être supprimé, le troisième élément vaut None.
Il faut donc faire un diff, générer la liste des triggers à envoyer, puis
les envoyer.
class event(BasicService):
"""Event service class. It extends BasicService, but should not implement
any change trigger, since it's this service which is designed to call
change triggers of other services.
"""
logger.info("Received message %r", body)
@classmethod
def get_changes(cls, body, diff):
"""Compute changes from diff"""
diff = diff_o_matic(body)
return [None]
# À cette étape, on a un dico des attrs ayant subi une modif
# a["macAddress"] par exemple, pourrait ressembler à
# (["aa:bb:cc:dd:ee:fg"], ["aa:bb:cc:dd:ee:ff"]), la liste de gauche
# étant les trucs perdus, celle de droite ceux gagnés. Suivant le type
# des attributs, ça peut être un remplacement (mac, ip...), ou juste
# des retraits/ajouts (mailAlias...)
# Avec ça on peut trigger tout ce qu'on veut.
@classmethod
def regen(cls, body=()):
"""When any event arrives on trigger-civet-event, this method is called
and designed to transcript the body (ldap data) in something usable for
the services. Afterwards, it sends these transcripts on the good way
using routing_key.
# Si la mac ou l'IP a changé…
if diff.has_key(lc_ldap.attributs.ipHostNumber.ldap_name) or diff.has_key(lc_ldap.attributs.macAddress.ldap_name):
logger.info("Detected MAC or IP update, calling trigger_mac_ip…")
trigger_mac_ip(body, diff)
body is a 3-tuple, containing LDAP dn, the former state of the object
(a simple dict), and the later state. The data are non-binding-dependant.
def trigger_mac_ip(body, diff):
macs = tuple([body[i].get(lc_ldap.attributs.macAddress.ldap_name, [''])[0] for i in xrange(1, 3)])
ips = tuple([body[i].get(lc_ldap.attributs.ipHostNumber.ldap_name, [''])[0] for i in xrange(1, 3)])
hostnames = tuple([body[i].get(lc_ldap.attributs.host.ldap_name, [''])[0] for i in xrange(1, 3)])
A new object has body[1] to None, a deleted one has body[2] to None.
# Régénération du DHCP :
if not macs[0]:
# Création d'une nouvelle machine.
dhcp = {'add': [(macs[1], ips[1], hostnames[1])]}
fw = {'add': [(macs[1], ips[1])]}
elif not macs[1]:
# Destruction d'une machine.
dhcp = {'delete': [(macs[0], ips[0])]}
fw = {'delete': [(macs[0], ips[0])]}
else:
# Mise à jour.
dhcp = {'update': [(macs[0], ips[0], macs[1], ips[1], hostnames[1])]}
fw = {'update': [(macs[0], ips[0], macs[1], ips[1])]}
logger.info("Sending DHCP trigger with body %r", dhcp)
# XXX - Remove # when putting in production, needs further tests
#trigger_send("dhcp", dhcp)
logger.info("Sending firewall trigger for mac_ip with body %r", fw)
# XXX - Remove # when in prod, tested on 15/06/2014, functionnal.
trigger_send("firewall", ("mac_ip", fw))
logger.info("trigger_mac_ip done.")
"""
logger.info("Received message %r", body)
diff = diff_o_matic(body)
# Now, diff is a dict containing attributes which has been modified.
# diff['macAddress'] could look like (['aa:bb:cc:dd:ee:fg'], ['aa:bb:cc:dd:ee:ff']),
# where the list on the left is the former value of attributes, and the list on the
# right the latter values.
# -*- Explain -*-
#In [11]: import itertools
#
#In [12]: a = [[(3, 'lol'), ('7', 3)], [(5, 6), None], [None], [('lol', 'lal')]]
#
#In [13]: a
#Out[13]: [[(3, 'lol'), ('7', 3)], [(5, 6), None], [None], [('lol', 'lal')]]
#
#In [14]: list(set([message for message in itertools.chain(*a)]))
#Out[14]: [('7', 3), (5, 6), None, ('lol', 'lal'), (3, 'lol')] # Only one None from a, since [None, x, y, None] is equivalent for itertools to [x, y]
#
#In [15]: b = list(set([message for message in itertools.chain(*a) if message is not None]))
#
#In [16]: b
#Out[16]: [('7', 3), (5, 6), ('lol', 'lal'), (3, 'lol')]
msg_to_send = [message for message in itertools.chain(*[service.get_changes(body, diff) for service in TriggerFactory.get_services()]) if message is not None]
for msg in msg_to_send:
logger.info("Sending %r on the road \\o/", msg)
# XXX - uncomment this when in production
# trigger_send(*msg)
def trigger_send(routing_key, body):
sender = Event("civet")