scripts/gestion/trigger/host.py
2015-03-10 21:51:08 +01:00

179 lines
6.6 KiB
Python

#!/bin/bash /usr/scripts/python.sh
# -*- coding: utf-8 -*-
#
# Basic trigger host, will be imported from any other
# Contains a TriggerFactory, which records the host functions
# decorated with @record.
# Contains a trigger which calls good functions from factory.
#
# License : New BSD License
# Date : 10/03/2015
# Copyright : Pierre-Elliott Bécue <becue@crans.org>
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the <organization> nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""This module provides host functions for trigger, such as the TriggerFactory which
stores parsers and services metadata."""
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")
# *
# * Factory
# *
class TriggerFactory(object):
"""The TriggerFactory is designed to store services and parsers.
* services are functions decorated with record_service, and, possibly,
chaining. Such functions are designed to do some operations on
servers in order to take into account LDAP modifications (or,
potentially, whatever else).
* parsers are functions decorated with record parsers, which are
triggered by some keywords (eg, ldap attributes names whose values
have been modified). They return stuff to do, and a service to
call.
"""
_services = {}
_parsers = collections.defaultdict(list)
@classmethod
def register_service(cls, key, value):
"""Stores the appropriate service in the factory"""
cls._services[key] = value
@classmethod
def get_service(cls, key):
"""Retrieves the appropriate service"""
return cls._services.get(key, None)
@classmethod
def get_services(cls):
"""Retrieves the list of all services"""
return cls._services.values()
@classmethod
def register_parser(cls, keys, parser):
"""Stores the attributes to watch and the function"""
for key in keys:
if not isinstance(key, str) and not isinstance(key, unicode):
key = getattr(key, 'ldap_name', None)
if key is not None:
cls._parsers[key].append(parser)
else:
LOGGER.debug("Problem when recording parser %r on keys %r.", parser.func_name, keys)
@classmethod
def get_parser(cls, keyword):
"""Restitutes the parser using keywords"""
return cls._parsers[keyword]
# *
# * Factory manipulation
# *
def record_service(ack=True):
"""Records a service in the TriggerFactory.
It uses its name as a key for reference, the value being
the function itself.
"""
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):
"""Calls the appropriate service"""
return TriggerFactory.get_service(what)
def record_parser(*watched):
"""Stores the function in TriggerFactory, using args as
keys for the dict"""
def find_parser(func):
"""Adds the chaining_pos at the end of the return of functions."""
@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(watched, enhanced_func)
return enhanced_func
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"""
LOGGER.debug("%r chaining pos : %r", func.func_name, pos)
setattr(func, "chaining_pos", pos)
return func
return add_pos