scripts/gestion/trigger/services/event.py
2014-11-21 09:40:28 +01:00

154 lines
4.9 KiB
Python

#!/bin/bash /usr/scripts/python.sh
# -*- coding: utf-8 -*-
#
# Trigger library, designed to send events messages.
#
# Author : Pierre-Elliott Bécue <becue@crans.org>
# 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 importlib
import itertools
import traceback
import gestion.secrets_new as secrets
# Trigger features
import gestion.config.trigger as trigger_config
from gestion.trigger.host import TriggerFactory, record_service
from gestion.trigger.producer import EventProducer
# Clogger
import cranslib.clogger as clogger
# lc_ldap
import lc_ldap.attributs
logger = clogger.CLogger("trigger", "event", trigger_config.log_level, trigger_config.debug)
services = []
for config_service in trigger_config.all_services:
try:
services.append(importlib.import_module("gestion.trigger.parsers.%s" % (config_service,)))
except Exception as e:
logger.critical("Fatal : import of %s failed, see following traceback. %s", config_service, traceback.format_exc())
def diff_o_matic(body=()):
"""Fait un diff exhaustif des deux dicos"""
if not 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 {}
# set(dico) retourne un set de dico.keys()
keys_pool = set(before).union(set(after))
diff = {}
for key in keys_pool:
if before.has_key(key):
if not isinstance(before[key], list):
blist = [before[key]]
else:
blist = list(before[key])
else:
blist = []
if after.has_key(key):
if not isinstance(after[key], list):
alist = [after[key]]
else:
alist = list(after[key])
else:
alist = []
moins, plus = compare_lists(blist, alist)
if moins != [] or plus != []:
diff[key] = (moins, plus)
return diff
def compare_lists(list1, list2):
"""Compare deux listes, retourne deux listes, une
avec les données perdues, et une avec les données
apparues
Insensible à la casse.
"""
moins, plus = [], []
for elem in [] + list1:
try:
ind = list2.index(elem.lower())
except ValueError:
moins.append(elem)
continue
list1.remove(elem)
list2.pop(ind)
plus = plus + list2
return moins, plus
@record_service
def event(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.
body is a 5-tuple, containing timestamp, the former state of the object
(a simple dict), and the later state, a dict with additionnal (but
non-LDAP) data and a dict of step indicators (an int). The data are
non-binding-dependant.
A new object has body[1] to None, a deleted one has body[2] to None.
"""
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')]
functions = list(set([function for function in itertools.chain(*[TriggerFactory.get_parser(key) for key in diff]) if function is not None]))
# Compute the whole list of messages. This returns a list of 2-tuples. We remove None messages, which
# may occur, since there is chained-services.
msg_to_send = [msg for msg in [function(body, diff) for function in functions] if msg 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, orig=None):
sender = EventProducer("trigger.civet")
if orig is not None:
body = (body, orig)
sender.send_message("trigger.%s" % (routing_key,), body)