#!/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 cmb import cPickle 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 """ def __init__(self, app_id): """Extended """ logger.info("Starting trigger Event program…") super(Event, self).__init__(trigger_config.master, 'trigger', app_id) 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 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. """ @classmethod def get_changes(cls, body, diff): """Compute changes from diff""" return [None] @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. 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')] 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") sender.send_message("trigger.%s" % (routing_key,), body)