#!/bin/bash /usr/scripts/python.sh # -*- coding: utf-8 -*- # # Trigger library, designed to send events messages. # # Author : Pierre-Elliott Bécue # 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)