[trigger] Going back to simplified version

This commit is contained in:
Pierre-Elliott Bécue 2014-07-31 11:50:47 +02:00
parent 8068f057e0
commit 091a2d161e
6 changed files with 287 additions and 292 deletions

View file

@ -7,6 +7,7 @@
import itertools import itertools
debug = True debug = True
log_level = "info"
# Serveur maître # Serveur maître
master = "rabbitmq.adm.crans.org" master = "rabbitmq.adm.crans.org"

View file

@ -10,27 +10,50 @@
# License : GPLv3 # License : GPLv3
# Date : 28/04/2014 # Date : 28/04/2014
import collections
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
""" """
_meths = {} _services = {}
_parsers = collections.defaultdict(list)
@classmethod @classmethod
def register(cls, key, value): def register_service(cls, key, value):
cls._meths[key] = value cls._services[key] = value
@classmethod @classmethod
def get(cls, key): def get_service(cls, key):
return cls._meths.get(key, None) return cls._services.get(key, None)
@classmethod @classmethod
def get_services(cls): def get_services(cls):
return cls._meths.values() return cls._services.values()
def record(cls): @classmethod
TriggerFactory.register(cls.__name__.lower(), cls) def register_parser(cls, keys, parser):
for key in keys:
cls._parsers[key].append(parser)
def trigger(what): @classmethod
return TriggerFactory.get(what) def get_parser(cls, keyword):
return cls._parsers[keyword]
def record_service(func):
"""Records in the triggerfactory the function
The function provided are services to regen
"""
TriggerFactory.register_service(func.func_name, func)
def trigger_service(what):
return TriggerFactory.get_service(what)
def record_parser(*args):
def find_parser(func):
TriggerFactory.register_parser(args, func)
return func
return find_parser

View file

@ -18,38 +18,31 @@ import struct
import gestion.config.trigger as trigger_config import gestion.config.trigger as trigger_config
import cranslib.clogger as clogger import cranslib.clogger as clogger
logger = clogger.CLogger("trigger", "dhcp", "debug", trigger_config.debug) logger = clogger.CLogger("trigger", "dhcp", trigger_config.log_level, trigger_config.debug)
hostname = socket.gethostname().split(".")[0] + ".adm.crans.org" hostname = socket.gethostname().split(".")[0]
hostname_adm = hostname + ".adm.crans.org"
import lc_ldap.shortcuts import lc_ldap.shortcuts
from gestion.trigger.services.service import BasicService
from cranslib.conffile import ConfFile from cranslib.conffile import ConfFile
import gestion.config.dhcp as dhcp_config import gestion.config.dhcp as dhcp_config
import gestion.secrets_new as secrets_new import gestion.secrets_new as secrets_new
import gestion.affichage as affichage import gestion.affichage as affichage
import gestion.iptools as iptools import gestion.iptools as iptools
from gestion.trigger.pypureomapi import pack_ip, pack_mac, OMAPI_OP_UPDATE from gestion.trigger.pypureomapi import pack_ip, pack_mac, OMAPI_OP_UPDATE, Omapi, OmapiMessage
from gestion.trigger.pypureomapi import Omapi, OmapiMessage from gestion.trigger.host import record_service, record_parser, TriggerFactory
class Dhcp(BasicService):
"""Class responsible of dhcp service.
"""
# Class lookup table to define which changes call which function.
changes_trigger = {
lc_ldap.attributs.macAddress.ldap_name: ('send_mac_ip',),
lc_ldap.attributs.ipHostNumber.ldap_name: ('send_mac_ip',),
}
if "dhcp" in trigger_config.services[hostname]:
dhcp_omapi_keyname = secrets_new.get("dhcp_omapi_keyname")
dhcp_omapi_key = secrets_new.get("dhcp_omapi_keys")[hostname_adm]
ldap_conn = lc_ldap.shortcuts.lc_ldap_readonly()
else:
dhcp_omapi_keyname = None dhcp_omapi_keyname = None
dhcp_omapi_key = None dhcp_omapi_key = None
ldap_conn = None ldap_conn = None
@classmethod @record_parser(lc_ldap.attributs.macAddress.ldap_name, lc_ldap.attributs.ipHostNumber.ldap_name)
def send_mac_ip(cls, body, diff): def send_mac_ip(body, diff):
"""Computes mac_ip data to send from body and diff """Computes mac_ip data to send from body and diff
""" """
@ -69,12 +62,11 @@ class Dhcp(BasicService):
dhcp_dict = {'update': [(macs[0], ips[0], macs[1], ips[1], hostnames[1])]} dhcp_dict = {'update': [(macs[0], ips[0], macs[1], ips[1], hostnames[1])]}
return ("dhcp", dhcp_dict) return ("dhcp", dhcp_dict)
@classmethod @record_service
def regen(cls, body=None): def dhcp(body=None):
"""Regenerates dhcp service taking body into account. """Regenerates dhcp service taking body into account.
""" """
cls.check_params()
# http://satyajit.ranjeev.in/2012/01/12/python--dangerous-default-value-as-argument.html # http://satyajit.ranjeev.in/2012/01/12/python--dangerous-default-value-as-argument.html
# dict are referenced. # dict are referenced.
@ -85,27 +77,27 @@ class Dhcp(BasicService):
for (mac, ip, name) in body.get("add", []): for (mac, ip, name) in body.get("add", []):
logger.info("Updating DHCP db by adding %s, %s, %s", mac, ip, name) logger.info("Updating DHCP db by adding %s, %s, %s", mac, ip, name)
# XXX - Uncommend this when we need to start prod # XXX - Uncommend this when we need to start prod
# cls.add_dhcp_host(mac, ip, name) # add_dhcp_host(mac, ip, name)
for (mac, ip) in body.get("delete", []): for (mac, ip) in body.get("delete", []):
logger.info("Updating DHCP db by deleting %s, %s", mac, ip) logger.info("Updating DHCP db by deleting %s, %s", mac, ip)
# XXX - Uncommend this when we need to start prod # XXX - Uncommend this when we need to start prod
# cls.delete_dhcp_host(mac, ip) # delete_dhcp_host(mac, ip)
for (rmac, rip, mac, ip, name) in body.get("update", []): for (rmac, rip, mac, ip, name) in body.get("update", []):
logger.info("Updating DHCP db by modifying %s, %s to %s, %s, %s", rmac, rip, mac, ip, name) logger.info("Updating DHCP db by modifying %s, %s to %s, %s, %s", rmac, rip, mac, ip, name)
# XXX - Uncommend this when we need to start prod # XXX - Uncommend this when we need to start prod
# cls.delete_dhcp_host(rmac, rip) # delete_dhcp_host(rmac, rip)
# cls.add_dhcp_host(mac, ip, name) # add_dhcp_host(mac, ip, name)
elif body == True: elif body == True:
hosts = {} hosts = {}
host_template = """ host_template = """
host %(nom)s { host %(nom)s {
hardware ethernet %(mac)s; hardware ethernet %(mac)s;
fixed-address %(ip)s; fixed-address %(ip)s;
option host-name "%(host)s"; option host-name "%(host)s";
} }
""" """
affichage.prettyDoin("Chargement des machines", "...") affichage.prettyDoin("Chargement des machines", "...")
machines = cls.ldap_conn.allMachines() machines = ldap_conn.allMachines()
affichage.prettyDoin("Chargement des machines", "Ok") affichage.prettyDoin("Chargement des machines", "Ok")
animation = affichage.Animation(texte="Génération de la configuration", animation = affichage.Animation(texte="Génération de la configuration",
nb_cycles=len(machines), nb_cycles=len(machines),
@ -143,34 +135,18 @@ class Dhcp(BasicService):
step = "Nettoyage des fichiers de leases" step = "Nettoyage des fichiers de leases"
affichage.prettyDoin(step, "...") affichage.prettyDoin(step, "...")
try: try:
cls.lease_clean() lease_clean()
affichage.prettyDoin(step, "Ok") affichage.prettyDoin(step, "Ok")
except: except:
affichage.prettyDoin(step, "Erreur") affichage.prettyDoin(step, "Erreur")
print "During lease clean, an error occured." print "During lease clean, an error occured."
raise raise
@classmethod
def check_params(cls):
"""This method allows lazy evaluation for dhcp_omapi_keyname
and dhcp_omapi_key, since event imports all services. This is actually
the best lazy eval we can hope, since property won't work on
classmethods.
""" def add_dhcp_host(mac, ip, name=None):
if cls.dhcp_omapi_keyname is None:
cls.dhcp_omapi_keyname = secrets_new.get("dhcp_omapi_keyname")
if cls.dhcp_omapi_key is None:
cls.dhcp_omapi_key = secrets_new.get("dhcp_omapi_keys")[hostname]
if cls.ldap_conn is None:
cls.ldap_conn = lc_ldap.shortcuts.lc_ldap_readonly()
@classmethod
def add_dhcp_host(cls, mac, ip, name=None):
"""Adds a dhcp host using omapi """Adds a dhcp host using omapi
""" """
cls.check_params()
if '<automatique>' in [ip, mac]: if '<automatique>' in [ip, mac]:
return return
@ -182,16 +158,15 @@ class Dhcp(BasicService):
msg.obj.append((b"ip-address", pack_ip(ip))) msg.obj.append((b"ip-address", pack_ip(ip)))
if name: if name:
msg.obj.append((b"name", bytes(name))) msg.obj.append((b"name", bytes(name)))
conn = Omapi(hostname, 9991, cls.dhcp_omapi_keyname, cls.dhcp_omapi_key) conn = Omapi(hostname_adm, 9991, dhcp_omapi_keyname, dhcp_omapi_key)
_ = conn.query_server(msg) _ = conn.query_server(msg)
conn.close() conn.close()
@classmethod
def delete_dhcp_host(cls, mac, ip): def delete_dhcp_host(mac, ip):
"""Deletes dhcp host using omapi """Deletes dhcp host using omapi
""" """
cls.check_params()
if '<automatique>' in [ip, mac]: if '<automatique>' in [ip, mac]:
return return
@ -199,14 +174,14 @@ class Dhcp(BasicService):
msg.obj.append((b"hardware-address", pack_mac(mac))) msg.obj.append((b"hardware-address", pack_mac(mac)))
msg.obj.append((b"hardware-type", struct.pack("!I", 1))) msg.obj.append((b"hardware-type", struct.pack("!I", 1)))
msg.obj.append((b"ip-address", pack_ip(ip))) msg.obj.append((b"ip-address", pack_ip(ip)))
conn = Omapi(hostname, 9991, cls.dhcp_omapi_keyname, cls.dhcp_omapi_key) conn = Omapi(hostname_adm, 9991, dhcp_omapi_keyname, dhcp_omapi_key)
response = conn.query_server(msg) response = conn.query_server(msg)
if response.opcode == OMAPI_OP_UPDATE: if response.opcode == OMAPI_OP_UPDATE:
_ = conn.query_server(OmapiMessage.delete(response.handle)) _ = conn.query_server(OmapiMessage.delete(response.handle))
conn.close() conn.close()
@staticmethod
def lease_clean(): def lease_clean():
"""Clean the lease file """Clean the lease file
""" """
@ -226,4 +201,3 @@ class Dhcp(BasicService):
leasefile.close() leasefile.close()
newleasefile.close() newleasefile.close()
os.rename(dhcp_config.dhcplease+'.new', dhcp_config.dhcplease) os.rename(dhcp_config.dhcplease+'.new', dhcp_config.dhcplease)

View file

@ -21,10 +21,10 @@ import itertools
import traceback import traceback
import gestion.secrets_new as secrets 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 from gestion.trigger.host import TriggerFactory, record_service, record_parser
from gestion.trigger.services.service import BasicService
# Clogger # Clogger
import cranslib.clogger as clogger import cranslib.clogger as clogger
@ -32,7 +32,7 @@ import cranslib.clogger as clogger
# lc_ldap # lc_ldap
import lc_ldap.attributs import lc_ldap.attributs
logger = clogger.CLogger("trigger", "event", "debug", trigger_config.debug) logger = clogger.CLogger("trigger", "event", trigger_config.log_level, trigger_config.debug)
services = [] services = []
for config_service in trigger_config.all_services: for config_service in trigger_config.all_services:
@ -136,21 +136,9 @@ def compare_lists(list1, list2):
return moins, plus return moins, plus
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.
""" @record_service
def event(body=()):
@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 """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
@ -187,12 +175,13 @@ class Event(BasicService):
# #
#In [16]: b #In [16]: b
#Out[16]: [('7', 3), (5, 6), ('lol', 'lal'), (3, 'lol')] #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] 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: for msg in msg_to_send:
logger.info("Sending %r on the road \\o/", msg) 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): def trigger_send(routing_key, body):
sender = EventProducer("civet") sender = EventProducer("civet")

View file

@ -16,26 +16,33 @@ it to regenerate what needs to.
import cranslib.clogger as clogger import cranslib.clogger as clogger
import gestion.config.trigger as trigger_config import gestion.config.trigger as trigger_config
logger = clogger.CLogger("trigger", "firewall", "debug", trigger_config.debug) logger = clogger.CLogger("trigger", "firewall", trigger_config.log_level, trigger_config.debug)
import lc_ldap.shortcuts import lc_ldap.shortcuts
from gestion.trigger.services.service import BasicService from gestion.trigger.host import record_service, record_parser
import gestion.trigger.firewall4.firewall4 as firewall4 import gestion.trigger.firewall4.firewall4 as firewall4
class Firewall(BasicService): class FwFactory(object):
"""Firewall service that handles any modification in the firewall. """Records firewall functions, and provide them.
""" """
# Class lookup table to define which changes call which function. _fwfuns = {}
changes_trigger = {
lc_ldap.attributs.macAddress.ldap_name: ('send_mac_ip',),
lc_ldap.attributs.ipHostNumber.ldap_name: ('send_mac_ip',),
}
@classmethod @classmethod
def send_mac_ip(cls, body, diff): def register(cls, key, value):
cls._fwfuns[key] = value
@classmethod
def get(cls, key):
return cls._fwfuns.get(key, None)
def fwrecord(fun):
FwFactory.register(fun.func_name, fun)
@record_parser(lc_ldap.attributs.macAddress.ldap_name, lc_ldap.attributs.ipHostNumber.ldap_name)
def send_mac_ip(body, diff):
"""Computes mac_ip data to send from body and diff """Computes mac_ip data to send from body and diff
""" """
@ -54,8 +61,8 @@ class Firewall(BasicService):
fw = {'update': [(macs[0], ips[0], macs[1], ips[1])]} fw = {'update': [(macs[0], ips[0], macs[1], ips[1])]}
return ("firewall", ("mac_ip", fw)) return ("firewall", ("mac_ip", fw))
@classmethod @record_service
def regen(cls, body=()): def firewall(body=()):
"""Regens the specific service """Regens the specific service
""" """
@ -64,10 +71,11 @@ class Firewall(BasicService):
return return
(service, data) = body (service, data) = body
logger.info("Calling service %s for data %r", service, data) logger.info("Calling service %s for data %r", service, data)
getattr(cls, service)(data) # XXX - Uncomment when in prod
#FwFactory.get(service)(data)
@classmethod @fwrecord
def mac_ip(cls, body): def mac_ip(body):
host_fw = firewall4.firewall() host_fw = firewall4.firewall()
if body and isinstance(body, dict): if body and isinstance(body, dict):
for (mac, ip) in body.get("add", []): for (mac, ip) in body.get("add", []):

View file

@ -20,12 +20,12 @@ import pika
import gestion.secrets_new as secrets import gestion.secrets_new as secrets
import gestion.config.trigger as trigger_config import gestion.config.trigger as trigger_config
import gestion.affichage as affichage import gestion.affichage as affichage
from gestion.trigger.host import trigger 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", "info", 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
@ -70,7 +70,7 @@ class EvenementListener(cmb.AsynchronousConsumer):
# 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(about).regen(body) trigger_service(about)(body)
else: else:
raise AttributeError raise AttributeError
except AttributeError: except AttributeError:
@ -122,7 +122,7 @@ if __name__ == '__main__':
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(host_service).regen(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:
@ -133,4 +133,4 @@ if __name__ == '__main__':
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(arg_service).regen(True) trigger_service(arg_service)(True)