Implémentation d'un gestionnaire d'événements sommaire.
This commit is contained in:
parent
6c97d6998f
commit
201377528c
12 changed files with 411 additions and 239 deletions
|
@ -19,17 +19,17 @@ ssl = True
|
||||||
# useradd : Envoie le mail de bienvenue, et crée le home
|
# useradd : Envoie le mail de bienvenue, et crée le home
|
||||||
# userdel : Détruit le home, déconnecte l'utilisateur sur zamok, détruit les indexes dovecot, désinscrit l'adresse crans des mailing listes associées
|
# userdel : Détruit le home, déconnecte l'utilisateur sur zamok, détruit les indexes dovecot, désinscrit l'adresse crans des mailing listes associées
|
||||||
services = {
|
services = {
|
||||||
'civet' : ["event"],
|
'civet' : ["event", "ack"],
|
||||||
'dhcp' : ["dhcp"],
|
'dhcp' : ["dhcp"],
|
||||||
'dyson' : ["autostatus"],
|
'dyson' : ["autostatus"],
|
||||||
'isc' : ["dhcp"],
|
'isc' : ["dhcp"],
|
||||||
'komaz' : ["firewall", "secours"],
|
'komaz' : ["firewall", "secours"],
|
||||||
'owl' : ["userdel"],
|
'owl' : ["users"],
|
||||||
'redisdead' : ["mailman", "modif_ldap", "solde", "userdel", "secours"],
|
'redisdead' : ["mailman", "modif_ldap", "solde", "users", "secours"],
|
||||||
'sable' : ["dns"],
|
'sable' : ["dns"],
|
||||||
'titanic' : ["secours"],
|
'titanic' : ["secours"],
|
||||||
'zamok' : ["userdel"],
|
'zamok' : ["users"],
|
||||||
'zbee' : ["useradd", "userdel"],
|
'zbee' : ["users"],
|
||||||
}
|
}
|
||||||
|
|
||||||
# XXX - Uncomment this when in prod
|
# XXX - Uncomment this when in prod
|
||||||
|
|
|
@ -9,8 +9,22 @@
|
||||||
# Author : Pierre-Elliott Bécue <becue@crans.org>
|
# Author : Pierre-Elliott Bécue <becue@crans.org>
|
||||||
# License : GPLv3
|
# License : GPLv3
|
||||||
# Date : 28/04/2014
|
# Date : 28/04/2014
|
||||||
|
"""This module provides host functions for trigger, such as the TriggerFactory which
|
||||||
|
stores parsers and services metadata.
|
||||||
|
"""
|
||||||
|
|
||||||
import collections
|
import collections
|
||||||
|
import functools
|
||||||
|
|
||||||
|
import gestion.config.trigger as trigger_config
|
||||||
|
from gestion.trigger.producer import EventProducer
|
||||||
|
|
||||||
|
# Clogger
|
||||||
|
import cranslib.clogger as clogger
|
||||||
|
|
||||||
|
LOGGER = clogger.CLogger("trigger", "host.py/ack", trigger_config.log_level, trigger_config.debug)
|
||||||
|
|
||||||
|
PRODUCER = EventProducer("trigger.civet")
|
||||||
|
|
||||||
class TriggerFactory(object):
|
class TriggerFactory(object):
|
||||||
"""Factory containing which function is part of the trigger set
|
"""Factory containing which function is part of the trigger set
|
||||||
|
@ -22,38 +36,96 @@ class TriggerFactory(object):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def register_service(cls, key, value):
|
def register_service(cls, key, value):
|
||||||
|
"""Stores the appropriate service in the factory"""
|
||||||
cls._services[key] = value
|
cls._services[key] = value
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_service(cls, key):
|
def get_service(cls, key):
|
||||||
|
"""Retrieves the appropriate service"""
|
||||||
return cls._services.get(key, None)
|
return cls._services.get(key, None)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_services(cls):
|
def get_services(cls):
|
||||||
|
"""Retrieves the list of all services"""
|
||||||
return cls._services.values()
|
return cls._services.values()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def register_parser(cls, keys, parser):
|
def register_parser(cls, keys, parser):
|
||||||
|
"""Stores the attributes to watch and the function"""
|
||||||
for key in keys:
|
for key in keys:
|
||||||
cls._parsers[key].append(parser)
|
cls._parsers[key].append(parser)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_parser(cls, keyword):
|
def get_parser(cls, keyword):
|
||||||
|
"""Restitutes the parser using keywords"""
|
||||||
return cls._parsers[keyword]
|
return cls._parsers[keyword]
|
||||||
|
|
||||||
def record_service(func):
|
def record_service(ack=True):
|
||||||
"""Records in the triggerfactory the function
|
"""Records in the triggerfactory the function
|
||||||
|
|
||||||
The function provided are services to regen
|
The function provided are services to regen
|
||||||
|
|
||||||
"""
|
"""
|
||||||
TriggerFactory.register_service(func.func_name, func)
|
def enhance_func(func):
|
||||||
|
"""Creates an enhanced function which tests if ack is True and
|
||||||
|
creates an ack if it's the case."""
|
||||||
|
@functools.wraps(func)
|
||||||
|
def enhanced_func(*args, **kwargs):
|
||||||
|
"""Dummy"""
|
||||||
|
# The first arg is ob_id, execpt if kwargs.
|
||||||
|
if args:
|
||||||
|
__ob_id = args[0]
|
||||||
|
else:
|
||||||
|
__ob_id = kwargs['ob_id']
|
||||||
|
|
||||||
|
# The function does not return.
|
||||||
|
func(*args, **kwargs)
|
||||||
|
|
||||||
|
LOGGER.debug("[%r] Ran %r on (%r, %r)", __ob_id, func.func_name, args, kwargs, )
|
||||||
|
|
||||||
|
if ack:
|
||||||
|
# We send directly with routing key trigger.ack on the way.
|
||||||
|
# Thus, ack service does not need any parser.
|
||||||
|
routing_key = "ack"
|
||||||
|
body = (__ob_id, func.func_name)
|
||||||
|
LOGGER.debug("[%r] Ack %r.", __ob_id, body)
|
||||||
|
PRODUCER.send_message("trigger.%s" % (routing_key,), body)
|
||||||
|
TriggerFactory.register_service(func.func_name, enhanced_func)
|
||||||
|
return enhanced_func
|
||||||
|
return enhance_func
|
||||||
|
|
||||||
def trigger_service(what):
|
def trigger_service(what):
|
||||||
|
"""Calls the appropriate service"""
|
||||||
return TriggerFactory.get_service(what)
|
return TriggerFactory.get_service(what)
|
||||||
|
|
||||||
def record_parser(*args):
|
def record_parser(*args):
|
||||||
|
"""Stores the function in TriggerFactory, using args as
|
||||||
|
keys for the dict"""
|
||||||
|
|
||||||
def find_parser(func):
|
def find_parser(func):
|
||||||
TriggerFactory.register_parser(args, func)
|
"""Adds the chaining_pos at the end of the return of functions."""
|
||||||
return func
|
@functools.wraps(func)
|
||||||
|
def enhanced_func(*args, **kwargs):
|
||||||
|
"""dummy"""
|
||||||
|
__ob_id = args[0]
|
||||||
|
ret = func(*args, **kwargs)
|
||||||
|
LOGGER.debug("[%r] In record_parser.find_parser, ran %r(%r, %r). Got %r.", __ob_id, func.func_name, args, kwargs, ret)
|
||||||
|
if ret is not None:
|
||||||
|
ret = [elem for elem in ret] + [getattr(func, "chaining_pos", 0)]
|
||||||
|
LOGGER.debug("[%r] In record_parser.find_parser, for %r got chaining_pos %r", __ob_id, func.func_name, ret[-1])
|
||||||
|
return ret
|
||||||
|
TriggerFactory.register_parser(args, enhanced_func)
|
||||||
|
return enhanced_func
|
||||||
|
|
||||||
return find_parser
|
return find_parser
|
||||||
|
|
||||||
|
def chaining(pos):
|
||||||
|
"""Allows chaining of operations, by adding a position marker
|
||||||
|
on the function."""
|
||||||
|
|
||||||
|
def add_pos(func):
|
||||||
|
"""Adds the chaining_pos variable to func"""
|
||||||
|
setattr(func, "chaining_pos", pos)
|
||||||
|
return func
|
||||||
|
|
||||||
|
return add_pos
|
||||||
|
|
|
@ -16,13 +16,14 @@ import lc_ldap.attributs
|
||||||
from gestion.trigger.host import record_parser
|
from gestion.trigger.host import record_parser
|
||||||
|
|
||||||
@record_parser(lc_ldap.attributs.macAddress.ldap_name, lc_ldap.attributs.ipHostNumber.ldap_name)
|
@record_parser(lc_ldap.attributs.macAddress.ldap_name, lc_ldap.attributs.ipHostNumber.ldap_name)
|
||||||
def send_mac_ip(body, diff):
|
def send_mac_ip(ob_id, body, diff):
|
||||||
"""Computes mac_ip data to send from body and diff
|
"""Computes mac_ip data to send from body and diff
|
||||||
|
|
||||||
"""
|
The dict contains lists of tuples, so we can iterate on them
|
||||||
macs = tuple([body[i].get(lc_ldap.attributs.macAddress.ldap_name, [''])[0] for i in xrange(1, 3)])
|
in the service."""
|
||||||
ips = tuple([body[i].get(lc_ldap.attributs.ipHostNumber.ldap_name, [''])[0] for i in xrange(1, 3)])
|
macs = tuple([body[i].get(lc_ldap.attributs.macAddress.ldap_name, [''])[0] for i in xrange(0, 2)])
|
||||||
hostnames = tuple([body[i].get(lc_ldap.attributs.host.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(0, 2)])
|
||||||
|
hostnames = tuple([body[i].get(lc_ldap.attributs.host.ldap_name, [''])[0] for i in xrange(0, 2)])
|
||||||
|
|
||||||
# Régénération du DHCP :
|
# Régénération du DHCP :
|
||||||
if not macs[0]:
|
if not macs[0]:
|
||||||
|
|
|
@ -14,22 +14,24 @@ import lc_ldap.attributs
|
||||||
from gestion.trigger.host import record_parser
|
from gestion.trigger.host import record_parser
|
||||||
|
|
||||||
@record_parser(lc_ldap.attributs.macAddress.ldap_name, lc_ldap.attributs.ipHostNumber.ldap_name)
|
@record_parser(lc_ldap.attributs.macAddress.ldap_name, lc_ldap.attributs.ipHostNumber.ldap_name)
|
||||||
def send_mac_ip(body, diff):
|
def send_mac_ip(ob_id, body, diff):
|
||||||
"""Computes mac_ip data to send from body and diff
|
"""Computes mac_ip data to send from body and diff
|
||||||
|
|
||||||
|
Body is a couple of two dicts (before, after)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
macs = tuple([body[i].get(lc_ldap.attributs.macAddress.ldap_name, [''])[0] for i in xrange(1, 3)])
|
macs = tuple([body[i].get(lc_ldap.attributs.macAddress.ldap_name, [''])[0] for i in xrange(0, 2)])
|
||||||
ips = tuple([body[i].get(lc_ldap.attributs.ipHostNumber.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(0, 2)])
|
||||||
|
|
||||||
# Mise à jour du parefeu mac_ip
|
# Mise à jour du parefeu mac_ip
|
||||||
if not macs[0]:
|
if not macs[0]:
|
||||||
# Création d'une nouvelle machine.
|
# Création d'une nouvelle machine.
|
||||||
fw = {'add': [(macs[1], ips[1])]}
|
fw_dict = {'add': [(macs[1], ips[1])]}
|
||||||
elif not macs[1]:
|
elif not macs[1]:
|
||||||
# Destruction d'une machine.
|
# Destruction d'une machine.
|
||||||
fw = {'delete': [(macs[0], ips[0])]}
|
fw_dict = {'delete': [(macs[0], ips[0])]}
|
||||||
else:
|
else:
|
||||||
# Mise à jour.
|
# Mise à jour.
|
||||||
fw = {'update': [(macs[0], ips[0], macs[1], ips[1])]}
|
fw_dict = {'update': [(macs[0], ips[0], macs[1], ips[1])]}
|
||||||
return ("firewall", ("mac_ip", fw))
|
return ("firewall", ("mac_ip", fw_dict))
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ import cranslib.clogger as clogger
|
||||||
# Trigger features
|
# Trigger features
|
||||||
import gestion.config.trigger as trigger_config
|
import gestion.config.trigger as trigger_config
|
||||||
|
|
||||||
logger = clogger.CLogger("trigger", "event", trigger_config.log_level, trigger_config.debug)
|
logger = clogger.CLogger("trigger", "EventProducer", trigger_config.log_level, trigger_config.debug)
|
||||||
|
|
||||||
class EventProducer(cmb.BasicProducer):
|
class EventProducer(cmb.BasicProducer):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
Auteur : PEB <becue@crans.org>
|
Auteur : PEB <becue@crans.org>
|
||||||
Date : 14/07/2014
|
Date : 09/03/2015
|
||||||
Licence : GPLv3
|
Licence : GPLv3
|
||||||
|
|
||||||
Documentation succincte de trigger
|
What the fuck is happening?
|
||||||
==================================
|
===========================
|
||||||
|
|
||||||
Tous les fichiers sont renseignés depuis /usr/scripts.
|
|
||||||
|
|
||||||
Trigger est une sorte de librairie de remplacement de generate et des services
|
Trigger est une sorte de librairie de remplacement de generate et des services
|
||||||
dans la base LDAP, qui fonctionnent avec bien trop de délai.
|
dans la base LDAP, qui fonctionnent avec bien trop de délai.
|
||||||
|
@ -13,6 +11,18 @@ dans la base LDAP, qui fonctionnent avec bien trop de délai.
|
||||||
Trigger est le fruit d'une longue et intelligente (quelle modestie) réflexion,
|
Trigger est le fruit d'une longue et intelligente (quelle modestie) réflexion,
|
||||||
et donc nous allons ici décrire son fonctionnement.
|
et donc nous allons ici décrire son fonctionnement.
|
||||||
|
|
||||||
|
Mise à jour LDAP : the fuck is happening?
|
||||||
|
=========================================
|
||||||
|
|
||||||
|
Le binding envoit un tuple contenant en première entrée un hash, en deuxième entrée
|
||||||
|
un dico contenant les attributs avant modif par le binding, en troisième entrée un
|
||||||
|
dico contenant les attributs après modif, en quatrième entrée des données additionnelles
|
||||||
|
(inchangées durant tout le processing).
|
||||||
|
|
||||||
|
Documentation succincte de trigger
|
||||||
|
==================================
|
||||||
|
|
||||||
|
Tous les fichiers sont renseignés depuis /usr/scripts.
|
||||||
* gestion/trigger/trigger.py est un fichier python qui importe un consumer de
|
* gestion/trigger/trigger.py est un fichier python qui importe un consumer de
|
||||||
la librairie cmb. Il marche de manière asynchrone, c'est-à-dire qu'il attend et
|
la librairie cmb. Il marche de manière asynchrone, c'est-à-dire qu'il attend et
|
||||||
traîte les messages un par un. Dans gestion/config/trigger.py, il y a la liste
|
traîte les messages un par un. Dans gestion/config/trigger.py, il y a la liste
|
||||||
|
@ -21,10 +31,11 @@ et donc nous allons ici décrire son fonctionnement.
|
||||||
qu'il doit importer. Par exemple, sur l'hôte dhcp, le seul service présent est
|
qu'il doit importer. Par exemple, sur l'hôte dhcp, le seul service présent est
|
||||||
dhcp, et donc trigger va aller chercher gestion/trigger/service/dhcp.py, et
|
dhcp, et donc trigger va aller chercher gestion/trigger/service/dhcp.py, et
|
||||||
travailler avec.
|
travailler avec.
|
||||||
* gestion/trigger/trigger.py importe une méthode trigger depuis
|
* gestion/trigger/trigger.py importe des services, qui sont dans le dossier
|
||||||
gestion/trigger/host.py. Cette méthode permet d'aller puiser dans une factory
|
services, et eux importent une méthode depuis gestion/trigger/host.py, qui leur
|
||||||
portant le nom TriggerFactory les références vers les services utiles. Cela
|
permet d'enregistrer des triggers. Cette méthode permet d'aller puiser dans une
|
||||||
permet ensuite de les régénérer à la volée.
|
factory portant le nom TriggerFactory les références vers les services utiles.
|
||||||
|
Cela permet ensuite de les régénérer à la volée.
|
||||||
|
|
||||||
* Le dossier gestion/trigger/services contient la liste des services existants
|
* Le dossier gestion/trigger/services contient la liste des services existants
|
||||||
pour trigger. Le fonctionnement des services sera détaillé ci-après.
|
pour trigger. Le fonctionnement des services sera détaillé ci-après.
|
||||||
|
@ -32,56 +43,56 @@ et donc nous allons ici décrire son fonctionnement.
|
||||||
Fonctionnement des services
|
Fonctionnement des services
|
||||||
===========================
|
===========================
|
||||||
|
|
||||||
"Un service est une classe qui ne sera jamais instanciée"
|
Un service est un fichier dans le dossier gestion/trigger/services. Il contient
|
||||||
|
une fonction décorée avec record_service. C'est une fonction qui sera appelée quand
|
||||||
|
trigger recevra une demande sur un serveur fournissant ledit service.
|
||||||
|
|
||||||
Un service est la donnée dans un fichier d'une classe portant le nom du fichier
|
Pour que civet sache si un service doit être régénéré, et donc qu'il lui envoie
|
||||||
(et donc du service). La casse dans le nom de la classe n'importe pas. Cette
|
un message, il faut définir un parser. Ces parsers sont contenus dans
|
||||||
classe hérite de BasicService, une classe définie dans
|
gestion/trigger/parsers/, et portent le nom du service associé. Ils contiennent
|
||||||
gestion/trigger/services/service.py. Cette classe s'appuie sur la métaclasse
|
au moins une fonction décorée avec record_parser (dont les arguments sont des
|
||||||
MetaService pour se construire, ce qui permet d'établir un certain nombre de
|
attributs ldap à surveiller). Quand civet reçoit des modifs des bindings, il regarde
|
||||||
liens entre les méthodes d'une classe représentant un service et des attributs
|
pour chaque attribut ayant changé s'ils sont surveillés par des parsers, et le cas
|
||||||
de lc_ldap que l'on souhaite monitorer. La métaclasse et l'ensemble des liens
|
échéant demande la régénération des services associés.
|
||||||
susmentionnés n'ont d'intérêt que pour la partie "transcription des modifs de la
|
|
||||||
base LDAP dans un langage compréhensible par les services".
|
|
||||||
|
|
||||||
Enfin, tout service contient une méthode regen prévue pour régénérer ledit
|
|
||||||
service.
|
|
||||||
|
|
||||||
Les services peuvent ensuite contenir autant de méthodes que souhaitées, dans la
|
|
||||||
mesure où se sont des méthodes de classe ou statiques.
|
|
||||||
|
|
||||||
La variable faisant le lien entre les attributs ldap à monitorer et les
|
|
||||||
fonctions à appeler pour transcrire les changements s'appelle changes_trigger.
|
|
||||||
C'est un dictionnaire dont les clefs sont le nom des attributs ldap à
|
|
||||||
surveiller, et les valeurs des tuples contenant les noms des fonctions à
|
|
||||||
appeler en cas de changement.
|
|
||||||
|
|
||||||
Ces fonctions devront toujours avoir le prototype suivant :
|
|
||||||
@classmethod
|
|
||||||
def toto(cls, body, diff):
|
|
||||||
où body et diff sont gérés et fournis tels quels par le service event. body est
|
|
||||||
un 3-tuple contenant le dn de l'objet ldap modifié, la liste des clefs avant
|
|
||||||
modification, et celle après. diff est un dictionnaire de différences calculé
|
|
||||||
entre body[1] et body[2].
|
|
||||||
|
|
||||||
Ajouter un nouveau service
|
Ajouter un nouveau service
|
||||||
==========================
|
==========================
|
||||||
|
|
||||||
Pour ajouter un service, il faut créer un fichier adapté dans trigger/services/,
|
Pour ajouter un service, il faut créer un fichier adapté dans trigger/services/,
|
||||||
puis, définir une classe héritant de BasicService, et respecter quelques règles
|
et un dans trigger/parsers/. Il faut écrire des fonctions adaptées (le nom est libre),
|
||||||
primordiales.
|
par exemple, pour un parser :
|
||||||
|
|
||||||
Premièrement, ce service sera importé sur chaque machine où il est configuré
|
{{{
|
||||||
pour fonctionner, et sur civet dans event.py. Pensez donc une fois le tout
|
@record_parser(lc_ldap.attributs.macAddress.ldap_name, lc_ldap.attributs.ipHostNumber.ldap_name)
|
||||||
configuré à relancer trigger sur civet, et à vérifier que ça marche. La variable
|
def send_mac_ip(body, diff):
|
||||||
de configuration debug dans gestion/config/trigger.py est là pour aider. Parmi
|
}}}
|
||||||
les choses importantes, l'idéal est d'avoir des dépendances les plus paresseuses
|
|
||||||
possibles d'un point de vue évaluation. Ainsi, civet qui ne fait qu'importer le
|
|
||||||
fichier et utiliser les fonctions d'analyse listées dans changes_trigger peut
|
|
||||||
éviter de jouer avec ce qui ne le concerne pas.
|
|
||||||
|
|
||||||
Ensuite, il faut absolument une méthode regen, et définir changes_trigger. (un
|
body est le message reçu par civet sans transformation. diff est le diff calculé
|
||||||
dict vide convient)
|
à la volée. Le nom de la fonction n'est pas important. Le décorateur prend les
|
||||||
|
noms d'attributs à surveiller en paramètre. La fonction doit retourner un tuple
|
||||||
|
dont le premier élément est le nom du service à régénérer (par exemple, "dhcp"),
|
||||||
|
et le second les choses que le service devra lire et gérer pour se régénérer.
|
||||||
|
|
||||||
|
Pour un service, voici un exemple :
|
||||||
|
|
||||||
|
{{{
|
||||||
|
@record_service
|
||||||
|
def dhcp(body=None):
|
||||||
|
}}}
|
||||||
|
|
||||||
|
body contient le "body" construit dans un parseur. La fonction est décorée, et
|
||||||
|
son nom est stocké dans la TriggerFactory. Comme souligné précédemment, le nom
|
||||||
|
de la fonction est important, au même titre que le nom des fichiers dans
|
||||||
|
trigger/parsers et triggers/services.
|
||||||
|
|
||||||
|
Il faut ensuite référencer le service dans config/trigger.py pour les serveurs
|
||||||
|
où il est important, et relancer trigger sur ces machines. Lors des tests, il ne
|
||||||
|
faut pas hésiter à passer trigger en debug dans le fichier config/trigger.py.
|
||||||
|
|
||||||
|
Parmi les choses importantes, l'idéal est d'avoir des dépendances les plus
|
||||||
|
paresseuses possibles d'un point de vue évaluation. Ainsi, civet qui ne fait
|
||||||
|
qu'importer le fichier et utiliser les fonctions d'analyse listées dans
|
||||||
|
changes_trigger peut éviter de jouer avec ce qui ne le concerne pas.
|
||||||
|
|
||||||
Enfin, si vous avez des questions, posez-les avant, pas après.
|
Enfin, si vous avez des questions, posez-les avant, pas après.
|
||||||
|
|
||||||
|
@ -94,11 +105,10 @@ trigger-*-nomduservice.
|
||||||
Un service spécial
|
Un service spécial
|
||||||
==================
|
==================
|
||||||
|
|
||||||
civet est un hôte spécial, qui gère un service spécial : le transcripteur. Le
|
Le service event est celui qui utilise les parseurs pour savoir quels services
|
||||||
transcripteur est le service event, dans gestion/trigger/services/event.py,
|
doivent être régénérés. Quand il reçoit le body, il fait un calcul des différences
|
||||||
qui reçoit des messages sur la queue trigger-civet-event. C'est lui qui,
|
entre body[1] et body[2] (les deux dicos), et fournit ces différences aux parseurs,
|
||||||
fonction des messages reçus, les répartis tous vers les autres queues avec
|
qui lui rendent des messages à envoyer.
|
||||||
clef de routage idoine.
|
|
||||||
|
|
||||||
L'intérêt est d'assurer une indépendance maximale entre binding ldap et la
|
L'intérêt est d'assurer une indépendance maximale entre binding ldap et la
|
||||||
librairie trigger : le binding doit juste envoyer avec clef de routage
|
librairie trigger : le binding doit juste envoyer avec clef de routage
|
||||||
|
|
50
gestion/trigger/services/ack.py
Normal file
50
gestion/trigger/services/ack.py
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
#!/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 : 10/03/2015
|
||||||
|
|
||||||
|
"""
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Trigger features
|
||||||
|
import gestion.config.trigger as trigger_config
|
||||||
|
from gestion.trigger.host import record_service
|
||||||
|
from gestion.trigger.services.event import EventTracker, trigger_send # really useful EventList ?
|
||||||
|
|
||||||
|
# Clogger
|
||||||
|
import cranslib.clogger as clogger
|
||||||
|
|
||||||
|
logger = clogger.CLogger("trigger", "ack", trigger_config.log_level, trigger_config.debug)
|
||||||
|
|
||||||
|
@record_service(ack=False)
|
||||||
|
def ack(ob_id, service_name):
|
||||||
|
"""Ack when something has been done.
|
||||||
|
|
||||||
|
Removes the acked thing from
|
||||||
|
"""
|
||||||
|
|
||||||
|
logger.info("Received message %r…", (ob_id, service_name))
|
||||||
|
|
||||||
|
todo = EventTracker.ack(ob_id, service_name)
|
||||||
|
|
||||||
|
# if todo is None, then we have finished a list, or emptied
|
||||||
|
# EventTracker's content.
|
||||||
|
if todo is None:
|
||||||
|
todo = EventTracker.get_off_record(ob_id)
|
||||||
|
logger.info("Emptied one list in the chain %r. Trying to continue. Got %r", ob_id, todo)
|
||||||
|
|
||||||
|
if todo:
|
||||||
|
for msg in todo:
|
||||||
|
logger.info("Sending %r on the road \\o/", msg)
|
||||||
|
# XXX - uncomment this when in production
|
||||||
|
trigger_send(*msg)
|
||||||
|
else:
|
||||||
|
logger.info("Aaaaaand, nothing.")
|
||||||
|
|
|
@ -42,7 +42,7 @@ else:
|
||||||
ldap_conn = None
|
ldap_conn = None
|
||||||
|
|
||||||
@record_service
|
@record_service
|
||||||
def dhcp(body=None):
|
def dhcp(ob_id, body=None):
|
||||||
"""Regenerates dhcp service taking body into account.
|
"""Regenerates dhcp service taking body into account.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
#
|
#
|
||||||
# Author : Pierre-Elliott Bécue <becue@crans.org>
|
# Author : Pierre-Elliott Bécue <becue@crans.org>
|
||||||
# License : GPLv3
|
# License : GPLv3
|
||||||
# Date : 18/05/2014
|
# Date : 10/03/2015
|
||||||
|
|
||||||
"""
|
"""
|
||||||
This service (event) is designed to receive any modification done on LDAP
|
This service (event) is designed to receive any modification done on LDAP
|
||||||
|
@ -17,8 +17,6 @@ import importlib
|
||||||
import itertools
|
import itertools
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
import gestion.secrets_new as secrets
|
|
||||||
|
|
||||||
# Trigger features
|
# Trigger features
|
||||||
import gestion.config.trigger as trigger_config
|
import gestion.config.trigger as trigger_config
|
||||||
from gestion.trigger.host import TriggerFactory, record_service
|
from gestion.trigger.host import TriggerFactory, record_service
|
||||||
|
@ -27,26 +25,145 @@ from gestion.trigger.producer import EventProducer
|
||||||
# Clogger
|
# Clogger
|
||||||
import cranslib.clogger as clogger
|
import cranslib.clogger as clogger
|
||||||
|
|
||||||
# lc_ldap
|
LOGGER = clogger.CLogger("trigger", "event", trigger_config.log_level, trigger_config.debug)
|
||||||
import lc_ldap.attributs
|
|
||||||
|
|
||||||
logger = clogger.CLogger("trigger", "event", trigger_config.log_level, trigger_config.debug)
|
PRODUCER = EventProducer("trigger.civet")
|
||||||
|
|
||||||
services = []
|
SERVICES = []
|
||||||
for config_service in trigger_config.all_services:
|
for config_service in trigger_config.all_services:
|
||||||
try:
|
try:
|
||||||
services.append(importlib.import_module("gestion.trigger.parsers.%s" % (config_service,)))
|
SERVICES.append(importlib.import_module("gestion.trigger.parsers.%s" % (config_service,)))
|
||||||
except Exception as e:
|
except Exception:
|
||||||
logger.critical("Fatal : import of %s failed, see following traceback. %s", config_service, traceback.format_exc())
|
LOGGER.critical("Fatal : import of %r failed, see following traceback. %r", config_service, traceback.format_exc())
|
||||||
|
|
||||||
def diff_o_matic(body=()):
|
class EventList(list):
|
||||||
|
"""List which is designed to grow up when one try to acces an element out of
|
||||||
|
range"""
|
||||||
|
|
||||||
|
def __fill(self, index):
|
||||||
|
"""Fills the intermediates indexes if needed"""
|
||||||
|
while len(self) <= index:
|
||||||
|
self.append({})
|
||||||
|
|
||||||
|
def __getitem__(self, index):
|
||||||
|
"""Gets the item after filling if needed"""
|
||||||
|
self.__fill(index)
|
||||||
|
return super(EventList, self).__getitem__(index)
|
||||||
|
|
||||||
|
def __setitem__(self, index, value):
|
||||||
|
"""Sets the item after filling if needed"""
|
||||||
|
self.__fill(index)
|
||||||
|
return super(EventList, self).__setitem__(index, value)
|
||||||
|
|
||||||
|
class EventTracker(object):
|
||||||
|
"""Stores events actions from event service. It allows to track all services
|
||||||
|
regeneration, and to chain services execution when needed. To avoid data loss
|
||||||
|
during process, the EventTracker duplicates its own data in a file.
|
||||||
|
|
||||||
|
This file will be synced, but, by default, RAM data is considered as the
|
||||||
|
current state of the factory. A sanity check method allows to guess if the
|
||||||
|
file should be loaded to RAM."""
|
||||||
|
|
||||||
|
event_chain = {}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def record_event_to_chain(cls, ob_id, pos, service_name, service_data):
|
||||||
|
"""Records a chain of events. args contains a tuple which arguments
|
||||||
|
is a list of dicts. ob_id is a unique identifier of the current chain.
|
||||||
|
|
||||||
|
Each dicts points to a message to send independently via trigger.
|
||||||
|
|
||||||
|
args should look like :
|
||||||
|
([("dhcp", {'update':...}, ob_id), (...., ob_id)], [...])"""
|
||||||
|
|
||||||
|
# If no entry, we create an EventList.
|
||||||
|
if ob_id not in cls.event_chain:
|
||||||
|
cls.event_chain[ob_id] = EventList()
|
||||||
|
|
||||||
|
# If service is already there, we are facing a double setting of service, which is not
|
||||||
|
# normal.
|
||||||
|
if service_name in cls.event_chain[ob_id][pos]:
|
||||||
|
LOGGER.critical("[%r] Weird. event_chain[%r][%r][%r] set to %r, but asking me to set it to %r.", ob_id, ob_id, pos, service_name, cls.event_chain[ob_id][pos][service_name], service_data)
|
||||||
|
else:
|
||||||
|
LOGGER.debug("[%r] Adding %r to EventTracker.event_chain[%r][%r][%r].", ob_id, service_data, ob_id, pos, service_name)
|
||||||
|
cls.event_chain[ob_id][pos][service_name] = service_data
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def check_empty(cls, ob_id):
|
||||||
|
"""Checks if cls.event_chain[ob_id] is empty"""
|
||||||
|
if ob_id not in cls.event_chain:
|
||||||
|
LOGGER.debug("[%r] EventTracker.cls_event_chain free of %r.", ob_id, ob_id)
|
||||||
|
return True
|
||||||
|
|
||||||
|
if len(cls.event_chain[ob_id]) == 0:
|
||||||
|
cls.event_chain.pop(ob_id)
|
||||||
|
LOGGER.debug("[%r] EventTracker.cls_event_chain free of %r.", ob_id, ob_id)
|
||||||
|
return True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_off_record(cls, ob_id):
|
||||||
|
"""Expedits a formatted record"""
|
||||||
|
# We will pop items from event_chain[ob_id]
|
||||||
|
# untill we have a non-empty dict.
|
||||||
|
if cls.check_empty(ob_id):
|
||||||
|
return []
|
||||||
|
|
||||||
|
dico = False
|
||||||
|
while not dico:
|
||||||
|
if len(cls.event_chain[ob_id]) > 0:
|
||||||
|
dico = cls.event_chain[ob_id][0]
|
||||||
|
# Should not happen.
|
||||||
|
if not dico:
|
||||||
|
cls.event_chain[ob_id].pop(0)
|
||||||
|
else:
|
||||||
|
# If we are at the end of the list
|
||||||
|
dico = True
|
||||||
|
|
||||||
|
# then, we have nothing to do.
|
||||||
|
if dico == True:
|
||||||
|
return []
|
||||||
|
|
||||||
|
if isinstance(bool, dico):
|
||||||
|
dico = {}
|
||||||
|
|
||||||
|
return [
|
||||||
|
(ob_id, service_name, service_data)
|
||||||
|
for (service_name, service_data) in dico.iteritems()
|
||||||
|
]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def ack(cls, ob_id, service_name):
|
||||||
|
"""Removes service_name from the event_chain, since
|
||||||
|
everything is ok."""
|
||||||
|
|
||||||
|
if cls.check_empty(ob_id):
|
||||||
|
LOGGER.info("[%r] Ack for %r, but nothing to ack...", ob_id, service_name)
|
||||||
|
return None
|
||||||
|
|
||||||
|
if service_name not in cls.event_chain[ob_id][0]:
|
||||||
|
LOGGER.info("[%r] Ack for %r, but nothing in event_chain[%r][0]...", ob_id, service_name, ob_id)
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Remove the service_name from the dict.
|
||||||
|
cls.event_chain[ob_id][0].pop(service_name)
|
||||||
|
|
||||||
|
# If dict is empty, we drop it.
|
||||||
|
if not cls.event_chain[ob_id][0]:
|
||||||
|
cls.event_chain[ob_id].pop(0)
|
||||||
|
return None
|
||||||
|
|
||||||
|
# If the list is empty, we drop it.
|
||||||
|
if not cls.event_chain[ob_id]:
|
||||||
|
cls.event_chain.pop(ob_id)
|
||||||
|
return None
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def diff_o_matic(before, after):
|
||||||
"""Fait un diff exhaustif des deux dicos"""
|
"""Fait un diff exhaustif des deux dicos"""
|
||||||
|
|
||||||
if not body:
|
if not before and not after:
|
||||||
raise ValueError("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." % ((before, after),))
|
||||||
|
|
||||||
before = dict(body[1]) or {}
|
|
||||||
after = dict(body[2]) or {}
|
|
||||||
|
|
||||||
# set(dico) retourne un set de dico.keys()
|
# set(dico) retourne un set de dico.keys()
|
||||||
keys_pool = set(before).union(set(after))
|
keys_pool = set(before).union(set(after))
|
||||||
|
@ -96,25 +213,28 @@ def compare_lists(list1, list2):
|
||||||
return moins, plus
|
return moins, plus
|
||||||
|
|
||||||
|
|
||||||
@record_service
|
@record_service(ack=False)
|
||||||
def event(body=()):
|
def event(ob_id, before, after, more):
|
||||||
"""When any event arrives on trigger-civet-event, this method is called
|
"""When any event arrives on trigger-civet-event, this method is called
|
||||||
and designed to transcript the body (ldap data) in something usable for
|
and designed to transcript the body (ldap data) in something usable for
|
||||||
the services. Afterwards, it sends these transcripts on the good way
|
the services. Afterwards, it sends these transcripts on the good way
|
||||||
using routing_key.
|
using routing_key.
|
||||||
|
|
||||||
body is a 5-tuple, containing timestamp, the former state of the object
|
body is a 4-tuple, containing hash, the former state of the object
|
||||||
(a simple dict), and the later state, a dict with additionnal (but
|
(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-LDAP) data. The data are non-binding-dependant.
|
||||||
non-binding-dependant.
|
|
||||||
|
|
||||||
A new object has body[1] to None, a deleted one has body[2] to None.
|
A new object has body[1] to None, a deleted one has body[2] to None.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
logger.info("Received message %r…", body)
|
LOGGER.info("[%r] Received message %r…", ob_id, (ob_id, before, after, more))
|
||||||
|
|
||||||
diff = diff_o_matic(body)
|
# Hey, I'll follow you 'till your end.
|
||||||
|
diff = diff_o_matic(before, after)
|
||||||
|
|
||||||
|
# Some debug if needed.
|
||||||
|
LOGGER.debug("[%r] in service event, diff is %r.", ob_id, diff)
|
||||||
|
|
||||||
# Now, diff is a dict containing attributes which has been modified.
|
# 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']),
|
# diff['macAddress'] could look like (['aa:bb:cc:dd:ee:fg'], ['aa:bb:cc:dd:ee:ff']),
|
||||||
|
@ -140,15 +260,23 @@ def event(body=()):
|
||||||
|
|
||||||
# Compute the whole list of messages. This returns a list of 2-tuples. We remove None messages, which
|
# 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.
|
# 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]
|
msgs_to_send = [msg for msg in [function(ob_id, (before, after), diff) for function in functions] if msg is not None]
|
||||||
|
LOGGER.debug("[%r] in service event, messages are %r.", ob_id, msgs_to_send)
|
||||||
|
|
||||||
for msg in msg_to_send:
|
for msg in msgs_to_send:
|
||||||
logger.info("Sending %r on the road \\o/", msg)
|
service_name, body, pos = msg[0], msg[1], msg[2]
|
||||||
|
LOGGER.info("[%r] Adding %r on the EventTracker", ob_id, (pos, service_name, body))
|
||||||
|
EventTracker.record_event_to_chain(ob_id, pos, service_name, body)
|
||||||
|
|
||||||
|
# Sends the first wave on the way.
|
||||||
|
todo = EventTracker.get_off_record(ob_id)
|
||||||
|
for msg in todo:
|
||||||
|
LOGGER.info("Sending %r on the road \\o/", msg)
|
||||||
# XXX - uncomment this when in production
|
# XXX - uncomment this when in production
|
||||||
trigger_send(*msg)
|
trigger_send(*msg)
|
||||||
|
|
||||||
def trigger_send(routing_key, body, orig=None):
|
def trigger_send(ob_id, routing_key, body):
|
||||||
sender = EventProducer("trigger.civet")
|
"""Sends a message via civet/trigger"""
|
||||||
if orig is not None:
|
|
||||||
body = (body, orig)
|
body = tuple([ob_id] + [elem for elem in body])
|
||||||
sender.send_message("trigger.%s" % (routing_key,), body)
|
PRODUCER.send_message("trigger.%s" % (routing_key,), body)
|
||||||
|
|
|
@ -42,7 +42,7 @@ def fwrecord(fun):
|
||||||
FwFactory.register(fun.func_name, fun)
|
FwFactory.register(fun.func_name, fun)
|
||||||
|
|
||||||
@record_service
|
@record_service
|
||||||
def firewall(body=()):
|
def firewall(ob_id, body=()):
|
||||||
"""Regens the specific service
|
"""Regens the specific service
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,96 +0,0 @@
|
||||||
#!/bin/bash /usr/scripts/python.sh
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# This module is NOT used anymore (will be buried soon).
|
|
||||||
|
|
||||||
"""
|
|
||||||
This module provides a basic service class to other services. It should *NOT*
|
|
||||||
be referenced in configuration of trigger.
|
|
||||||
|
|
||||||
It is not used anymore.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import collections
|
|
||||||
|
|
||||||
import cranslib.clogger as clogger
|
|
||||||
import gestion.config.trigger as trigger_config
|
|
||||||
from gestion.trigger.host import TriggerFactory
|
|
||||||
|
|
||||||
logger = clogger.CLogger("trigger", "service", "debug", trigger_config.debug)
|
|
||||||
|
|
||||||
class MetaService(type):
|
|
||||||
"""Metaclass designed to handle all services.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __new__(mcs, cname, cpar, cattrs):
|
|
||||||
"""Method producing the new class itself
|
|
||||||
At first, I wanted to put the changes_trigger modification in __new__,
|
|
||||||
using direct modification of cattrs['changes_trigger'] by pointing the
|
|
||||||
required methods (classmethods). The problem was that these methods were
|
|
||||||
bound at the return of type.__new__, for a reason I could not exactly
|
|
||||||
explain.
|
|
||||||
|
|
||||||
I found a workaround using __init__, so the point would be to remove
|
|
||||||
__new__, and directly use type.__new__, but this comment seems useful,
|
|
||||||
so __new__ will survive.
|
|
||||||
|
|
||||||
"""
|
|
||||||
return super(MetaService, mcs).__new__(mcs, cname, cpar, cattrs)
|
|
||||||
|
|
||||||
def __init__(cls, cname, cpar, cattrs):
|
|
||||||
"""Used to register the generated classes in TriggerFactory, and modify the behavior of
|
|
||||||
changes_trigger by pointing functions instead of their names. This allows to cancel any
|
|
||||||
positional requirement in class definition.
|
|
||||||
|
|
||||||
Do NEVER return something in __init__ function.
|
|
||||||
|
|
||||||
"""
|
|
||||||
if not cname == "BasicService":
|
|
||||||
TriggerFactory.register(cname.lower(), cls)
|
|
||||||
changes_trigger = collections.defaultdict(list)
|
|
||||||
# I love getattr
|
|
||||||
text_changes_trigger = getattr(cls, "changes_trigger", {})
|
|
||||||
for (ldap_attr_name, funcs_name) in text_changes_trigger.items():
|
|
||||||
for func_name in funcs_name:
|
|
||||||
# I really love getattr.
|
|
||||||
get = getattr(cls, func_name, None)
|
|
||||||
if get is None:
|
|
||||||
logger.critical("Fatal, bad function (%s) reference in %s.", func_name, cname)
|
|
||||||
continue
|
|
||||||
changes_trigger[ldap_attr_name].append(get)
|
|
||||||
setattr(cls, "changes_trigger", changes_trigger)
|
|
||||||
super(MetaService, cls).__init__(cname, cpar, cattrs)
|
|
||||||
|
|
||||||
class BasicService(object):
|
|
||||||
"""Basic service handler. Other services should inherit fron this one.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
__metaclass__ = MetaService
|
|
||||||
|
|
||||||
changes_trigger = {}
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_changes(cls, body, diff):
|
|
||||||
"""Looks for changes and creates messages to send back
|
|
||||||
|
|
||||||
"""
|
|
||||||
# list of all messages to send.
|
|
||||||
msg_list = []
|
|
||||||
|
|
||||||
# lists all functions to call
|
|
||||||
func_list = set()
|
|
||||||
for (attrib, functions) in cls.changes_trigger.iteritems():
|
|
||||||
if attrib in diff:
|
|
||||||
func_list.update(functions)
|
|
||||||
for function in func_list:
|
|
||||||
msg_list.append(function(body, diff))
|
|
||||||
return msg_list
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def regen(cls, body):
|
|
||||||
"""This method is referenced to avoid uncaught exceptions
|
|
||||||
|
|
||||||
"""
|
|
||||||
pass
|
|
|
@ -8,7 +8,8 @@
|
||||||
#
|
#
|
||||||
# Author : Pierre-Elliott Bécue <becue@crans.org>
|
# Author : Pierre-Elliott Bécue <becue@crans.org>
|
||||||
# License : GPLv3
|
# License : GPLv3
|
||||||
# Date : 29/04/2014
|
# Date : 10/03/2015
|
||||||
|
"""Main program for trigger library"""
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import cPickle
|
import cPickle
|
||||||
|
@ -24,21 +25,21 @@ from gestion.trigger.host import trigger_service
|
||||||
import cranslib.clogger as clogger
|
import cranslib.clogger as clogger
|
||||||
import cmb
|
import cmb
|
||||||
|
|
||||||
hostname = socket.gethostname().split(".")[0]
|
HOSTNAME = socket.gethostname().split(".")[0]
|
||||||
logger = clogger.CLogger("trigger", "trigger", trigger_config.log_level, trigger_config.debug)
|
LOGGER = clogger.CLogger("trigger", "trigger", trigger_config.log_level, trigger_config.debug)
|
||||||
|
|
||||||
# Ce bloc contient le peu de "magie" de la librairie, on utilise les services listés dans config/trigger.py
|
# Ce bloc contient le peu de "magie" de la librairie, on utilise les services listés dans config/trigger.py
|
||||||
# comme référence. Pour éviter toute redondance, la commande importe donc les services utiles suivant cette
|
# comme référence. Pour éviter toute redondance, la commande importe donc les services utiles suivant cette
|
||||||
# config. Leur import ne sert pas directemet, il permet juste de peupler la TriggerFactory contenue dans
|
# config. Leur import ne sert pas directement, il permet juste de peupler la TriggerFactory contenue dans
|
||||||
# gestion/trigger/host.py.
|
# gestion/trigger/host.py.
|
||||||
# Il faut donc bien importer ces fichiers, mais ils ne sont pas utilisés directement ensuite.
|
# Il faut donc bien importer ces fichiers, mais ils ne sont pas utilisés directement ensuite.
|
||||||
import importlib
|
import importlib
|
||||||
services = {}
|
SERVICES = {}
|
||||||
for config_service in trigger_config.services[hostname]:
|
for config_service in trigger_config.services[HOSTNAME]:
|
||||||
try:
|
try:
|
||||||
services[config_service] = importlib.import_module("gestion.trigger.services.%s" % (config_service,))
|
SERVICES[config_service] = importlib.import_module("gestion.trigger.services.%s" % (config_service,))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.critical("Fatal : import of %s failed, see following traceback. %s", config_service, traceback.format_exc())
|
LOGGER.critical("Fatal : import of %s failed, see following traceback. %s", config_service, traceback.format_exc())
|
||||||
|
|
||||||
class EvenementListener(cmb.AsynchronousConsumer):
|
class EvenementListener(cmb.AsynchronousConsumer):
|
||||||
"""
|
"""
|
||||||
|
@ -64,18 +65,20 @@ class EvenementListener(cmb.AsynchronousConsumer):
|
||||||
#origin = properties.app_id
|
#origin = properties.app_id
|
||||||
#message_id = properties.message_id
|
#message_id = properties.message_id
|
||||||
body = cPickle.loads(body)
|
body = cPickle.loads(body)
|
||||||
logger.info('Received message # %s from %s: %s',
|
LOGGER.info('Received message # %s from %s: %s',
|
||||||
basic_deliver.delivery_tag, properties.app_id, body)
|
basic_deliver.delivery_tag, properties.app_id, body)
|
||||||
|
|
||||||
# On tente d'invoquer le trigger attendu, à l'aide de la méthode trigger
|
# On tente d'invoquer le trigger attendu, à l'aide de la méthode trigger
|
||||||
# about contient le nom de la fonction à appeler, body lui est filé en argument.
|
# about contient le nom de la fonction à appeler, body lui est filé en argument.
|
||||||
try:
|
try:
|
||||||
if about in trigger_config.services[hostname]:
|
if about in trigger_config.services[HOSTNAME]:
|
||||||
trigger_service(about)(body)
|
trigger_service(about)(*body)
|
||||||
else:
|
else:
|
||||||
raise AttributeError
|
raise AttributeError
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
logger.warning('No suitable trigger found for message # %s from %s: %s on host %s. Discarding it.',
|
LOGGER.warning('No suitable trigger found for message # %s from %s: %s on host %s. Discarding it.',
|
||||||
basic_deliver.delivery_tag, properties.app_id, body, hostname)
|
basic_deliver.delivery_tag, properties.app_id, body, HOSTNAME)
|
||||||
|
|
||||||
self.acknowledge_message(basic_deliver.delivery_tag)
|
self.acknowledge_message(basic_deliver.delivery_tag)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
@ -83,54 +86,56 @@ class EvenementListener(cmb.AsynchronousConsumer):
|
||||||
starting the IOLoop to block and allow the SelectConnection to operate.
|
starting the IOLoop to block and allow the SelectConnection to operate.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
logger.info("""Crans Message Broker
|
LOGGER.info("""Crans Message Broker
|
||||||
+--------------------------------------------+
|
+--------------------------------------------+
|
||||||
| Welcome on Trigger |
|
| Welcome on Trigger |
|
||||||
+--------------------------------------------+""")
|
+--------------------------------------------+""")
|
||||||
self._connection = self.connect()
|
self._connection = self.connect()
|
||||||
for service in trigger_config.services[hostname]:
|
for service in trigger_config.services[HOSTNAME]:
|
||||||
self.add_queue("trigger-%s-%s" % (hostname, service), "trigger.%s" % (service,))
|
self.add_queue("trigger-%s-%s" % (HOSTNAME, service), "trigger.%s" % (service,))
|
||||||
self._connection.ioloop.start()
|
self._connection.ioloop.start()
|
||||||
|
|
||||||
def daemonize():
|
def daemonize():
|
||||||
|
"""Runs the script in "background"."""
|
||||||
trigger_password = secrets.get('rabbitmq_trigger_password')
|
trigger_password = secrets.get('rabbitmq_trigger_password')
|
||||||
credentials = pika.PlainCredentials(trigger_config.user, trigger_password)
|
credentials = pika.PlainCredentials(trigger_config.user, trigger_password)
|
||||||
listener = EvenementListener(url=trigger_config.master, exchange_name="trigger", exchange_type="topic", port=trigger_config.port, credentials=credentials, ssl=trigger_config.ssl)
|
listener = EvenementListener(url=trigger_config.master, exchange_name="trigger", exchange_type="topic", port=trigger_config.port, credentials=credentials, ssl=trigger_config.ssl)
|
||||||
try:
|
try:
|
||||||
listener.run()
|
listener.run()
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
logger.warning("Caught SIGINT, will now go for shutdown.")
|
LOGGER.warning("Caught SIGINT, will now go for shutdown.")
|
||||||
listener.stop()
|
listener.stop()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
# We use a parser to capture all possible arguments designed for one host
|
# We use a parser to capture all possible arguments designed for one host
|
||||||
parser = argparse.ArgumentParser(description="Initier une régénération de services.", add_help=False)
|
PARSER = argparse.ArgumentParser(description="Initier une régénération de services.", add_help=False)
|
||||||
parser.add_argument('-a', '--all', help="Régénération complète des services sur l'hôte %s." % (hostname,), action="store_true")
|
PARSER.add_argument('-a', '--all', help="Régénération complète des services sur l'hôte %s." % (HOSTNAME,), action="store_true")
|
||||||
parser.add_argument('-d', '--daemon', help="Écouter sur civet en arrière plan.", action="store_true")
|
PARSER.add_argument('-d', '--daemon', help="Écouter en arrière plan.", action="store_true")
|
||||||
parser.add_argument('-h', '--help', help="Affiche ce message et quitte.", action="store_true")
|
PARSER.add_argument('-h', '--help', help="Affiche ce message et quitte.", action="store_true")
|
||||||
|
|
||||||
# For each service supposingly managed by host, generate one parser option
|
# For each service supposingly managed by host, generate one parser option
|
||||||
# Deuxième petit morceau "magique" du code.
|
# Deuxième petit morceau "magique" du code.
|
||||||
for arg_service in trigger_config.services[hostname]:
|
for arg_service in trigger_config.services[HOSTNAME]:
|
||||||
parser.add_argument('--%s' % (arg_service,), help="Force la régénération du service %s." % (arg_service,), action="store_true")
|
PARSER.add_argument('--%s' % (arg_service,), help="Force la régénération du service %s." % (arg_service,), action="store_true")
|
||||||
args = parser.parse_args()
|
ARGS = PARSER.parse_args()
|
||||||
|
|
||||||
if args.help:
|
if ARGS.help:
|
||||||
parser.print_help()
|
PARSER.print_help()
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
elif args.all:
|
elif ARGS.all:
|
||||||
# Regenerates all services availables, don't crash on nonexistant ones
|
# Regenerates all services availables, don't crash on nonexistant ones
|
||||||
for host_service in trigger_config.services[hostname]:
|
for host_service in trigger_config.services[HOSTNAME]:
|
||||||
try:
|
try:
|
||||||
print affichage.style(" (Ré)Génération du service %s" % (host_service,), "cyan")
|
print affichage.style(" (Ré)Génération du service %s" % (host_service,), "cyan")
|
||||||
trigger_service(host_service)(True)
|
trigger_service(host_service)(True)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
print "No suitable trigger handle found for service %s on host %s" % (host_service, hostname)
|
print "No suitable trigger handle found for service %s on host %s" % (host_service, HOSTNAME)
|
||||||
elif args.daemon:
|
elif ARGS.daemon:
|
||||||
# Daemonize the trigger app, in order to listen and execute commands from civet.
|
# Daemonize the trigger app, in order to listen and execute commands from civet.
|
||||||
daemonize()
|
daemonize()
|
||||||
else:
|
else:
|
||||||
# If not all and not daemon, try all services one by one.
|
# If not all and not daemon, try all services one by one.
|
||||||
for arg_service in trigger_config.services[hostname]:
|
for arg_service in trigger_config.services[HOSTNAME]:
|
||||||
if getattr(args, arg_service, False) == True:
|
if getattr(ARGS, arg_service, False) == True:
|
||||||
print affichage.style(" (Ré)Génération du service %s" % (arg_service,), "cyan")
|
print affichage.style(" (Ré)Génération du service %s" % (arg_service,), "cyan")
|
||||||
trigger_service(arg_service)(True)
|
trigger_service(arg_service)(True)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue