scripts/gestion/trigger/services/event.py
2014-08-01 23:22:19 +02:00

188 lines
6.1 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 cmb
import cPickle
import pika
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, record_parser
# 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.services.%s" % (config_service,)))
except Exception as e:
logger.critical("Fatal : import of %s failed, see following traceback. %s", config_service, traceback.format_exc())
class EventProducer(cmb.BasicProducer):
"""
EventProducer tracker
"""
def __init__(self, app_id):
"""Extended
"""
logger.info("Starting trigger EventProducer program for app %s", app_id)
trigger_password = secrets.get('rabbitmq_trigger_password')
credentials = pika.PlainCredentials(trigger_config.user, trigger_password)
super(EventProducer, self).__init__(url=trigger_config.master, exchange_name="trigger", app_id=app_id, port=trigger_config.port, credentials=credentials, ssl=trigger_config.ssl)
self._connection = self.connect()
self.get_chan()
def send_message(self, routing_key, body):
"""Sends basic message with app_id and body
"""
try:
logger.info("Sending message %s with routing_key %s.", body, routing_key)
body = cPickle.dumps(body)
self._channel.basic_publish(exchange=self._exchange_name,
routing_key=routing_key,
body=body,
properties=pika.BasicProperties(
delivery_mode=2,
app_id=self._app_id,
))
except:
print "Failure in trigger.event"
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 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 3-tuple, containing LDAP dn, the former state of the object
(a simple dict), and the later state. 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])]))
msg_to_send = [function(body, diff) for function in functions]
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 = EventProducer("civet")
sender.send_message("trigger.%s" % (routing_key,), body)