Commit a52ef72a authored by Pierre-Elliott Bécue's avatar Pierre-Elliott Bécue

[trigger] MetaService en place, et amélioration du débug

parent d2934339
...@@ -13,9 +13,9 @@ class CLogger(logging.Logger): ...@@ -13,9 +13,9 @@ class CLogger(logging.Logger):
Crans logger Crans logger
""" """
def __init__(self, loggerName, level): def __init__(self, loggerName, service, level, debug=False):
""" """
Initialise le logger Initializes logger. The debug variable is useful to have a print to stdout (when debugging)
""" """
super(CLogger, self).__init__(loggerName) super(CLogger, self).__init__(loggerName)
...@@ -27,10 +27,17 @@ class CLogger(logging.Logger): ...@@ -27,10 +27,17 @@ class CLogger(logging.Logger):
self.fh.setLevel(self.fhlevel) self.fh.setLevel(self.fhlevel)
# Creates formatter # Creates formatter
self.formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') self.formatter = logging.Formatter('%%(asctime)s - %%(name)s - %(service)s - %%(levelname)s - %%(message)s' % {'service': service})
# Adds formatter to FileHandler # Adds formatter to FileHandler
self.fh.setFormatter(self.formatter) self.fh.setFormatter(self.formatter)
if debug:
self.sh = logging.StreamHandler()
self.shlevel = logging.DEBUG
self.sh.setLevel(self.shlevel)
self.sh.setFormatter(self.formatter)
self.addHandler(self.sh)
# Adds FileHandler to Handlers # Adds FileHandler to Handlers
self.addHandler(self.fh) self.addHandler(self.fh)
...@@ -6,6 +6,8 @@ ...@@ -6,6 +6,8 @@
import itertools import itertools
debug = True
# Serveur maître # Serveur maître
master = "civet.adm.crans.org" master = "civet.adm.crans.org"
......
...@@ -30,7 +30,7 @@ class TriggerFactory(object): ...@@ -30,7 +30,7 @@ class TriggerFactory(object):
return cls._meths.values() return cls._meths.values()
def record(cls): def record(cls):
TriggerFactory.register(cls.__name__, cls) TriggerFactory.register(cls.__name__.lower(), cls)
def trigger(what): def trigger(what):
return TriggerFactory.get(what) return TriggerFactory.get(what)
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
# Licence : GPLv3 # Licence : GPLv3
import lc_ldap.shortcuts import lc_ldap.shortcuts
from gestion.trigger.host import record import gestion.config.trigger as trigger_config
from gestion.trigger.services.service import BasicService from gestion.trigger.services.service import BasicService
from cranslib.conffile import ConfFile from cranslib.conffile import ConfFile
import cranslib.clogger as clogger import cranslib.clogger as clogger
...@@ -23,82 +23,25 @@ from gestion.trigger.pypureomapi import pack_ip, pack_mac, OMAPI_OP_UPDATE ...@@ -23,82 +23,25 @@ from gestion.trigger.pypureomapi import pack_ip, pack_mac, OMAPI_OP_UPDATE
from gestion.trigger.pypureomapi import Omapi, OmapiMessage from gestion.trigger.pypureomapi import Omapi, OmapiMessage
import struct import struct
logger = clogger.CLogger("trigger.dhcp", "debug") logger = clogger.CLogger("trigger", "dhcp", "debug", trigger_config.debug)
hostname = socket.gethostname().split(".")[0] + ".adm.crans.org" hostname = socket.gethostname().split(".")[0] + ".adm.crans.org"
dhcp_omapi_keyname = secrets_new.get("dhcp_omapi_keyname")
dhcp_omapi_key = secrets_new.get("dhcp_omapi_keys")[hostname]
ldap_conn = lc_ldap.shortcuts.lc_ldap_readonly() ldap_conn = lc_ldap.shortcuts.lc_ldap_readonly()
def add_dhcp_host(mac, ip, name=None):
"""Adds a dhcp host using omapi
""" class Dhcp(BasicService):
if '<automatique>' in [ip, mac]:
return
msg = OmapiMessage.open(b"host")
msg.message.append((b"create", struct.pack("!I", 1)))
msg.message.append((b"exclusive", struct.pack("!I", 1)))
msg.obj.append((b"hardware-address", pack_mac(mac)))
msg.obj.append((b"hardware-type", struct.pack("!I", 1)))
msg.obj.append((b"ip-address", pack_ip(ip)))
if name:
msg.obj.append((b"name", bytes(name)))
conn = Omapi(hostname, 9991, dhcp_omapi_keyname, dhcp_omapi_key)
response = conn.query_server(msg)
conn.close()
def delete_dhcp_host(mac, ip):
"""Deletes dhcp host using omapi
"""
if '<automatique>' in [ip, mac]:
return
msg = OmapiMessage.open(b"host")
msg.obj.append((b"hardware-address", pack_mac(mac)))
msg.obj.append((b"hardware-type", struct.pack("!I", 1)))
msg.obj.append((b"ip-address", pack_ip(ip)))
conn = Omapi(hostname, 9991, dhcp_omapi_keyname, dhcp_omapi_key)
response = conn.query_server(msg)
if response.opcode == OMAPI_OP_UPDATE:
response = conn.query_server(OmapiMessage.delete(response.handle))
conn.close()
def lease_clean():
"""Clean the lease file
"""
# TODO : use ConfigFile structure
leasefile = open(dhcp_config.dhcplease)
newleasefile = open(dhcp_config.dhcplease + '.new', 'w')
config = ""
line = leasefile.readline()
write = True
while line:
if line.strip().startswith('host'):
write = False
if write:
newleasefile.write(line)
if not write and line.strip().endswith('}'):
write = True
line = leasefile.readline()
leasefile.close()
newleasefile.close()
os.rename(dhcp_config.dhcplease+'.new', dhcp_config.dhcplease)
@record
class dhcp(BasicService):
"""Class responsible of dhcp service. """Class responsible of dhcp service.
""" """
# Class lookup table to define which changes call which function. # Class lookup table to define which changes call which function.
changes_trigger = { changes_trigger = {
lc_ldap.attributs.macAddress.ldap_name: (dhcp.send_mac_ip,), lc_ldap.attributs.macAddress.ldap_name: ('send_mac_ip',),
lc_ldap.attributs.ipHostNumber.ldap_name: (dhcp.send_mac_ip,), lc_ldap.attributs.ipHostNumber.ldap_name: ('send_mac_ip',),
} }
dhcp_omapi_keyname = None
dhcp_omapi_key = None #secrets_new.get("dhcp_omapi_keys")[hostname]
@classmethod @classmethod
def send_mac_ip(cls, body, diff): def send_mac_ip(cls, body, diff):
"""Computes mac_ip data to send from body and diff """Computes mac_ip data to send from body and diff
...@@ -111,14 +54,14 @@ class dhcp(BasicService): ...@@ -111,14 +54,14 @@ class dhcp(BasicService):
# Régénération du DHCP : # Régénération du DHCP :
if not macs[0]: if not macs[0]:
# Création d'une nouvelle machine. # Création d'une nouvelle machine.
dhcp = {'add': [(macs[1], ips[1], hostnames[1])]} dhcp_dict = {'add': [(macs[1], ips[1], hostnames[1])]}
elif not macs[1]: elif not macs[1]:
# Destruction d'une machine. # Destruction d'une machine.
dhcp = {'delete': [(macs[0], ips[0])]} dhcp_dict = {'delete': [(macs[0], ips[0])]}
else: else:
# Mise à jour. # Mise à jour.
dhcp = {'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) return ("dhcp", dhcp_dict)
@classmethod @classmethod
def regen(cls, body=None): def regen(cls, body=None):
...@@ -132,12 +75,12 @@ class dhcp(BasicService): ...@@ -132,12 +75,12 @@ class dhcp(BasicService):
if body and isinstance(body, dict): if body and isinstance(body, dict):
for (mac, ip, name) in body.get("add", []): for (mac, ip, name) in body.get("add", []):
add_dhcp_host(mac, ip, name) cls.add_dhcp_host(mac, ip, name)
for (mac, ip) in body.get("delete", []): for (mac, ip) in body.get("delete", []):
delete_dhcp_host(mac, ip) cls.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", []):
delete_dhcp_host(rmac, rip) cls.delete_dhcp_host(rmac, rip)
add_dhcp_host(mac, ip, name) cls.add_dhcp_host(mac, ip, name)
elif body == True: elif body == True:
hosts = {} hosts = {}
host_template = """ host_template = """
...@@ -186,9 +129,85 @@ class dhcp(BasicService): ...@@ -186,9 +129,85 @@ class dhcp(BasicService):
step = "Nettoyage des fichiers de leases" step = "Nettoyage des fichiers de leases"
affichage.prettyDoin(step, "...") affichage.prettyDoin(step, "...")
try: try:
lease_clean() cls.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_secrets(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.
"""
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]
@classmethod
def add_dhcp_host(cls, mac, ip, name=None):
"""Adds a dhcp host using omapi
"""
cls.check_secrets()
if '<automatique>' in [ip, mac]:
return
msg = OmapiMessage.open(b"host")
msg.message.append((b"create", struct.pack("!I", 1)))
msg.message.append((b"exclusive", struct.pack("!I", 1)))
msg.obj.append((b"hardware-address", pack_mac(mac)))
msg.obj.append((b"hardware-type", struct.pack("!I", 1)))
msg.obj.append((b"ip-address", pack_ip(ip)))
if name:
msg.obj.append((b"name", bytes(name)))
conn = Omapi(hostname, 9991, cls.dhcp_omapi_keyname, cls.dhcp_omapi_key)
_ = conn.query_server(msg)
conn.close()
@classmethod
def delete_dhcp_host(cls, mac, ip):
"""Deletes dhcp host using omapi
"""
cls.check_secrets()
if '<automatique>' in [ip, mac]:
return
msg = OmapiMessage.open(b"host")
msg.obj.append((b"hardware-address", pack_mac(mac)))
msg.obj.append((b"hardware-type", struct.pack("!I", 1)))
msg.obj.append((b"ip-address", pack_ip(ip)))
conn = Omapi(hostname, 9991, cls.dhcp_omapi_keyname, cls.dhcp_omapi_key)
response = conn.query_server(msg)
if response.opcode == OMAPI_OP_UPDATE:
_ = conn.query_server(OmapiMessage.delete(response.handle))
conn.close()
@staticmethod
def lease_clean():
"""Clean the lease file
"""
# TODO : use ConfigFile structure
leasefile = open(dhcp_config.dhcplease)
newleasefile = open(dhcp_config.dhcplease + '.new', 'w')
line = leasefile.readline()
write = True
while line:
if line.strip().startswith('host'):
write = False
if write:
newleasefile.write(line)
if not write and line.strip().endswith('}'):
write = True
line = leasefile.readline()
leasefile.close()
newleasefile.close()
os.rename(dhcp_config.dhcplease+'.new', dhcp_config.dhcplease)
...@@ -18,6 +18,7 @@ import cPickle ...@@ -18,6 +18,7 @@ import cPickle
import pika import pika
import importlib import importlib
import itertools import itertools
import traceback
# Trigger features # Trigger features
import gestion.config.trigger as trigger_config import gestion.config.trigger as trigger_config
...@@ -30,20 +31,24 @@ import cranslib.clogger as clogger ...@@ -30,20 +31,24 @@ import cranslib.clogger as clogger
# lc_ldap # lc_ldap
import lc_ldap.attributs import lc_ldap.attributs
logger = clogger.CLogger("trigger.event", "info") logger = clogger.CLogger("trigger", "event", "debug", trigger_config.debug)
services = [importlib.import_module("gestion.trigger.services.%s" % (config_service,)) for config_service in trigger_config.all_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 %r failed, see following traceback. %r", config_service, traceback.format_exc())
class Event(cmb.BasicProducer): class EventProducer(cmb.BasicProducer):
""" """
Event tracker EventProducer tracker
""" """
def __init__(self, app_id): def __init__(self, app_id):
"""Extended """Extended
""" """
logger.info("Starting trigger Event program…") logger.info("Starting trigger EventProducer program…")
super(Event, self).__init__(trigger_config.master, 'trigger', app_id) super(EventProducer, self).__init__(trigger_config.master, 'trigger', app_id)
self._connection = self.connect() self._connection = self.connect()
self.get_chan() self.get_chan()
...@@ -127,7 +132,6 @@ def compare_lists(list1, list2): ...@@ -127,7 +132,6 @@ def compare_lists(list1, list2):
return moins, plus return moins, plus
@record
class event(BasicService): class event(BasicService):
"""Event service class. It extends BasicService, but should not implement """Event service class. It extends BasicService, but should not implement
any change trigger, since it's this service which is designed to call any change trigger, since it's this service which is designed to call
...@@ -187,5 +191,5 @@ class event(BasicService): ...@@ -187,5 +191,5 @@ class event(BasicService):
# trigger_send(*msg) # trigger_send(*msg)
def trigger_send(routing_key, body): def trigger_send(routing_key, body):
sender = Event("civet") sender = EventProducer("civet")
sender.send_message("trigger.%s" % (routing_key,), body) sender.send_message("trigger.%s" % (routing_key,), body)
...@@ -15,57 +15,22 @@ it to regenerate what needs to. ...@@ -15,57 +15,22 @@ it to regenerate what needs to.
""" """
import lc_ldap.shortcuts import lc_ldap.shortcuts
from gestion.trigger.host import record import gestion.config.trigger as trigger_config
from gestion.trigger.services.service import BasicService from gestion.trigger.services.service import BasicService
import cranslib.clogger as clogger import cranslib.clogger as clogger
import gestion.config.firewall as firewall_config
import gestion.trigger.firewall4.firewall4 as firewall4 import gestion.trigger.firewall4.firewall4 as firewall4
logger = clogger.CLogger("trigger.firewall", "debug") logger = clogger.CLogger("trigger", "firewall", "debug", trigger_config.debug)
class FwFunFactory(object): class Firewall(BasicService):
"""Factory containing which function is part of the trigger set
"""
_meths = {}
@classmethod
def register(cls, key, value):
"""Stores in factory the function name and its value
"""
cls._meths[key] = value
@classmethod
def get(cls, key):
"""Gets what is stored
"""
return cls._meths.get(key, None)
def fwrecord(function):
"""Records function in FwFunFactory
"""
FwFunFactory.register(function.func_name, function)
def fwcall(fwfun):
"""Calls in function from FwFunFactory
"""
return FwFunFactory.get(fwfun)
@record
class firewall(BasicService):
"""Firewall service that handles any modification in the firewall. """Firewall service that handles any modification in the firewall.
""" """
# Class lookup table to define which changes call which function. # Class lookup table to define which changes call which function.
changes_trigger = { changes_trigger = {
lc_ldap.attributs.macAddress.ldap_name: (firewall.send_mac_ip,), lc_ldap.attributs.macAddress.ldap_name: ('send_mac_ip',),
lc_ldap.attributs.ipHostNumber.ldap_name: (firewall.send_mac_ip,), lc_ldap.attributs.ipHostNumber.ldap_name: ('send_mac_ip',),
} }
@classmethod @classmethod
...@@ -98,19 +63,19 @@ class firewall(BasicService): ...@@ -98,19 +63,19 @@ 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)
fwcall(service)(data) getattr(cls, service)(data)
@fwrecord @classmethod
def mac_ip(body): def mac_ip(cls, 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", []):
logger.info("Adding mac_ip %s,%s", mac, ip) logger.info("Adding mac_ip %s,%s", mac, ip)
host_fw.mac_ip_append(mac, ip) host_fw.mac_ip_append(mac, ip)
for (mac, ip) in body.get("delete", []): for (mac, ip) in body.get("delete", []):
logger.info("Removing mac_ip %s,%s", mac, ip) logger.info("Removing mac_ip %s,%s", mac, ip)
host_fw.mac_ip_remove(mac, ip) host_fw.mac_ip_remove(mac, ip)
for (rmac, rip, mac, ip) in body.get("update", []): for (rmac, rip, mac, ip) in body.get("update", []):
logger.info("Updating mac_ip %s,%s with %s,%s", rmac, rip, mac, ip) logger.info("Updating mac_ip %s,%s with %s,%s", rmac, rip, mac, ip)
host_fw.mac_ip_remove(rmac, rip) host_fw.mac_ip_remove(rmac, rip)
host_fw.mac_ip_append(mac, ip) host_fw.mac_ip_append(mac, ip)
...@@ -6,11 +6,65 @@ This module provides a basic service class to other services. It should *NOT* ...@@ -6,11 +6,65 @@ This module provides a basic service class to other services. It should *NOT*
be referenced in configuration of trigger. be referenced in configuration of trigger.
""" """
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 (%r) reference in %r.", 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): class BasicService(object):
"""Basic service handler. Other services should inherit fron this one. """Basic service handler. Other services should inherit fron this one.
""" """
__metaclass__ = MetaService
changes_trigger = {} changes_trigger = {}
@classmethod @classmethod
......
...@@ -11,16 +11,19 @@ ...@@ -11,16 +11,19 @@
# Date : 29/04/2014 # Date : 29/04/2014
import argparse import argparse
import cranslib.clogger as clogger
import cmb
import cPickle import cPickle
import socket import socket
import traceback
import sys
import gestion.config.trigger as trigger_config import gestion.config.trigger as trigger_config
import gestion.affichage as affichage import gestion.affichage as affichage
import sys
from gestion.trigger.host import trigger from gestion.trigger.host import trigger
import cranslib.clogger as clogger
import cmb
hostname = socket.gethostname().split(".")[0] hostname = socket.gethostname().split(".")[0]
logger = clogger.CLogger("trigger", "trigger", "info", 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
...@@ -30,8 +33,10 @@ hostname = socket.gethostname().split(".")[0] ...@@ -30,8 +33,10 @@ hostname = socket.gethostname().split(".")[0]
import importlib import importlib
services = {} services = {}
for config_service in trigger_config.services[hostname]: for config_service in trigger_config.services[hostname]:
services[config_service] = importlib.import_module("gestion.trigger.services.%s" % (config_service,)) try:
logger = clogger.CLogger("trigger", "info") services[config_service] = importlib.import_module("gestion.trigger.services.%s" % (config_service,))
except Exception as e:
logger.critical("Fatal : import of %r failed, see following traceback. %r", config_service, traceback.format_exc())
class EvenementListener(cmb.AsynchronousConsumer): class EvenementListener(cmb.AsynchronousConsumer):
""" """
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment