Exemplo n.º 1
0
class ReceiverLink(SatelliteLink):
    """
    Class to manage the receiver information
    """
    _id = 0
    my_type = 'receiver'
    properties = SatelliteLink.properties.copy()
    properties.update({
        'receiver_name':
        StringProp(fill_brok=['full_status'], to_send=True),
        'port':
        IntegerProp(default=7772, fill_brok=['full_status']),
        'manage_sub_realms':
        BoolProp(default=True, fill_brok=['full_status']),
        'manage_arbiters':
        BoolProp(default=False, fill_brok=['full_status'], to_send=True),
        'direct_routing':
        BoolProp(default=False, fill_brok=['full_status'], to_send=True),
        'accept_passive_unknown_check_results':
        BoolProp(default=False, fill_brok=['full_status'], to_send=True),
    })

    def register_to_my_realm(self):
        """
        Add this reactionner to the realm

        :return: None
        """
        self.realm.receivers.append(self)

    def push_host_names(self, sched_id, hnames):
        """
        Send host names to receiver

        :param sched_id: id of the scheduler
        :type sched_id: int
        :param hnames: list of host names
        :type hnames: list
        :return: None
        """
        try:
            if self.con is None:
                self.create_connection()
            logger.info(" (%s)", self.uri)

            # If the connection failed to initialize, bail out
            if self.con is None:
                self.add_failed_check_attempt()
                return

            # r = self.con.push_host_names(sched_id, hnames)
            self.con.get('ping')
            self.con.post('push_host_names', {
                'sched_id': sched_id,
                'hnames': hnames
            },
                          wait='long')
        except HTTPEXCEPTIONS, exp:
            self.add_failed_check_attempt(reason=str(exp))
Exemplo n.º 2
0
class Servicedependency(Item):
    """Servicedependency class is a simple implementation of service dependency as
    defined in a monitoring context (dependency period, notification_failure_criteria ..)

    """
    my_type = "servicedependency"

    # F is dep of D
    # host_name                      Host B
    #       service_description             Service D
    #       dependent_host_name             Host C
    #       dependent_service_description   Service F
    #       execution_failure_criteria      o
    #       notification_failure_criteria   w,u
    #       inherits_parent         1
    #       dependency_period       24x7

    properties = Item.properties.copy()
    properties.update({
        'dependent_host_name':
        StringProp(),
        'dependent_hostgroup_name':
        StringProp(default=''),
        'dependent_service_description':
        StringProp(),
        'host_name':
        StringProp(),
        'hostgroup_name':
        StringProp(default=''),
        'service_description':
        StringProp(),
        'inherits_parent':
        BoolProp(default=False),
        'execution_failure_criteria':
        ListProp(default=['n'], split_on_comma=True),
        'notification_failure_criteria':
        ListProp(default=['n'], split_on_comma=True),
        'dependency_period':
        StringProp(default=''),
        'explode_hostgroup':
        BoolProp(default=False)
    })

    def get_name(self):
        """Get name based on 4 class attributes
        Each attribute is substituted by '' if attribute does not exist

        :return: dependent_host_name/dependent_service_description..host_name/service_description
        :rtype: str
        TODO: Clean this function (use format for string)
        """
        return getattr(self, 'dependent_host_name', '') + '/'\
            + getattr(self, 'dependent_service_description', '') \
            + '..' + getattr(self, 'host_name', '') + '/' \
            + getattr(self, 'service_description', '')
Exemplo n.º 3
0
class SchedulerLink(SatelliteLink):
    """
    Class to manage the scheduler information
    """
    _id = 0

    # Ok we lie a little here because we are a mere link in fact
    my_type = 'scheduler'

    properties = SatelliteLink.properties.copy()
    properties.update({
        'scheduler_name':
        StringProp(fill_brok=['full_status']),
        'port':
        IntegerProp(default=7768, fill_brok=['full_status']),
        'weight':
        IntegerProp(default=1, fill_brok=['full_status']),
        'skip_initial_broks':
        BoolProp(default=False, fill_brok=['full_status']),
        'accept_passive_unknown_check_results':
        BoolProp(default=False, fill_brok=['full_status']),
    })

    running_properties = SatelliteLink.running_properties.copy()
    running_properties.update({
        'conf': StringProp(default=None),
        'need_conf': StringProp(default=True),
        'external_commands': StringProp(default=[]),
        'push_flavor': IntegerProp(default=0),
    })

    def run_external_commands(self, commands):
        """
        Run external commands

        :param commands:
        :type commands:
        :return: False, None
        :rtype: bool | None
        TODO: need recode this fonction because return types are too many
        """
        if self.con is None:
            self.create_connection()
        if not self.alive:
            return None
        logger.debug("[SchedulerLink] Sending %d commands", len(commands))
        try:
            self.con.post('run_external_commands', {'cmds': commands})
        except HTTPEXCEPTIONS, exp:
            self.con = None
            logger.debug(exp)
            return False
Exemplo n.º 4
0
class SchedulerLink(SatelliteLink):
    """
    Class to manage the scheduler information
    """

    # Ok we lie a little here because we are a mere link in fact
    my_type = 'scheduler'

    properties = SatelliteLink.properties.copy()
    properties.update({
        'type':
        StringProp(default=u'scheduler',
                   fill_brok=['full_status'],
                   to_send=True),
        'scheduler_name':
        StringProp(default='', fill_brok=['full_status']),
        'port':
        IntegerProp(default=7768, fill_brok=['full_status'], to_send=True),
        'weight':
        IntegerProp(default=1, fill_brok=['full_status']),
        'skip_initial_broks':
        BoolProp(default=False, fill_brok=['full_status'], to_send=True),
        'accept_passive_unknown_check_results':
        BoolProp(default=False, fill_brok=['full_status'], to_send=True),
    })

    running_properties = SatelliteLink.running_properties.copy()
    running_properties.update({
        # 'conf':
        #     StringProp(default=None),
        # 'cfg':
        #     DictProp(default={}),
        'need_conf': StringProp(default=True),
        'external_commands': StringProp(default=[]),
    })

    def get_override_configuration(self):
        """
        Some parameters can give as 'overridden parameters' like use_timezone
        so they will be mixed (in the scheduler) with the standard conf sent by the arbiter

        :return: dictionary of properties
        :rtype: dict
        """
        res = {}
        properties = self.__class__.properties
        for prop, entry in list(properties.items()):
            if entry.override:
                res[prop] = getattr(self, prop)
        return res
Exemplo n.º 5
0
class BrokerLink(SatelliteLink):
    """
    Class to manage the broker information
    """
    my_type = 'broker'
    properties = SatelliteLink.properties.copy()
    properties.update({
        'type':
        StringProp(default=u'broker', fill_brok=['full_status'], to_send=True),
        'broker_name':
        StringProp(default='', fill_brok=['full_status']),
        'port':
        IntegerProp(default=7772, fill_brok=['full_status'], to_send=True),
        'initialized':
        BoolProp(default=False, fill_brok=['full_status'], to_send=True),
    })

    def prepare_for_conf(self):
        """Initialize the pushed configuration dictionary
        with the inner properties that are to be propagated to the satellite link.

        :return: None
        """
        super(BrokerLink, self).prepare_for_conf()

        self.cfg.update({
            'satellites': {
                'receivers': {},
                'pollers': {},
                'reactionners': {}
            }
        })
Exemplo n.º 6
0
class EventHandler(Action):
    """Notification class, inherits from action class. Used to execute action
    when a host or a service is in a bad state

    """

    # AutoSlots create the __slots__ with properties and
    # running_properties names
    __metaclass__ = AutoSlots

    my_type = 'eventhandler'

    properties = Action.properties.copy()
    properties.update({
        'is_a': StringProp(default=u'eventhandler'),
        'is_snapshot': BoolProp(default=False),
    })

    def __init__(self, params=None, parsing=False):
        super(EventHandler, self).__init__(params, parsing=parsing)

        self.fill_default()

        # An event handler mus be launched as soon as possible
        self.t_to_go = time.time()

    def __str__(self):  # pragma: no cover
        return "Event Handler %s, item: %s, status: %s command: %s" \
               % (self.uuid, self.ref, self.status, self.command)

    def get_return_from(self, e_handler):
        """Setter of the following attributes::

        * exit_status
        * output
        * long_output
        * check_time
        * execution_time
        * perf_data

        :param e_handler: event handler to get data from
        :type e_handler: alignak.eventhandler.EventHandler
        :return: None
        """
        for prop in [
                'exit_status', 'output', 'long_output', 'check_time',
                'execution_time', 'perf_data'
        ]:
            setattr(self, prop, getattr(e_handler, prop))

    def get_outputs(self, out, max_plugins_output_length):
        """Setter of output attribute

        :param out: new output
        :type out: str
        :param max_plugins_output_length: not used
        :type max_plugins_output_length: int
        :return: None
        """
        self.output = out
Exemplo n.º 7
0
class Comment(AlignakObject):
    """Comment class implements comments for monitoring purpose.
    It contains data like author, type etc..
    """

    my_type = 'comment'
    properties = {
        'entry_time':   IntegerProp(),
        'author':       StringProp(default='(Alignak)'),
        'comment':      StringProp(default='Automatic Comment'),
        'comment_type': IntegerProp(),
        'entry_type':   IntegerProp(),
        'source':       IntegerProp(),
        'expires':      BoolProp(),
        'ref':  StringProp(default=''),
    }

    def __init__(self, params, parsing=True):
        """Adds a comment to a particular service.

        :param ref: reference object (host / service)
        :type ref: alignak.object.schedulingitem.SchedulingItem
        :param author: Author of this comment
        :type author: str
        :param comment: text comment itself
        :type comment: str
        :param comment_type: comment type ::

                            * 1 <=> HOST_COMMENT
                            * 2 <=> SERVICE_COMMENT

        :type comment_type: int
        :param entry_type: type of entry linked to this comment ::

                          * 1 <=> USER_COMMENT
                          * 2 <=>DOWNTIME_COMMENT
                          * 3 <=>FLAPPING_COMMENT
                          * 4 <=>ACKNOWLEDGEMENT_COMMENT

        :type entry_type: int
        :param source: source of this comment ::

                      * 0 <=> COMMENTSOURCE_INTERNAL
                      * 1 <=> COMMENTSOURCE_EXTERNAL

        :type source: int
        :param expires: comment expires or not
        :type expires: bool
        :return: None
        """
        super(Comment, self).__init__(params, parsing)
        if not hasattr(self, 'entry_time'):
            self.entry_time = int(time.time())
        self.fill_default()

    def __str__(self):
        return "Comment id=%s %s" % (self.uuid, self.comment)
Exemplo n.º 8
0
class ReceiverLink(SatelliteLink):
    """
    Class to manage the receiver information
    """
    my_type = 'receiver'
    properties = SatelliteLink.properties.copy()
    properties.update({
        'receiver_name':      StringProp(fill_brok=['full_status'], to_send=True),
        'port':               IntegerProp(default=7772, fill_brok=['full_status']),
        'manage_sub_realms':  BoolProp(default=True, fill_brok=['full_status']),
        'manage_arbiters':    BoolProp(default=False, fill_brok=['full_status'], to_send=True),
        'accept_passive_unknown_check_results': BoolProp(default=False,
                                                         fill_brok=['full_status'], to_send=True),
    })

    def register_to_my_realm(self):  # pragma: no cover, seems not to be used anywhere
        """
        Add this reactionner to the realm

        :return: None
        """
        self.realm.receivers.append(self)
Exemplo n.º 9
0
class Hostdependency(Item):
    """Hostdependency class is a simple implementation of host dependency as
    defined in a monitoring context (dependency period, notification_failure_criteria ..)

    """
    _id = 0
    my_type = 'hostdependency'

    # F is dep of D
    # host_name                      Host B
    #       service_description             Service D
    #       dependent_host_name             Host C
    #       dependent_service_description   Service F
    #       execution_failure_criteria      o
    #       notification_failure_criteria   w,u
    #       inherits_parent         1
    #       dependency_period       24x7

    properties = Item.properties.copy()
    properties.update({
        'dependent_host_name':           StringProp(),
        'dependent_hostgroup_name':      StringProp(default=''),
        'host_name':                     StringProp(),
        'hostgroup_name':                StringProp(default=''),
        'inherits_parent':               BoolProp(default=False),
        'execution_failure_criteria':    ListProp(default=['n'], split_on_coma=True),
        'notification_failure_criteria': ListProp(default=['n'], split_on_coma=True),
        'dependency_period':             StringProp(default='')
    })

    def get_name(self):
        """Get name based on dependent_host_name and host_name attributes
        Each attribute is substituted by 'unknown' if attribute does not exist

        :return: dependent_host_name/host_name
        :rtype: str
        """
        dependent_host_name = 'unknown'
        if getattr(self, 'dependent_host_name', None):
            dependent_host_name = getattr(
                getattr(self, 'dependent_host_name'), 'host_name', 'unknown'
            )
        host_name = 'unknown'
        if getattr(self, 'host_name', None):
            host_name = getattr(getattr(self, 'host_name'), 'host_name', 'unknown')
        return dependent_host_name + '/' + host_name
Exemplo n.º 10
0
class Contact(Item):
    """Host class implements monitoring concepts for contact.
    For example it defines host_notification_period, service_notification_period etc.
    """
    _id = 1  # zero is always special in database, so we do not take risk here
    my_type = 'contact'

    properties = Item.properties.copy()
    properties.update({
        'contact_name':     StringProp(fill_brok=['full_status']),
        'alias':            StringProp(default='none', fill_brok=['full_status']),
        'contactgroups':    ListProp(default=[], fill_brok=['full_status']),
        'host_notifications_enabled': BoolProp(default=True, fill_brok=['full_status']),
        'service_notifications_enabled': BoolProp(default=True, fill_brok=['full_status']),
        'host_notification_period': StringProp(fill_brok=['full_status']),
        'service_notification_period': StringProp(fill_brok=['full_status']),
        'host_notification_options': ListProp(default=[''], fill_brok=['full_status'],
                                              split_on_coma=True),
        'service_notification_options': ListProp(default=[''], fill_brok=['full_status'],
                                                 split_on_coma=True),
        # To be consistent with notificationway object attributes
        'host_notification_commands': ListProp(fill_brok=['full_status']),
        'service_notification_commands': ListProp(fill_brok=['full_status']),
        'min_business_impact':    IntegerProp(default=0, fill_brok=['full_status']),
        'email':            StringProp(default='none', fill_brok=['full_status']),
        'pager':            StringProp(default='none', fill_brok=['full_status']),
        'address1':         StringProp(default='none', fill_brok=['full_status']),
        'address2':         StringProp(default='none', fill_brok=['full_status']),
        'address3':        StringProp(default='none', fill_brok=['full_status']),
        'address4':         StringProp(default='none', fill_brok=['full_status']),
        'address5':         StringProp(default='none', fill_brok=['full_status']),
        'address6':         StringProp(default='none', fill_brok=['full_status']),
        'can_submit_commands': BoolProp(default=False, fill_brok=['full_status']),
        'is_admin':         BoolProp(default=False, fill_brok=['full_status']),
        'expert':           BoolProp(default=False, fill_brok=['full_status']),
        'retain_status_information': BoolProp(default=True, fill_brok=['full_status']),
        'notificationways': ListProp(default=[], fill_brok=['full_status']),
        'password':        StringProp(default='NOPASSWORDSET', fill_brok=['full_status']),
    })

    running_properties = Item.running_properties.copy()
    running_properties.update({
        'modified_attributes': IntegerProp(default=0L, fill_brok=['full_status'], retention=True),
        'downtimes': StringProp(default=[], fill_brok=['full_status'], retention=True),
    })
Exemplo n.º 11
0
class Check(Action):  # pylint: disable=R0902
    """Check class implements monitoring concepts of checks :(status, state, output)
    Check instance are used to store monitoring plugins data (exit status, output)
    and used by schedule to raise alert, reschedule check etc.

    """
    # AutoSlots create the __slots__ with properties and
    # running_properties names

    # FIXME : re-enable AutoSlots if possible
    # __metaclass__ = AutoSlots

    my_type = 'check'

    properties = Action.properties.copy()
    properties.update({
        'is_a':
        StringProp(default=u'check'),
        'state':
        IntegerProp(default=0),
        'depend_on':
        ListProp(default=[]),
        'depend_on_me':
        ListProp(default=[], split_on_comma=False),
        'passive_check':
        BoolProp(default=False),
        'freshness_expiry_check':
        BoolProp(default=False),
        'poller_tag':
        StringProp(default=u'None'),
        'dependency_check':
        BoolProp(default=False),
    })

    def __init__(self, params=None, parsing=False):
        super(Check, self).__init__(params, parsing=parsing)

        if self.command.startswith('_'):
            self.internal = True

    def __str__(self):  # pragma: no cover
        return "Check %s %s, item: %s, status: %s, command:'%s'" % \
               (self.uuid, "active" if not self.passive_check else "passive",
                self.ref, self.status, self.command)

    def get_return_from(self, check):
        """Update check data from action (notification for instance)

        :param check: action to get data from
        :type check: alignak.action.Action
        :return: None
        """
        for prop in [
                'exit_status', 'output', 'long_output', 'check_time',
                'execution_time', 'perf_data', 'u_time', 's_time'
        ]:
            setattr(self, prop, getattr(check, prop))

    def set_type_active(self):
        """Set this check as an active one (indeed, not passive)

        :return: None
        """
        self.passive_check = False

    def set_type_passive(self):
        """Set this check as a passive one

        :return: None
        """
        self.passive_check = True

    def is_dependent(self):
        """Getter for dependency_check attribute

        :return: True if this check was created for a dependent one, False otherwise
        :rtype: bool
        """
        return self.dependency_check

    def serialize(self):
        """This function serializes into a simple dict object.

        The only usage is to send to poller, and it does not need to have the
        depend_on and depend_on_me properties.

        :return: json representation of a Check
        :rtype: dict
        """
        res = super(Check, self).serialize()
        if 'depend_on' in res:
            del res['depend_on']
        if 'depend_on_me' in res:
            del res['depend_on_me']
        return res
Exemplo n.º 12
0
class Broker(BaseSatellite):
    """
    Class to manage a Broker daemon
    A Broker is used to get data from Scheduler and send them to modules. These modules in most
    cases export to other software, databases...
    """
    properties = BaseSatellite.properties.copy()
    properties.update({
        'type': StringProp(default='broker'),
        'port': IntegerProp(default=7772),
        'got_initial_broks': BoolProp(default=False)
    })

    def __init__(self, **kwargs):
        """Broker daemon initialisation

        :param kwargs: command line arguments
        """
        super(Broker,
              self).__init__(kwargs.get('daemon_name', 'Default-broker'),
                             **kwargs)

        # Our schedulers and arbiters are initialized in the base class

        # Our pollers, reactionners and receivers
        self.pollers = {}
        self.reactionners = {}
        self.receivers = {}

        # Modules are load one time
        self.have_modules = False

        # All broks to manage
        self.external_broks = []  # broks to manage

        # broks raised internally by the broker
        self.internal_broks = []
        # broks raised by the arbiters, we need a lock so the push can be in parallel
        # to our current activities and won't lock the arbiter
        self.arbiter_broks = []
        self.arbiter_broks_lock = threading.RLock()

        self.timeout = 1.0

        self.http_interface = BrokerInterface(self)

    def add(self, elt):
        """Generic function to add objects to the daemon internal lists.
        Manage Broks, External commands and Messages (from modules queues)

        :param elt: object to add
        :type elt: alignak.AlignakObject
        :return: None
        """
        if isinstance(elt, Brok):
            # For brok, we tag the brok with our instance_id
            elt.instance_id = self.instance_id
            if elt.type == 'monitoring_log':
                # The brok is a monitoring event
                with self.events_lock:
                    self.events.append(elt)
                statsmgr.counter('events', 1)
            else:
                with self.broks_lock:
                    self.broks.append(elt)
            statsmgr.counter('broks.added', 1)
        elif isinstance(elt, ExternalCommand):
            logger.debug("Queuing an external command '%s'", str(elt.__dict__))
            with self.external_commands_lock:
                self.external_commands.append(elt)
                statsmgr.counter('external-commands.added', 1)
        # Maybe we got a Message from the modules, it's way to ask something
        # like from now a full data from a scheduler for example.
        elif isinstance(elt, Message):
            # We got a message, great!
            logger.debug(str(elt.__dict__))
            if elt.get_type() == 'NeedData':
                data = elt.get_data()
                # Full instance id means: I got no data for this scheduler
                # so give me all dumb-ass!
                if 'full_instance_id' in data:
                    c_id = data['full_instance_id']
                    source = getattr(elt, 'source',
                                     getattr(elt, '_source', None))
                    logger.info(
                        'The module %s is asking me to get all initial data '
                        'from the scheduler %d', source, c_id)
                    # so we just reset the connection and the running_id,
                    # it will just get all new things
                    try:
                        self.schedulers[c_id]['con'] = None
                        self.schedulers[c_id]['running_id'] = 0
                    except KeyError:  # maybe this instance was not known, forget it
                        logger.warning(
                            "the module %s ask me a full_instance_id "
                            "for an unknown ID (%d)!", source, c_id)
            # Maybe a module tells me that it's dead, I must log its last words...
            if elt.get_type() == 'ICrash':
                data = elt.get_data()
                logger.error(
                    'the module %s just crash! Please look at the traceback:',
                    data['name'])
                logger.error(data['trace'])

            statsmgr.counter('message.added', 1)
            # The module death will be looked for elsewhere and restarted.

    def manage_brok(self, brok):
        """Get a brok.
        We put brok data to the modules

        :param brok: object with data
        :type brok: object
        :return: None
        """
        # Unserialize the brok before consuming it
        brok.prepare()

        for module in self.modules_manager.get_internal_instances():
            try:
                _t0 = time.time()
                module.manage_brok(brok)
                statsmgr.timer('manage-broks.internal.%s' % module.get_name(),
                               time.time() - _t0)
            except Exception as exp:  # pylint: disable=broad-except
                logger.warning(
                    "The module %s raised an exception: %s, "
                    "I'm tagging it to restart later", module.get_name(),
                    str(exp))
                logger.exception(exp)
                self.modules_manager.set_to_restart(module)

    def get_internal_broks(self):
        """Get all broks from self.broks_internal_raised and append them to our broks
        to manage

        :return: None
        """
        statsmgr.gauge('get-new-broks-count.broker', len(self.internal_broks))
        # Add the broks to our global list
        self.external_broks.extend(self.internal_broks)
        self.internal_broks = []

    def get_arbiter_broks(self):
        """Get the broks from the arbiters,
        but as the arbiter_broks list can be push by arbiter without Global lock,
        we must protect this with a lock

        TODO: really? check this arbiter behavior!

        :return: None
        """
        with self.arbiter_broks_lock:
            statsmgr.gauge('get-new-broks-count.arbiter',
                           len(self.arbiter_broks))
            # Add the broks to our global list
            self.external_broks.extend(self.arbiter_broks)
            self.arbiter_broks = []

    def get_new_broks(self):
        """Get new broks from our satellites

        :return: None
        """
        for satellites in [
                self.schedulers, self.pollers, self.reactionners,
                self.receivers
        ]:
            for satellite_link in list(satellites.values()):
                logger.debug("Getting broks from %s", satellite_link)

                _t0 = time.time()
                try:
                    tmp_broks = satellite_link.get_broks(self.name)
                except LinkError:
                    logger.warning(
                        "Daemon %s connection failed, I could not get the broks!",
                        satellite_link)
                else:
                    if tmp_broks:
                        logger.debug("Got %d Broks from %s in %s",
                                     len(tmp_broks), satellite_link.name,
                                     time.time() - _t0)
                        statsmgr.gauge(
                            'get-new-broks-count.%s' % (satellite_link.name),
                            len(tmp_broks))
                        statsmgr.timer(
                            'get-new-broks-time.%s' % (satellite_link.name),
                            time.time() - _t0)
                        for brok in tmp_broks:
                            brok.instance_id = satellite_link.instance_id

                        # Add the broks to our global list
                        self.external_broks.extend(tmp_broks)

    # def do_stop(self):
    #     """Stop all children of this process
    #
    #     :return: None
    #     """
    #     # my_active_children = active_children()
    #     # for child in my_active_children:
    #     #     child.terminate()
    #     #     child.join(1)
    #     super(Broker, self).do_stop()

    def setup_new_conf(self):
        # pylint: disable=too-many-branches, too-many-locals
        """Broker custom setup_new_conf method

        This function calls the base satellite treatment and manages the configuration needed
        for a broker daemon:
        - get and configure its pollers, reactionners and receivers relation
        - configure the modules

        :return: None
        """
        # Execute the base class treatment...
        super(Broker, self).setup_new_conf()

        # ...then our own specific treatment!
        with self.conf_lock:
            # # self_conf is our own configuration from the alignak environment
            # self_conf = self.cur_conf['self_conf']
            self.got_initial_broks = False

            # Now we create our pollers, reactionners and receivers
            for link_type in ['pollers', 'reactionners', 'receivers']:
                if link_type not in self.cur_conf['satellites']:
                    logger.error("No %s in the configuration!", link_type)
                    continue

                my_satellites = getattr(self, link_type, {})
                received_satellites = self.cur_conf['satellites'][link_type]
                for link_uuid in received_satellites:
                    rs_conf = received_satellites[link_uuid]
                    logger.debug("- received %s - %s: %s",
                                 rs_conf['instance_id'], rs_conf['type'],
                                 rs_conf['name'])

                    # Must look if we already had a configuration and save our broks
                    already_got = rs_conf['instance_id'] in my_satellites
                    broks = []
                    actions = {}
                    wait_homerun = {}
                    external_commands = {}
                    running_id = 0
                    if already_got:
                        logger.warning("I already got: %s",
                                       rs_conf['instance_id'])
                        # Save some information
                        running_id = my_satellites[link_uuid].running_id
                        (broks, actions,
                         wait_homerun, external_commands) = \
                            my_satellites[link_uuid].get_and_clear_context()
                        # Delete the former link
                        del my_satellites[link_uuid]

                    # My new satellite link...
                    new_link = SatelliteLink.get_a_satellite_link(
                        link_type[:-1], rs_conf)
                    my_satellites[new_link.uuid] = new_link
                    logger.info("I got a new %s satellite: %s", link_type[:-1],
                                new_link)

                    new_link.running_id = running_id
                    new_link.external_commands = external_commands
                    new_link.broks = broks
                    new_link.wait_homerun = wait_homerun
                    new_link.actions = actions

                    # Replace satellite address and port by those defined in satellite_map
                    # todo: check if it is really necessary! Add a unit test for this
                    # Not sure about this because of the daemons/satellites configuration
                    # if new_link.name in self_conf.get('satellite_map', {}):
                    #     new_link = dict(new_link)  # make a copy
                    #     new_link.update(self_conf.get('satellite_map', {})[new_link.name])

            if not self.have_modules:
                try:
                    self.modules = unserialize(self.cur_conf['modules'],
                                               no_load=True)
                except AlignakClassLookupException as exp:  # pragma: no cover, simple protection
                    logger.error(
                        'Cannot un-serialize modules configuration '
                        'received from arbiter: %s', exp)
                if self.modules:
                    logger.info("I received some modules configuration: %s",
                                self.modules)
                    self.have_modules = True

                    # Ok now start, or restart them!
                    # Set modules, init them and start external ones
                    self.do_load_modules(self.modules)
                    # and start external modules too
                    self.modules_manager.start_external_instances()
                else:
                    logger.info("I do not have modules")

            # Initialize connection with my schedulers first
            logger.info("Initializing connection with my schedulers:")
            my_satellites = self.get_links_of_type(s_type='scheduler')
            for satellite in list(my_satellites.values()):
                logger.info("- %s/%s", satellite.type, satellite.name)
                if not self.daemon_connection_init(satellite):
                    logger.error("Satellite connection failed: %s", satellite)

            # Initialize connection with all our satellites
            logger.info("Initializing connection with my satellites:")
            for sat_type in ['arbiter', 'reactionner', 'poller', 'receiver']:
                my_satellites = self.get_links_of_type(s_type=sat_type)
                for satellite in list(my_satellites.values()):
                    logger.info("- %s/%s", satellite.type, satellite.name)
                    if not self.daemon_connection_init(satellite):
                        logger.error("Satellite connection failed: %s",
                                     satellite)

        # Now I have a configuration!
        self.have_conf = True

    def clean_previous_run(self):
        """Clean all (when we received new conf)

        :return: None
        """
        # Execute the base class treatment...
        super(Broker, self).clean_previous_run()

        # Clean all satellites relations
        self.pollers.clear()
        self.reactionners.clear()
        self.receivers.clear()

        # Clean our internal objects
        self.external_broks = self.external_broks[:]
        self.internal_broks = self.internal_broks[:]
        with self.arbiter_broks_lock:
            self.arbiter_broks = self.arbiter_broks[:]
        self.external_commands = self.external_commands[:]

        # And now modules
        # self.have_modules = False
        # self.modules_manager.clear_instances()

    def do_loop_turn(self):
        # pylint: disable=too-many-branches
        """Loop used to:
         * get initial status broks
         * check if modules are alive, if not restart them
         * get broks from ourself, the arbiters and our satellites
         * add broks to the queue of each external module
         * manage broks with each internal module

         If the internal broks management is longer than 0.8 seconds, postpone to hte next
         loop turn to avoid overloading the broker daemon.

         :return: None
        """
        if not self.got_initial_broks:
            # Asking initial broks from my schedulers
            my_satellites = self.get_links_of_type(s_type='scheduler')
            for satellite in list(my_satellites.values()):
                logger.info("Asking my initial broks from '%s'",
                            satellite.name)
                _t0 = time.time()
                try:
                    my_initial_broks = satellite.get_initial_broks(self.name)
                    statsmgr.timer('broks.initial.%s.time' % satellite.name,
                                   time.time() - _t0)
                    if not my_initial_broks:
                        logger.info("No initial broks were raised, "
                                    "my scheduler is not yet ready...")
                        return

                    self.got_initial_broks = True
                    logger.debug("Got %d initial broks from '%s'",
                                 my_initial_broks, satellite.name)
                    statsmgr.gauge('broks.initial.%s.count' % satellite.name,
                                   my_initial_broks)
                except LinkError as exp:
                    logger.warning(
                        "Scheduler connection failed, I could not get initial broks!"
                    )

        logger.debug("Begin Loop: still some old broks to manage (%d)",
                     len(self.external_broks))
        if self.external_broks:
            statsmgr.gauge('unmanaged.broks', len(self.external_broks))

        # Try to see if one of my module is dead, and restart previously dead modules
        self.check_and_del_zombie_modules()

        # Call modules that manage a starting tick pass
        _t0 = time.time()
        self.hook_point('tick')
        statsmgr.timer('hook.tick', time.time() - _t0)

        # Maybe the last loop we did raised some broks internally
        self.get_internal_broks()

        # Also reap broks sent from the arbiters
        self.get_arbiter_broks()

        # Now get broks from our distant daemons
        self.get_new_broks()

        # Get the list of broks not yet sent to our external modules
        _t0 = time.time()
        broks_to_send = [
            brok for brok in self.external_broks
            if getattr(brok, 'to_be_sent', True)
        ]
        statsmgr.gauge('get-new-broks-count.to_send', len(broks_to_send))

        # Send the broks to all external modules to_q queue so they can get the whole packet
        # beware, the sub-process/queue can be die/close, so we put to restart the whole module
        # instead of killing ourselves :)
        for module in self.modules_manager.get_external_instances():
            try:
                _t00 = time.time()
                queue_size = module.to_q.qsize()
                statsmgr.gauge(
                    'queues.external.%s.to.size' % module.get_name(),
                    queue_size)
                module.to_q.put(broks_to_send)
                statsmgr.timer('queues.external.%s.to.put' % module.get_name(),
                               time.time() - _t00)
            except Exception as exp:  # pylint: disable=broad-except
                # first we must find the modules
                logger.warning(
                    "Module %s queue exception: %s, I'm tagging it to restart later",
                    module.get_name(), str(exp))
                logger.exception(exp)
                self.modules_manager.set_to_restart(module)

        # No more need to send them
        for brok in broks_to_send:
            brok.to_be_sent = False
        logger.debug("Time to send %s broks (%d secs)", len(broks_to_send),
                     time.time() - _t0)

        # Make the internal modules manage the broks
        start = time.time()
        while self.external_broks:
            now = time.time()
            # Do not 'manage' more than 0.8s, we must get new broks almost every second
            if now - start > 0.8:
                logger.info(
                    "I did not yet managed all my broks, still %d broks",
                    len(self.external_broks))
                break

            # Get the first brok in the list
            brok = self.external_broks.pop(0)
            if self.modules_manager.get_internal_instances():
                self.manage_brok(brok)
                # Make a very short pause to avoid overloading
                self.make_a_pause(0.01, check_time_change=False)
            else:
                if getattr(brok, 'to_be_sent', False):
                    self.external_broks.append(brok)

        # Maybe our external modules raised 'objects', so get them
        if self.get_objects_from_from_queues():
            statsmgr.gauge('external-commands.got.count',
                           len(self.external_commands))
            statsmgr.gauge('broks.got.count', len(self.external_broks))

    def get_daemon_stats(self, details=False):
        """Increase the stats provided by the Daemon base class

        :return: stats dictionary
        :rtype: dict
        """
        # Call the base Daemon one
        res = super(Broker, self).get_daemon_stats(details=details)

        res.update({'name': self.name, 'type': self.type})

        counters = res['counters']
        counters['broks-external'] = len(self.external_broks)
        counters['broks-internal'] = len(self.internal_broks)
        counters['broks-arbiter'] = len(self.arbiter_broks)
        counters['satellites.pollers'] = len(self.pollers)
        counters['satellites.reactionners'] = len(self.reactionners)
        counters['satellites.receivers'] = len(self.receivers)

        return res

    def main(self):
        """Main function, will loop forever

        :return: None
        """
        try:
            # Start the daemon mode
            if not self.do_daemon_init_and_start():
                self.exit_on_error(message="Daemon initialization error",
                                   exit_code=3)

            #  We wait for initial conf
            self.wait_for_initial_conf()
            if self.new_conf:
                # Setup the received configuration
                self.setup_new_conf()

                # Restore retention data
                self.hook_point('load_retention')

                # Now the main loop
                self.do_main_loop()
                logger.info("Exited from the main loop.")

            self.request_stop()
        except Exception:  # pragma: no cover, this should never happen indeed ;)
            self.exit_on_exception(traceback.format_exc())
            raise
Exemplo n.º 13
0
class SatelliteLink(Item):
    """SatelliteLink is a common Class for links between
    Arbiter and other satellites. Used by the Dispatcher object.

    """
    # _id = 0 each Class will have it's own id

    properties = Item.properties.copy()
    properties.update({
        'address':         StringProp(default='localhost', fill_brok=['full_status']),
        'timeout':         IntegerProp(default=3, fill_brok=['full_status']),
        'data_timeout':    IntegerProp(default=120, fill_brok=['full_status']),
        'check_interval':  IntegerProp(default=60, fill_brok=['full_status']),
        'max_check_attempts': IntegerProp(default=3, fill_brok=['full_status']),
        'spare':              BoolProp(default=False, fill_brok=['full_status']),
        'manage_sub_realms':  BoolProp(default=True, fill_brok=['full_status']),
        'manage_arbiters':    BoolProp(default=False, fill_brok=['full_status'], to_send=True),
        'modules':            ListProp(default=[''], to_send=True, split_on_coma=True),
        'polling_interval':   IntegerProp(default=1, fill_brok=['full_status'], to_send=True),
        'use_timezone':       StringProp(default='NOTSET', to_send=True),
        'realm':              StringProp(default='', fill_brok=['full_status'],
                                         brok_transformation=get_obj_name_two_args_and_void),
        'satellitemap':       DictProp(default={}, elts_prop=AddrProp, to_send=True, override=True),
        'use_ssl':             BoolProp(default=False, fill_brok=['full_status']),
        'hard_ssl_name_check': BoolProp(default=True, fill_brok=['full_status']),
        'passive':             BoolProp(default=False, fill_brok=['full_status'], to_send=True),
    })

    running_properties = Item.running_properties.copy()
    running_properties.update({
        'con':                  StringProp(default=None),
        'alive':                BoolProp(default=True, fill_brok=['full_status']),
        'broks':                StringProp(default=[]),

        # the number of failed attempt
        'attempt':              StringProp(default=0, fill_brok=['full_status']),

        # can be network ask or not (dead or check in timeout or error)
        'reachable':            BoolProp(default=False, fill_brok=['full_status']),
        'last_check':           IntegerProp(default=0, fill_brok=['full_status']),
        'managed_confs':        StringProp(default={}),
    })

    def __init__(self, *args, **kwargs):
        super(SatelliteLink, self).__init__(*args, **kwargs)

        self.arb_satmap = {'address': '0.0.0.0', 'port': 0}
        if hasattr(self, 'address'):
            self.arb_satmap['address'] = self.address
        if hasattr(self, 'port'):
            try:
                self.arb_satmap['port'] = int(self.port)
            except Exception:
                pass

    def get_name(self):
        """Get the name of the link based on its type
        if *mytype*_name is an attribute then returns self.*mytype*_name.
        otherwise returns "Unnamed *mytype*"
        Example : self.poller_name or "Unnamed poller"

        :return: String corresponding to the link name
        :rtype: str
        """
        return getattr(self,
                       "{0}_name".format(self.get_my_type()),
                       "Unnamed {0}".format(self.get_my_type()))

    def set_arbiter_satellitemap(self, satellitemap):
        """
            arb_satmap is the satellitemap in current context:
                - A SatelliteLink is owned by an Arbiter
                - satellitemap attribute of SatelliteLink is the map
                  defined IN THE satellite configuration
                  but for creating connections, we need the have the satellitemap of the Arbiter

        :return: None
        """
        self.arb_satmap = {'address': self.address, 'port': self.port, 'use_ssl': self.use_ssl,
                           'hard_ssl_name_check': self.hard_ssl_name_check}
        self.arb_satmap.update(satellitemap)

    def create_connection(self):
        """Initialize HTTP connection with a satellite (con attribute) and
        set uri attribute

        :return: None
        """
        self.con = HTTPClient(address=self.arb_satmap['address'], port=self.arb_satmap['port'],
                              timeout=self.timeout, data_timeout=self.data_timeout,
                              use_ssl=self.use_ssl,
                              strong_ssl=self.hard_ssl_name_check
                              )
        self.uri = self.con.uri

    def put_conf(self, conf):
        """Send the conf (serialized) to the satellite
        HTTP request to the satellite (POST / put_conf)

        :param conf: The conf to send (data depend on the satellite)
        :type conf:
        :return: None
        """
        if self.con is None:
            self.create_connection()

        # Maybe the connection was not ok, bail out
        if not self.con:
            return False

        try:
            self.con.get('ping')
            self.con.post('put_conf', {'conf': conf}, wait='long')
            print "PUT CONF SUCESS", self.get_name()
            return True
        except HTTPEXCEPTIONS, exp:
            self.con = None
            logger.error("Failed sending configuration for %s: %s", self.get_name(), str(exp))
            return False
Exemplo n.º 14
0
class Realm(Itemgroup):
    """Realm class is used to implement realm. It is basically a set of Host or Service
    assigned to a specific set of Scheduler/Poller (other daemon are optional)

    """
    my_type = 'realm'

    properties = Itemgroup.properties.copy()
    properties.update({
        'uuid': StringProp(default='', fill_brok=['full_status']),
        'realm_name': StringProp(fill_brok=['full_status']),
        'alias': StringProp(default=''),
        # No status_broker_name because it put hosts, not host_name
        'realm_members': ListProp(default=[], split_on_coma=True),
        'higher_realms': ListProp(default=[], split_on_coma=True),
        'default': BoolProp(default=False),
    })

    running_properties = Item.running_properties.copy()
    running_properties.update({
        'serialized_confs': DictProp(default={}),
        'unknown_higher_realms': ListProp(default=[]),
        'all_sub_members': ListProp(default=[]),
    })

    macros = {
        'REALMNAME': 'realm_name',
        'REALMMEMBERS': 'members',
    }

    potential_pollers = []
    potential_reactionners = []
    potential_brokers = []
    potential_receivers = []

    def get_name(self):
        """Accessor to realm_name attribute

        :return: realm name
        :rtype: str
        """
        return self.realm_name

    def add_string_member(self, member):
        """Add a realm to all_sub_members attribute

        :param member: realm names to add
        :type member: list
        :return: None
        """
        self.all_sub_members.extend(member)

    def get_realm_members(self):
        """
        Get list of members of this realm

        :return: list of realm (members)
        :rtype: list
        """
        # TODO: consistency: a Realm instance should always have its real_members defined,
        if hasattr(self, 'realm_members'):
            # more over it should already be decoded/parsed to its final type:
            # a list of strings (being the names of the members)
            return [r.strip() for r in self.realm_members]

        return []

    def fill_realm_members_with_higher_realms(self, realms):
        """
        if we have higher_realms defined, fill realm_members of the realm with my realm_name

        :param realms: list of all realms objects
        :type realms: list
        :return: None
        """
        higher_realms = getattr(self, 'higher_realms', [])
        for realm_nane in higher_realms:
            realm = realms.find_by_name(realm_nane.strip())
            if realm is not None:
                if not hasattr(realm, 'realm_members'):
                    realm.realm_members = []
                realm.realm_members.append(self.realm_name)

    def get_realms_by_explosion(self, realms):
        """Get all members of this realm including members of sub-realms on multi-levels

        :param realms: realms list, used to look for a specific one
        :type realms: alignak.objects.realm.Realms
        :return: list of members and add realm to realm_members attribute
        :rtype: list
        """
        # The recursive part
        # rec_tag is set to False every HG we explode
        # so if True here, it must be a loop in HG
        # calls... not GOOD!
        if self.rec_tag:
            err = "Error: we've got a loop in realm definition %s" % self.get_name(
            )
            self.configuration_errors.append(err)
            return None

        # Ok, not a loop, we tag it and continue
        self.rec_tag = True

        # we have yet exploded this realm
        if self.all_sub_members != []:
            return self.all_sub_members

        p_mbrs = self.get_realm_members()
        for p_mbr in p_mbrs:
            realm = realms.find_by_name(p_mbr.strip())
            if realm is not None:
                value = realm.get_realms_by_explosion(realms)
                if value is None:
                    # case loop problem
                    self.all_sub_members = []
                    self.realm_members = []
                    return None
                elif value:
                    self.add_string_member(value)
                self.add_string_member([realm.realm_name])
            else:
                self.add_string_unknown_member(p_mbr.strip())
        return self.all_sub_members

    def get_all_subs_satellites_by_type(self, sat_type, realms):
        """Get all satellites of the wanted type in this realm recursively

        :param sat_type: satellite type wanted (scheduler, poller ..)
        :type sat_type:
        :param realms: all realms
        :type realms: list of realm object
        :return: list of satellite in this realm
        :rtype: list
        TODO: Make this generic
        """
        res = copy.copy(getattr(self, sat_type))
        for member in self.all_sub_members:
            tmps = realms[member].get_all_subs_satellites_by_type(
                sat_type, realms)
            for mem in tmps:
                res.append(mem)
        return res

    def get_satellites_by_type(self, s_type):
        """Generic function to access one of the satellite attribute
        ie : self.pollers, self.reactionners ...

        :param s_type: satellite type wanted
        :type s_type: str
        :return: self.*type*s
        :rtype: list
        """

        if hasattr(self, s_type + 's'):
            return getattr(self, s_type + 's')

        logger.debug("[realm] do not have this kind of satellites: %s", s_type)
        return []

    def get_potential_satellites_by_type(self, s_type):
        """Generic function to access one of the potential satellite attribute
        ie : self.potential_pollers, self.potential_reactionners ...

        :param s_type: satellite type wanted
        :type s_type: str
        :return: self.potential_*type*s
        :rtype: list
        """
        if hasattr(self, 'potential_' + s_type + 's'):
            return getattr(self, 'potential_' + s_type + 's')

        logger.debug("[realm] do not have this kind of satellites: %s", s_type)
        return []

    def get_nb_of_must_have_satellites(self, s_type):
        """Generic function to access one of the number satellite attribute
        ie : self.nb_pollers, self.nb_reactionners ...

        :param s_type: satellite type wanted
        :type s_type: str
        :return: self.nb_*type*s
        :rtype: int
        """
        if hasattr(self, 'nb_' + s_type + 's'):
            return getattr(self, 'nb_' + s_type + 's')

        logger.debug("[realm] do not have this kind of satellites: %s", s_type)
        return 0

    def fill_broker_with_poller_reactionner_links(self, broker, pollers,
                                                  reactionners, receivers,
                                                  realms):
        """Fill brokerlink object with satellite data

        :param broker: broker link we want to fill
        :type broker: alignak.objects.brokerlink.Brokerlink
        :param pollers: pollers
        :type pollers:
        :param reactionners: reactionners
        :type reactionners:
        :param receivers: receivers
        :type receivers:
        :param realms: realms
        :type realms:
        :return: None
        """

        # TODO: find a better name...
        # TODO: and if he goes active?
        # First we create/void theses links
        broker.cfg['pollers'] = {}
        broker.cfg['reactionners'] = {}
        broker.cfg['receivers'] = {}

        # First our own level
        for poller_id in self.pollers:
            poller = pollers[poller_id]
            cfg = poller.give_satellite_cfg()
            broker.cfg['pollers'][poller.uuid] = cfg

        for reactionner_id in self.reactionners:
            reactionner = reactionners[reactionner_id]
            cfg = reactionner.give_satellite_cfg()
            broker.cfg['reactionners'][reactionner.uuid] = cfg

        for receiver_id in self.receivers:
            receiver = receivers[receiver_id]
            cfg = receiver.give_satellite_cfg()
            broker.cfg['receivers'][receiver.uuid] = cfg

        # Then sub if we must to it
        if broker.manage_sub_realms:
            # Now pollers
            for poller_id in self.get_all_subs_satellites_by_type(
                    'pollers', realms):
                poller = pollers[poller_id]
                cfg = poller.give_satellite_cfg()
                broker.cfg['pollers'][poller.uuid] = cfg

            # Now reactionners
            for reactionner_id in self.get_all_subs_satellites_by_type(
                    'reactionners', realms):
                reactionner = reactionners[reactionner_id]
                cfg = reactionner.give_satellite_cfg()
                broker.cfg['reactionners'][reactionner.uuid] = cfg

            # Now receivers
            for receiver_id in self.get_all_subs_satellites_by_type(
                    'receivers', realms):
                receiver = receivers[receiver_id]
                cfg = receiver.give_satellite_cfg()
                broker.cfg['receivers'][receiver.uuid] = cfg

    def get_satellites_links_for_scheduler(self, pollers, reactionners,
                                           brokers):
        """Get a configuration dict with pollers, reactionners and brokers data for scheduler

        :return: dict containing pollers, reactionners and brokers config (key is satellite id)
        :rtype: dict
        """

        # First we create/void theses links
        cfg = {
            'pollers': {},
            'reactionners': {},
            'brokers': {},
        }

        # First our own level
        for poller_id in self.pollers:
            poller = pollers[poller_id]
            config = poller.give_satellite_cfg()
            cfg['pollers'][poller.uuid] = config

        for reactionner_id in self.reactionners:
            reactionner = reactionners[reactionner_id]
            config = reactionner.give_satellite_cfg()
            cfg['reactionners'][reactionner.uuid] = config

        for broker_id in self.brokers:
            broker = brokers[broker_id]
            config = broker.give_satellite_cfg()
            cfg['brokers'][broker.uuid] = config

        return cfg
Exemplo n.º 15
0
class Satellite(BaseSatellite):  # pylint: disable=R0902
    """Satellite class.
    Sub-classed by Receiver, Reactionner and Poller

    """
    do_checks = False
    do_actions = False
    my_type = ''

    properties = BaseSatellite.properties.copy()
    properties.update({
        'passive':
            BoolProp(default=False),
        'max_plugins_output_length':
            IntegerProp(default=8192),
        'min_workers':
            IntegerProp(default=0, fill_brok=['full_status'], to_send=True),
        'max_workers':
            IntegerProp(default=0, fill_brok=['full_status'], to_send=True),
        'processes_by_worker':
            IntegerProp(default=256, fill_brok=['full_status'], to_send=True),
        'worker_polling_interval':
            IntegerProp(default=1, to_send=True),
        'poller_tags':
            ListProp(default=['None'], to_send=True),
        'reactionner_tags':
            ListProp(default=['None'], to_send=True),
    })

    def __init__(self, name, **kwargs):
        super(Satellite, self).__init__(name, **kwargs)

        # Move these properties to the base Daemon ?
        # todo: change this?
        # Keep broks so they can be eaten by a broker
        self.broks = []
        self.broks_lock = threading.RLock()

        # My active workers
        self.workers = {}

        # May be we are a passive daemon
        if self.passive:
            self.pre_log.append(("INFO", "Passive mode enabled."))

        # Our tags
        # ['None'] is the default tags
        if self.type in ['poller'] and self.poller_tags:
            self.pre_log.append(("INFO", "Poller tags: %s" % self.poller_tags))
        if self.type in ['reactionner'] and self.reactionner_tags:
            self.pre_log.append(("INFO", "Reactionner tags: %s" % self.reactionner_tags))

        # Now the limit part, 0 means the number of cpu of this machine :)
        cpu_count = psutil.cpu_count()
        # Do not use the logger in this function because it is not yet initialized...
        self.pre_log.append(("INFO",
                             "Detected %d CPUs" % cpu_count))
        if self.max_workers == 0:
            try:
                # Preserve one CPU if more than one detected
                self.max_workers = max(cpu_count - 1, 1)
            except NotImplementedError:  # pragma: no cover, simple protection
                self.max_workers = 1
        if self.min_workers == 0:
            try:
                self.min_workers = max(cpu_count - 1, 1)
            except NotImplementedError:  # pragma: no cover, simple protection
                self.min_workers = 1
        self.pre_log.append(("INFO",
                             "Using minimum %d workers, maximum %d workers, %d processes/worker"
                             % (self.min_workers, self.max_workers, self.processes_by_worker)))

        self.slave_q = None

        self.returns_queue = None
        self.q_by_mod = {}

        # Modules are only loaded one time
        self.have_modules = False

        # round robin queue ic
        self.rr_qid = 0

    def manage_action_return(self, action):
        """Manage action return from Workers
        We just put them into the corresponding sched
        and we clean unused properties like my_scheduler

        :param action: the action to manage
        :type action: alignak.action.Action
        :return: None
        """
        # Maybe our workers send us something else than an action
        # if so, just add this in other queues and return
        # todo: test a class instance
        if action.__class__.my_type not in ['check', 'notification', 'eventhandler']:
            self.add(action)
            return

        # Ok, it's a result. Get the concerned scheduler uuid
        scheduler_uuid = action.my_scheduler
        logger.debug("Got action return: %s / %s", scheduler_uuid, action.uuid)

        try:
            # Now that we know where to put the action result, we do not need any reference to
            # the scheduler nor the worker
            del action.my_scheduler
            del action.my_worker
        except AttributeError:  # pragma: no cover, simple protection
            logger.error("AttributeError Got action return: %s / %s", scheduler_uuid, action)

        # And we remove it from the actions queue of the scheduler too
        try:
            del self.schedulers[scheduler_uuid].actions[action.uuid]
        except KeyError as exp:
            logger.error("KeyError del scheduler action: %s / %s - %s",
                         scheduler_uuid, action.uuid, str(exp))

        # We tag it as "return wanted", and move it in the wait return queue
        try:
            self.schedulers[scheduler_uuid].wait_homerun[action.uuid] = action
        except KeyError:  # pragma: no cover, simple protection
            logger.error("KeyError Add home run action: %s / %s - %s",
                         scheduler_uuid, action.uuid, str(exp))

    def push_results(self):
        """Push the checks/actions results to our schedulers

        :return: None
        """
        # For all schedulers, we check for wait_homerun
        # and we send back results
        for scheduler_link_uuid in self.schedulers:
            scheduler_link = self.schedulers[scheduler_link_uuid]
            if not scheduler_link.active:
                logger.warning("My scheduler '%s' is not active currently", scheduler_link.name)
                continue

            if not scheduler_link.wait_homerun:
                # Nothing to push back...
                continue

            # NB: it's **mostly** safe for us to not use some lock around
            # this 'results' / sched['wait_homerun'].
            # Because it can only be modified (for adding new values) by the
            # same thread running this function (that is the main satellite
            # thread), and this occurs exactly in self.manage_action_return().
            # Another possibility is for the sched['wait_homerun'] to be
            # cleared within/by :
            # ISchedulers.get_results() -> Satelitte.get_return_for_passive()
            # This can so happen in an (http) client thread.
            results = scheduler_link.wait_homerun
            logger.debug("Pushing %d results to '%s'", len(results), scheduler_link.name)

            # So, at worst, some results would be received twice on the
            # scheduler level, which shouldn't be a problem given they are
            # indexed by their "action_id".

            scheduler_link.push_results(list(results.values()), self.name)
            results.clear()

    def create_and_launch_worker(self, module_name='fork'):
        """Create and launch a new worker, and put it into self.workers
         It can be mortal or not

        :param module_name: the module name related to the worker
                            default is "fork" for no module
                            Indeed, it is actually the module 'python_name'
        :type module_name: str
        :return: None
        """
        logger.info("Allocating new '%s' worker...", module_name)

        # If we are in the fork module, we do not specify a target
        target = None
        __warned = []
        if module_name == 'fork':
            target = None
        else:
            for module in self.modules_manager.instances:
                # First, see if the module name matches...
                if module.get_name() == module_name:
                    # ... and then if is a 'worker' module one or not
                    if not module.properties.get('worker_capable', False):
                        raise NotWorkerMod
                    target = module.work
            if target is None:
                if module_name not in __warned:
                    logger.warning("No target found for %s, NOT creating a worker for it...",
                                   module_name)
                    __warned.append(module_name)
                return
        # We give to the Worker the instance name of the daemon (eg. poller-master)
        # and not the daemon type (poller)
        queue = Queue()
        worker = Worker(module_name, queue, self.returns_queue, self.processes_by_worker,
                        max_plugins_output_length=self.max_plugins_output_length,
                        target=target, loaded_into=self.name)
        # worker.module_name = module_name
        # save this worker
        self.workers[worker.get_id()] = worker

        # And save the Queue of this worker, with key = worker id
        # self.q_by_mod[module_name][worker.uuid] = queue
        self.q_by_mod[module_name][worker.get_id()] = queue

        # Ok, all is good. Start it!
        worker.start()

        logger.info("Started '%s' worker: %s (pid=%d)",
                    module_name, worker.get_id(), worker.get_pid())

    def do_stop_workers(self):
        """Stop all workers

        :return: None
        """
        logger.info("Stopping all workers (%d)", len(self.workers))
        for worker in list(self.workers.values()):
            try:
                logger.info(" - stopping '%s'", worker.get_id())
                worker.terminate()
                worker.join(timeout=1)
                logger.info("stopped")
            # A already dead worker or in a worker
            except (AttributeError, AssertionError):
                pass
            except Exception as exp:  # pylint: disable=broad-except
                logger.error("exception: %s", str(exp))

    def do_stop(self):
        """Stop my workers and stop

        :return: None
        """
        self.do_stop_workers()

        super(Satellite, self).do_stop()

    def add(self, elt):
        """Generic function to add objects to the daemon internal lists.
        Manage Broks, External commands

        :param elt: object to add
        :type elt: alignak.AlignakObject
        :return: None
        """
        if isinstance(elt, Brok):
            # For brok, we tag the brok with our instance_id
            elt.instance_id = self.instance_id
            if elt.type == 'monitoring_log':
                # The brok is a monitoring event
                with self.events_lock:
                    self.events.append(elt)
                statsmgr.counter('events', 1)
            else:
                with self.broks_lock:
                    self.broks.append(elt)
            statsmgr.counter('broks.added', 1)
        elif isinstance(elt, ExternalCommand):
            logger.debug("Queuing an external command '%s'", str(elt.__dict__))
            with self.external_commands_lock:
                self.external_commands.append(elt)
            statsmgr.counter('external-commands.added', 1)

    def get_broks(self):
        """Get brok list from satellite

        :return: A copy of the broks list
        :rtype: list
        """
        res = copy.copy(self.broks)
        del self.broks[:]
        return res

    def check_and_del_zombie_workers(self):  # pragma: no cover, not with unit tests...
        # pylint: disable= not-callable
        """Check if worker are fine and kill them if not.
        Dispatch the actions in the worker to another one

        TODO: see if unit tests would allow to check this code?

        :return: None
        """
        # Active children make a join with everyone, useful :)
        # active_children()
        for p in active_children():
            logger.debug("got child: %s", p)

        w_to_del = []
        for worker in list(self.workers.values()):
            # If a worker goes down and we did not ask him, it's not
            # good: we can think that we have a worker and it's not True
            # So we del it
            logger.debug("checking if worker %s (pid=%d) is alive",
                         worker.get_id(), worker.get_pid())
            if not self.interrupted and not worker.is_alive():
                logger.warning("The worker %s (pid=%d) went down unexpectedly!",
                               worker.get_id(), worker.get_pid())
                # Terminate immediately
                worker.terminate()
                worker.join(timeout=1)
                w_to_del.append(worker.get_id())

        # OK, now really del workers from queues
        # And requeue the actions it was managed
        for worker_id in w_to_del:
            worker = self.workers[worker_id]

            # Del the queue of the module queue
            del self.q_by_mod[worker.module_name][worker.get_id()]

            for scheduler_uuid in self.schedulers:
                sched = self.schedulers[scheduler_uuid]
                for act in list(sched.actions.values()):
                    if act.status == ACT_STATUS_QUEUED and act.my_worker == worker_id:
                        # Got a check that will NEVER return if we do not restart it
                        self.assign_to_a_queue(act)

            # So now we can really forgot it
            del self.workers[worker_id]

    def adjust_worker_number_by_load(self):
        """Try to create the minimum workers specified in the configuration

        :return: None
        """
        if self.interrupted:
            logger.debug("Trying to adjust worker number. Ignoring because we are stopping.")
            return

        to_del = []
        logger.debug("checking worker count."
                     " Currently: %d workers, min per module : %d, max per module : %d",
                     len(self.workers), self.min_workers, self.max_workers)

        # I want at least min_workers by module then if I can, I add worker for load balancing
        for mod in self.q_by_mod:
            # At least min_workers
            todo = max(0, self.min_workers - len(self.q_by_mod[mod]))
            for _ in range(todo):
                try:
                    self.create_and_launch_worker(module_name=mod)
                # Maybe this modules is not a true worker one.
                # if so, just delete if from q_by_mod
                except NotWorkerMod:
                    to_del.append(mod)
                    break

        for mod in to_del:
            logger.warning("The module %s is not a worker one, I remove it from the worker list.",
                           mod)
            del self.q_by_mod[mod]
        # TODO: if len(workers) > 2*wish, maybe we can kill a worker?

    def _get_queue_for_the_action(self, action):
        """Find action queue for the action depending on the module.
        The id is found with action modulo on action id

        :param a: the action that need action queue to be assigned
        :type action: object
        :return: worker id and queue. (0, None) if no queue for the module_type
        :rtype: tuple
        """
        # get the module name, if not, take fork
        mod = getattr(action, 'module_type', 'fork')
        queues = list(self.q_by_mod[mod].items())

        # Maybe there is no more queue, it's very bad!
        if not queues:
            return (0, None)

        # if not get action round robin index to get action queue based
        # on the action id
        self.rr_qid = (self.rr_qid + 1) % len(queues)
        (worker_id, queue) = queues[self.rr_qid]

        # return the id of the worker (i), and its queue
        return (worker_id, queue)

    def add_actions(self, actions_list, scheduler_instance_id):
        """Add a list of actions to the satellite queues

        :param actions_list: Actions list to add
        :type actions_list: list
        :param scheduler_instance_id: sheduler link to assign the actions to
        :type scheduler_instance_id: SchedulerLink
        :return: None
        """
        # We check for new check in each schedulers and put the result in new_checks
        scheduler_link = None
        for scheduler_id in self.schedulers:
            logger.debug("Trying to add an action, scheduler: %s", self.schedulers[scheduler_id])
            if scheduler_instance_id == self.schedulers[scheduler_id].instance_id:
                scheduler_link = self.schedulers[scheduler_id]
                break
        else:
            logger.error("Trying to add actions from an unknwown scheduler: %s",
                         scheduler_instance_id)
            return
        if not scheduler_link:
            logger.error("Trying to add actions, but scheduler link is not found for: %s, "
                         "actions: %s", scheduler_instance_id, actions_list)
            return
        logger.debug("Found scheduler link: %s", scheduler_link)

        for action in actions_list:
            # First we look if the action is identified
            uuid = getattr(action, 'uuid', None)
            if uuid is None:
                try:
                    action = unserialize(action, no_load=True)
                    uuid = action.uuid
                except AlignakClassLookupException:
                    logger.error('Cannot un-serialize action: %s', action)
                    continue

            # If we already have this action, we are already working for it!
            if uuid in scheduler_link.actions:
                continue
            # Action is attached to a scheduler
            action.my_scheduler = scheduler_link.uuid
            scheduler_link.actions[action.uuid] = action
            self.assign_to_a_queue(action)

    def assign_to_a_queue(self, action):
        """Take an action and put it to a worker actions queue

        :param action: action to put
        :type action: alignak.action.Action
        :return: None
        """
        (worker_id, queue) = self._get_queue_for_the_action(action)
        if not worker_id:
            return

        # Tag the action as "in the worker i"
        action.my_worker = worker_id
        action.status = ACT_STATUS_QUEUED

        msg = Message(_type='Do', data=action, source=self.name)
        logger.debug("Queuing message: %s", msg)
        queue.put_nowait(msg)
        logger.debug("Queued")

    def get_new_actions(self):
        """ Wrapper function for do_get_new_actions
        For stats purpose

        :return: None
        TODO: Use a decorator for timing this function
        """
        try:
            _t0 = time.time()
            self.do_get_new_actions()
            statsmgr.timer('actions.got.time', time.time() - _t0)
        except RuntimeError:
            logger.error("Exception like issue #1007")

    def do_get_new_actions(self):
        """Get new actions from schedulers
        Create a Message and put into the module queue
        REF: doc/alignak-action-queues.png (1)

        :return: None
        """
        # Here are the differences between a poller and a reactionner:
        # Poller will only do checks,
        # Reactionner will do actions (notifications and event handlers)
        do_checks = self.__class__.do_checks
        do_actions = self.__class__.do_actions

        # We check and get the new actions to execute in each of our schedulers
        for scheduler_link_uuid in self.schedulers:
            scheduler_link = self.schedulers[scheduler_link_uuid]

            if not scheduler_link.active:
                logger.warning("My scheduler '%s' is not active currently", scheduler_link.name)
                continue

            logger.debug("get new actions, scheduler: %s", scheduler_link.name)

            # OK, go for it :)
            _t0 = time.time()
            actions = scheduler_link.get_actions({'do_checks': do_checks, 'do_actions': do_actions,
                                                  'poller_tags': self.poller_tags,
                                                  'reactionner_tags': self.reactionner_tags,
                                                  'worker_name': self.name,
                                                  'module_types': list(self.q_by_mod.keys())})
            if actions:
                logger.debug("Got %d actions from %s", len(actions), scheduler_link.name)
                # We 'tag' them with my_scheduler and put into queue for workers
                self.add_actions(actions, scheduler_link.instance_id)
                logger.debug("Got %d actions from %s in %s",
                             len(actions), scheduler_link.name, time.time() - _t0)
            statsmgr.gauge('actions.added.count.%s' % (scheduler_link.name), len(actions))

    def clean_previous_run(self):
        """Clean variables from previous configuration,
        such as schedulers, broks and external commands

        :return: None
        """
        # Execute the base class treatment...
        super(Satellite, self).clean_previous_run()

        # Clean my lists
        del self.broks[:]
        del self.events[:]

    def do_loop_turn(self):  # pylint: disable=too-many-branches
        """Satellite main loop::

        * Check and delete zombies actions / modules
        * Get returns from queues
        * Adjust worker number
        * Get new actions

        :return: None
        """
        # Try to see if one of my module is dead, and restart previously dead modules
        self.check_and_del_zombie_modules()

        # Also if some zombie workers exist...
        self.check_and_del_zombie_workers()

        # Call modules that manage a starting tick pass
        self.hook_point('tick')

        # Print stats for debug
        for _, sched in self.schedulers.items():
            for mod in self.q_by_mod:
                # In workers we've got actions sent to queue - queue size
                for (worker_id, queue) in list(self.q_by_mod[mod].items()):
                    try:
                        actions_count = queue.qsize()
                        results_count = self.returns_queue.qsize()
                        logger.debug("[%s][%s][%s] actions queued: %d, results queued: %d",
                                     sched.name, mod, worker_id, actions_count, results_count)
                        # Update the statistics
                        statsmgr.gauge('worker.%s.actions-queue-size' % worker_id,
                                       actions_count)
                        statsmgr.gauge('worker.%s.results-queue-size' % worker_id,
                                       results_count)
                    except (IOError, EOFError):
                        pass

        # todo temporaray deactivate all this stuff!
        # Before return or get new actions, see how we managed
        # the former ones: are they still in queue(s)? If so, we
        # must wait more or at least have more workers
        # wait_ratio = self.wait_ratio.get_load()
        # total_q = 0
        # try:
        #     for mod in self.q_by_mod:
        #         for queue in list(self.q_by_mod[mod].values()):
        #             total_q += queue.qsize()
        # except (IOError, EOFError):
        #     pass
        # if total_q != 0 and wait_ratio < 2 * self.worker_polling_interval:
        #     logger.debug("I decide to increase the wait ratio")
        #     self.wait_ratio.update_load(wait_ratio * 2)
        #     # self.wait_ratio.update_load(self.worker_polling_interval)
        # else:
        #     # Go to self.worker_polling_interval on normal run, if wait_ratio
        #     # was >2*self.worker_polling_interval,
        #     # it make it come near 2 because if < 2, go up :)
        #     self.wait_ratio.update_load(self.worker_polling_interval)
        # wait_ratio = self.wait_ratio.get_load()
        # statsmgr.timer('core.wait-ratio', wait_ratio)
        # if self.log_loop:
        #     logger.debug("[%s] wait ratio: %f", self.name, wait_ratio)

        # Maybe we do not have enough workers, we check for it
        # and launch the new ones if needed
        self.adjust_worker_number_by_load()

        # Manage all messages we've got in the last timeout
        # for queue in self.return_messages:
        try:
            logger.debug("[%s] manage action results: %d results",
                         self.name, self.returns_queue.qsize())
            while self.returns_queue.qsize():
                msg = self.returns_queue.get_nowait()
                if msg is None:
                    continue
                logger.debug("Got a message: %s", msg)
                if msg.get_type() == 'Done':
                    logger.debug("Got an action result: %s", msg.get_data())
                    self.manage_action_return(msg.get_data())
                    logger.debug("Managed action result")
                else:
                    logger.warning("Ignoring message of type: %s", msg.get_type())
        except Full:
            logger.warning("Returns queue is full")
        except Empty:
            logger.debug("Returns queue is empty")
        except (IOError, EOFError) as exp:
            logger.warning("My returns queue is no more available: %s", str(exp))
        except Exception as exp:  # pylint: disable=broad-except
            logger.error("Failed getting messages in returns queue: %s", str(exp))
            logger.error(traceback.format_exc())

        for _, sched in self.schedulers.items():
            if sched.wait_homerun:
                logger.debug("scheduler home run: %d results", len(sched.wait_homerun))

        if not self.passive:
            # If we are an active satellite, we do not initiate the check getting
            # and return
            try:
                # We send to our schedulers the results of all finished checks
                logger.debug("pushing results...")
                self.push_results()
            except LinkError as exp:
                logger.warning("Scheduler connection failed, I could not send my results!")

            try:
                # And we get the new actions from our schedulers
                logger.debug("getting new actions...")
                self.get_new_actions()
            except LinkError as exp:
                logger.warning("Scheduler connection failed, I could not get new actions!")

        # Get objects from our modules that are not Worker based
        if self.log_loop:
            logger.debug("[%s] get objects from queues", self.name)
        self.get_objects_from_from_queues()
        statsmgr.gauge('external-commands.count', len(self.external_commands))
        statsmgr.gauge('broks.count', len(self.broks))
        statsmgr.gauge('events.count', len(self.events))

    def do_post_daemon_init(self):
        """Do this satellite (poller or reactionner) post "daemonize" init

        :return: None
        """
        # We can open the Queue for fork AFTER
        self.q_by_mod['fork'] = {}

        # todo: check if this is always useful?
        self.returns_queue = Queue()

    def setup_new_conf(self):
        # pylint: disable=too-many-branches
        """Setup the new configuration received from Arbiter

        This function calls the base satellite treatment and manages the configuration needed
        for a simple satellite daemon that executes some actions (eg. poller or reactionner):
        - configure the passive mode
        - configure the workers
        - configure the tags
        - configure the modules

        :return: None
        """
        # Execute the base class treatment...
        super(Satellite, self).setup_new_conf()

        # ...then our own specific treatment!
        with self.conf_lock:
            logger.info("Received a new configuration")

            # self_conf is our own configuration from the alignak environment
            # self_conf = self.cur_conf['self_conf']

            # Now manage modules
            if not self.have_modules:
                try:
                    self.modules = unserialize(self.cur_conf['modules'], no_load=True)
                except AlignakClassLookupException as exp:  # pragma: no cover, simple protection
                    logger.error('Cannot un-serialize modules configuration '
                                 'received from arbiter: %s', exp)
                if self.modules:
                    logger.info("I received some modules configuration: %s", self.modules)
                    self.have_modules = True

                    for module in self.modules:
                        if module.name not in self.q_by_mod:
                            self.q_by_mod[module.name] = {}

                    self.do_load_modules(self.modules)
                    # and start external modules too
                    self.modules_manager.start_external_instances()
                else:
                    logger.info("I do not have modules")

            # Initialize connection with all our satellites
            logger.info("Initializing connection with my satellites:")
            my_satellites = self.get_links_of_type(s_type='')
            for satellite in list(my_satellites.values()):
                logger.info("- : %s/%s", satellite.type, satellite.name)
                if not self.daemon_connection_init(satellite):
                    logger.error("Satellite connection failed: %s", satellite)

        # Now I have a configuration!
        self.have_conf = True

    def get_daemon_stats(self, details=False):
        """Increase the stats provided by the Daemon base class

        :return: stats dictionary
        :rtype: dict
        """
        # call the daemon one
        res = super(Satellite, self).get_daemon_stats(details=details)

        counters = res['counters']
        counters['broks'] = len(self.broks)
        counters['events'] = len(self.events)
        counters['satellites.workers'] = len(self.workers)

        return res

    def main(self):
        """Main satellite function. Do init and then mainloop

        :return: None
        """
        try:
            # Start the daemon mode
            if not self.do_daemon_init_and_start():
                self.exit_on_error(message="Daemon initialization error", exit_code=3)

            self.do_post_daemon_init()

            # We wait for initial conf
            self.wait_for_initial_conf()
            if self.new_conf:
                # Setup the received configuration
                self.setup_new_conf()

                # Allocate Mortal Threads
                self.adjust_worker_number_by_load()

                # Now main loop
                self.do_main_loop()
                logger.info("Exited from the main loop.")

            self.request_stop()
        except Exception:  # pragma: no cover, this should never happen indeed ;)
            self.exit_on_exception(traceback.format_exc())
            raise
Exemplo n.º 16
0
class Downtime:
    """ Schedules downtime for a specified service. If the "fixed" argument is set
    to one (1), downtime will start and end at the times specified by the
    "start" and "end" arguments.
    Otherwise, downtime will begin between the "start" and "end" times and last
    for "duration" seconds. The "start" and "end" arguments are specified
    in time_t format (seconds since the UNIX epoch). The specified service
    downtime can be triggered by another downtime entry if the "trigger_id"
    is set to the ID of another scheduled downtime entry.
    Set the "trigger_id" argument to zero (0) if the downtime for the
    specified service should not be triggered by another downtime entry.

    """
    _id = 1

    # Just to list the properties we will send as pickle
    # so to others daemons, so all but NOT REF
    properties = {
        'activate_me': StringProp(default=[]),
        'entry_time': IntegerProp(default=0, fill_brok=['full_status']),
        'fixed': BoolProp(default=True, fill_brok=['full_status']),
        'start_time': IntegerProp(default=0, fill_brok=['full_status']),
        'duration': IntegerProp(default=0, fill_brok=['full_status']),
        'trigger_id': IntegerProp(default=0),
        'end_time': IntegerProp(default=0, fill_brok=['full_status']),
        'real_end_time': IntegerProp(default=0),
        'author': StringProp(default='', fill_brok=['full_status']),
        'comment': StringProp(default=''),
        'is_in_effect': BoolProp(default=False),
        'has_been_triggered': BoolProp(default=False),
        'can_be_deleted': BoolProp(default=False),

        # TODO: find a very good way to handle the downtime "ref".
        # ref must effectively not be in properties because it points
        # onto a real object.
        # 'ref': None
    }

    def __init__(self, ref, start_time, end_time, fixed, trigger_id, duration, author, comment):
        now = datetime.datetime.now()
        self._id = int(time.mktime(now.timetuple()) * 1e6 + now.microsecond)
        self.__class__._id = self._id + 1
        self.ref = ref  # pointer to srv or host we are apply
        self.activate_me = []  # The other downtimes i need to activate
        self.entry_time = int(time.time())
        self.fixed = fixed
        self.start_time = start_time
        self.duration = duration
        self.trigger_id = trigger_id
        if self.trigger_id != 0:  # triggered plus fixed makes no sense
            self.fixed = False
        self.end_time = end_time
        if fixed:
            self.duration = end_time - start_time
        # This is important for flexible downtimes. Here start_time and
        # end_time mean: in this time interval it is possible to trigger
        # the beginning of the downtime which lasts for duration.
        # Later, when a non-ok event happens, real_end_time will be
        # recalculated from now+duration
        # end_time will be displayed in the web interface, but real_end_time
        # is used internally
        self.real_end_time = end_time
        self.author = author
        self.comment = comment
        self.is_in_effect = False
        # fixed: start_time has been reached,
        # flexible: non-ok checkresult

        self.has_been_triggered = False  # another downtime has triggered me
        self.can_be_deleted = False
        self.add_automatic_comment()

    def __str__(self):
        if self.is_in_effect is True:
            active = "active"
        else:
            active = "inactive"
        if self.fixed is True:
            d_type = "fixed"
        else:
            d_type = "flexible"
        return "%s %s Downtime id=%d %s - %s" % (
            active, d_type, self._id, time.ctime(self.start_time), time.ctime(self.end_time))

    @property
    def id(self):  # pylint: disable=C0103
        """Getter for id, raise deprecation warning

        :return: self._id
        """
        warnings.warn("Access to deprecated attribute id %s Item class" % self.__class__,
                      DeprecationWarning, stacklevel=2)
        return self._id

    @id.setter
    def id(self, value):  # pylint: disable=C0103
        """Setter for id, raise deprecation warning

        :param value: value to set
        :return: None
        """
        warnings.warn("Access to deprecated attribute id of %s class" % self.__class__,
                      DeprecationWarning, stacklevel=2)
        self._id = value

    def trigger_me(self, other_downtime):
        """Wrapper to activate_me.append function
        Used to add another downtime to activate

        :param other_downtime: other downtime to activate/cancel
        :type other_downtime:
        :return: None
        """
        self.activate_me.append(other_downtime)

    def in_scheduled_downtime(self):
        """Getter for is_in_effect attribute

        :return: True if downtime is in effect, False otherwise
        :rtype: bool
        """
        return self.is_in_effect

    def enter(self):
        """Set ref in scheduled downtime and raise downtime log entry (start)

        :return: [], always
        :rtype: list
        TODO: res is useless
        """
        res = []
        self.is_in_effect = True
        if self.fixed is False:
            now = time.time()
            self.real_end_time = now + self.duration
        if self.ref.scheduled_downtime_depth == 0:
            self.ref.raise_enter_downtime_log_entry()
            self.ref.create_notifications('DOWNTIMESTART')
        self.ref.scheduled_downtime_depth += 1
        self.ref.in_scheduled_downtime = True
        for downtime in self.activate_me:
            res.extend(downtime.enter())
        return res

    def exit(self):
        """Remove ref in scheduled downtime and raise downtime log entry (exit)

        :return: [], always | None
        :rtype: list
        TODO: res is useless
        """
        res = []
        if self.is_in_effect is True:
            # This was a fixed or a flexible+triggered downtime
            self.is_in_effect = False
            self.ref.scheduled_downtime_depth -= 1
            if self.ref.scheduled_downtime_depth == 0:
                self.ref.raise_exit_downtime_log_entry()
                self.ref.create_notifications('DOWNTIMEEND')
                self.ref.in_scheduled_downtime = False
        else:
            # This was probably a flexible downtime which was not triggered
            # In this case it silently disappears
            pass
        self.del_automatic_comment()
        self.can_be_deleted = True
        # when a downtime ends and the service was critical
        # a notification is sent with the next critical check
        # So we should set a flag here which signals consume_result
        # to send a notification
        self.ref.in_scheduled_downtime_during_last_check = True
        return res

    def cancel(self):
        """Remove ref in scheduled downtime and raise downtime log entry (cancel)

        :return: [], always
        :rtype: list
        TODO: res is useless
        """
        res = []
        self.is_in_effect = False
        self.ref.scheduled_downtime_depth -= 1
        if self.ref.scheduled_downtime_depth == 0:
            self.ref.raise_cancel_downtime_log_entry()
            self.ref.in_scheduled_downtime = False
        self.del_automatic_comment()
        self.can_be_deleted = True
        self.ref.in_scheduled_downtime_during_last_check = True
        # Nagios does not notify on canceled downtimes
        # res.extend(self.ref.create_notifications('DOWNTIMECANCELLED'))
        # Also cancel other downtimes triggered by me
        for downtime in self.activate_me:
            res.extend(downtime.cancel())
        return res

    def add_automatic_comment(self):
        """Add comment on ref for downtime

        :return: None
        """
        if self.fixed is True:
            text = (
                "This %s has been scheduled for fixed downtime from %s to %s. "
                "Notifications for the %s will not be sent out during that time period." % (
                    self.ref.my_type,
                    time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(self.start_time)),
                    time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(self.end_time)),
                    self.ref.my_type)
            )
        else:
            hours, remainder = divmod(self.duration, 3600)
            minutes, seconds = divmod(remainder, 60)
            text = ("This %s has been scheduled for flexible downtime starting between %s and %s "
                    "and lasting for a period of %d hours and %d minutes. "
                    "Notifications for the %s will not be sent out during that time period." % (
                        self.ref.my_type,
                        time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(self.start_time)),
                        time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(self.end_time)),
                        hours, minutes, self.ref.my_type)
                    )
        if self.ref.my_type == 'host':
            comment_type = 1
        else:
            comment_type = 2
        comm = Comment(self.ref, False, "(Alignak)", text, comment_type, 2, 0, False, 0)
        self.comment_id = comm._id
        self.extra_comment = comm
        self.ref.add_comment(comm)

    def del_automatic_comment(self):
        """Remove automatic comment on ref previously created

        :return: None
        """
        # Extra comment can be None if we load it from a old version of Alignak
        # TODO: remove it in a future version when every one got upgrade
        if self.extra_comment is not None:
            self.extra_comment.can_be_deleted = True
        # self.ref.del_comment(self.comment_id)

    def fill_data_brok_from(self, data, brok_type):
        """Fill data with info of item by looking at brok_type
        in props of properties or running_properties

        :param data: data to fill
        :type data:
        :param brok_type: type of brok
        :type brok_type: str
        :return: None
        TODO: Duplicate from Notification.fill_data_brok_from
        """
        cls = self.__class__
        # Now config properties
        for prop, entry in cls.properties.items():
            if hasattr(prop, 'fill_brok'):
                if brok_type in entry['fill_brok']:
                    data[prop] = getattr(self, prop)

    def get_initial_status_brok(self):
        """Get a initial status brok

        :return: brok with wanted data
        :rtype: alignak.brok.Brok
        TODO: Duplicate from Notification.fill_data_brok_from
        """
        data = {'_id': self._id}

        self.fill_data_brok_from(data, 'full_status')
        brok = Brok('downtime_raise', data)
        return brok

    def __getstate__(self):
        """Call by pickle for dataify the comment
        because we DO NOT WANT REF in this pickleisation!

        :return: dict containing notification data
        :rtype: dict
        TODO: REMOVE THIS
        """
        cls = self.__class__
        # id is not in *_properties
        res = {'_id': self._id}
        for prop in cls.properties:
            if hasattr(self, prop):
                res[prop] = getattr(self, prop)
        return res

    def __setstate__(self, state):
        """Inverted function of getstate

        :param state: state to restore
        :type state: dict
        :return: None
        TODO: REMOVE THIS
        """
        cls = self.__class__

        # Maybe it's not a dict but a list like in the old 0.4 format
        # so we should call the 0.4 function for it
        if isinstance(state, list):
            self.__setstate_deprecated__(state)
            return

        self._id = state['_id']
        for prop in cls.properties:
            if prop in state:
                setattr(self, prop, state[prop])

        if self._id >= cls._id:
            cls._id = self._id + 1

    def __setstate_deprecated__(self, state):
        """In 1.0 we move to a dict save.

        :param state: it's the state
        :type state: dict
        :return: None
        TODO: REMOVE THIS"""
        cls = self.__class__
        # Check if the len of this state is like the previous,
        # if not, we will do errors!
        # -1 because of the '_id' prop
        if len(cls.properties) != (len(state) - 1):
            logger.info("Passing downtime")
            return

        self._id = state.pop()
        for prop in cls.properties:
            val = state.pop()
            setattr(self, prop, val)
        if self._id >= cls._id:
            cls._id = self._id + 1
Exemplo n.º 17
0
class NotificationWay(Item):
    """NotificationWay class is used to implement way of sending notifications (command, periods..)

    """
    my_type = 'notificationway'

    properties = Item.properties.copy()
    properties.update({
        'notificationway_name':
        StringProp(fill_brok=['full_status']),
        'host_notifications_enabled':
        BoolProp(default=True, fill_brok=['full_status']),
        'service_notifications_enabled':
        BoolProp(default=True, fill_brok=['full_status']),
        'host_notification_period':
        StringProp(fill_brok=['full_status']),
        'service_notification_period':
        StringProp(fill_brok=['full_status']),
        'host_notification_options':
        ListProp(default=[''], fill_brok=['full_status'], split_on_comma=True),
        'service_notification_options':
        ListProp(default=[''], fill_brok=['full_status'], split_on_comma=True),
        'host_notification_commands':
        ListProp(default=[], fill_brok=['full_status']),
        'service_notification_commands':
        ListProp(default=[], fill_brok=['full_status']),
        'min_business_impact':
        IntegerProp(default=0, fill_brok=['full_status']),
    })

    running_properties = Item.running_properties.copy()

    # This tab is used to transform old parameters name into new ones
    # so from Nagios2 format, to Nagios3 ones.
    # Or Alignak deprecated names like criticity
    old_properties = {
        'min_criticity': 'min_business_impact',
    }

    macros = {}

    special_properties = ('service_notification_commands',
                          'host_notification_commands',
                          'service_notification_period',
                          'host_notification_period')

    def __init__(self, params=None, parsing=True):
        if params is None:
            params = {}

        # At deserialization, thoses are dict
        # TODO: Separate parsing instance from recreated ones
        for prop in [
                'service_notification_commands', 'host_notification_commands'
        ]:
            if prop in params and isinstance(params[prop], list) and params[prop] \
                    and isinstance(params[prop][0], dict):
                new_list = [
                    CommandCall(elem, parsing=parsing) for elem in params[prop]
                ]
                # We recreate the object
                setattr(self, prop, new_list)
                # And remove prop, to prevent from being overridden
                del params[prop]
        super(NotificationWay, self).__init__(params, parsing=parsing)

    def serialize(self):
        res = super(NotificationWay, self).serialize()

        for prop in [
                'service_notification_commands', 'host_notification_commands'
        ]:
            if getattr(self, prop) is None:
                res[prop] = None
            else:
                res[prop] = [elem.serialize() for elem in getattr(self, prop)]

        return res

    def get_name(self):
        """Accessor to notificationway_name attribute

        :return: notificationway name
        :rtype: str
        """
        return self.notificationway_name

    def want_service_notification(self,
                                  timeperiods,
                                  timestamp,
                                  state,
                                  n_type,
                                  business_impact,
                                  cmd=None):
        # pylint: disable=too-many-return-statements
        """Check if notification options match the state of the service
        Notification is NOT wanted in ONE of the following case::

        * service notifications are disabled
        * cmd is not in service_notification_commands
        * business_impact < self.min_business_impact
        * service_notification_period is not valid
        * state does not match service_notification_options for problem, recovery and flapping
        * state does not match host_notification_options for downtime

        :param timestamp: time we want to notify the contact (usually now)
        :type timestamp: int
        :param state: host or service state ("WARNING", "CRITICAL" ..)
        :type state: str
        :param n_type: type of notification ("PROBLEM", "RECOVERY" ..)
        :type n_type: str
        :param business_impact: impact of this service
        :type business_impact: int
        :param cmd: command launched to notify the contact
        :type cmd: str
        :return: True if no condition is matched, otherwise False
        :rtype: bool
        TODO: Simplify function
        """
        if not self.service_notifications_enabled:
            return False

        # Maybe the command we ask for are not for us, but for another notification ways
        # on the same contact. If so, bail out
        if cmd and cmd not in self.service_notification_commands:
            return False

        # If the business_impact is not high enough, we bail out
        if business_impact < self.min_business_impact:
            return False

        notif_period = timeperiods[self.service_notification_period]
        in_notification_period = notif_period.is_time_valid(timestamp)
        if 'n' in self.service_notification_options:
            return False

        if in_notification_period:
            short_states = {
                u'WARNING': 'w',
                u'UNKNOWN': 'u',
                u'CRITICAL': 'c',
                u'RECOVERY': 'r',
                u'FLAPPING': 'f',
                u'DOWNTIME': 's'
            }
            if n_type == u'PROBLEM' and state in short_states:
                return short_states[state] in self.service_notification_options
            if n_type == u'RECOVERY' and n_type in short_states:
                return short_states[
                    n_type] in self.service_notification_options
            if n_type == u'ACKNOWLEDGEMENT':
                return in_notification_period
            if n_type in (u'FLAPPINGSTART', u'FLAPPINGSTOP',
                          u'FLAPPINGDISABLED'):
                return 'f' in self.service_notification_options
            if n_type in (u'DOWNTIMESTART', u'DOWNTIMEEND',
                          u'DOWNTIMECANCELLED'):
                # No notification when a downtime was cancelled. Is that true??
                # According to the documentation we need to look at _host_ options
                return 's' in self.host_notification_options

        return False

    def want_host_notification(self,
                               timperiods,
                               timestamp,
                               state,
                               n_type,
                               business_impact,
                               cmd=None):
        # pylint: disable=too-many-return-statements
        """Check if notification options match the state of the host
        Notification is NOT wanted in ONE of the following case::

        * host notifications are disabled
        * cmd is not in host_notification_commands
        * business_impact < self.min_business_impact
        * host_notification_period is not valid
        * state does not match host_notification_options for problem, recovery, flapping and dt


        :param timestamp: time we want to notify the contact (usually now)
        :type timestamp: int
        :param state: host or service state ("WARNING", "CRITICAL" ..)
        :type state: str
        :param n_type: type of notification ("PROBLEM", "RECOVERY" ..)
        :type n_type: str
        :param business_impact: impact of this service
        :type business_impact: int
        :param cmd: command launched to notify the contact
        :type cmd: str
        :return: True if no condition is matched, otherwise False
        :rtype: bool
        TODO: Simplify function
        """
        if not self.host_notifications_enabled:
            return False

        # If the business_impact is not high enough, we bail out
        if business_impact < self.min_business_impact:
            return False

        # Maybe the command we ask for are not for us, but for another notification ways
        # on the same contact. If so, bail out
        if cmd and cmd not in self.host_notification_commands:
            return False

        notif_period = timperiods[self.host_notification_period]
        in_notification_period = notif_period.is_time_valid(timestamp)
        if 'n' in self.host_notification_options:
            return False

        if in_notification_period:
            short_states = {
                u'DOWN': 'd',
                u'UNREACHABLE': 'u',
                u'RECOVERY': 'r',
                u'FLAPPING': 'f',
                u'DOWNTIME': 's'
            }
            if n_type == u'PROBLEM' and state in short_states:
                return short_states[state] in self.host_notification_options
            if n_type == u'RECOVERY' and n_type in short_states:
                return short_states[n_type] in self.host_notification_options
            if n_type == u'ACKNOWLEDGEMENT':
                return in_notification_period
            if n_type in (u'FLAPPINGSTART', u'FLAPPINGSTOP',
                          u'FLAPPINGDISABLED'):
                return 'f' in self.host_notification_options
            if n_type in (u'DOWNTIMESTART', u'DOWNTIMEEND',
                          u'DOWNTIMECANCELLED'):
                return 's' in self.host_notification_options

        return False

    def get_notification_commands(self, o_type):
        """Get notification commands for object type

        :param o_type: object type (host or service)
        :type o_type: str
        :return: command list
        :rtype: list[alignak.objects.command.Command]
        """
        # service_notification_commands for service
        notif_commands_prop = o_type + '_notification_commands'
        notif_commands = getattr(self, notif_commands_prop)
        return notif_commands

    def is_correct(self):
        # pylint: disable=too-many-branches
        """Check if this object configuration is correct ::

        * Check our own specific properties
        * Call our parent class is_correct checker

        :return: True if the configuration is correct, otherwise False
        :rtype: bool
        """
        state = True

        # Do not execute checks if notifications are disabled
        if (hasattr(self, 'service_notification_options')
                and self.service_notification_options == ['n']):
            if (hasattr(self, 'host_notification_options')
                    and self.host_notification_options == ['n']):
                return True

        # Internal checks before executing inherited function...

        # Service part
        if not hasattr(self, 'service_notification_commands'):
            msg = "[notificationway::%s] do not have any service_notification_commands defined" % (
                self.get_name())
            self.add_error(msg)
            state = False
        else:
            for cmd in self.service_notification_commands:
                if cmd is None:
                    msg = "[notificationway::%s] a service_notification_command is missing" % (
                        self.get_name())
                    self.add_error(msg)
                    state = False
                elif not cmd.is_valid():
                    msg = "[notificationway::%s] a service_notification_command is invalid" % (
                        self.get_name())
                    self.add_error(msg)
                    state = False

        if getattr(self, 'service_notification_period', None) is None:
            msg = "[notificationway::%s] the service_notification_period is invalid" % (
                self.get_name())
            self.add_error(msg)
            state = False

        # Now host part
        if not hasattr(self, 'host_notification_commands'):
            msg = "[notificationway::%s] do not have any host_notification_commands defined" % (
                self.get_name())
            self.add_error(msg)
            state = False
        else:
            for cmd in self.host_notification_commands:
                if cmd is None:
                    msg = "[notificationway::%s] a host_notification_command is missing" % (
                        self.get_name())
                    self.add_error(msg)
                    state = False
                elif not cmd.is_valid():
                    msg = "[notificationway::%s] a host_notification_command is invalid (%s)" % (
                        cmd.get_name(), str(cmd.__dict__))
                    self.add_error(msg)
                    state = False

        if getattr(self, 'host_notification_period', None) is None:
            msg = "[notificationway::%s] the host_notification_period is invalid" % (
                self.get_name())
            self.add_error(msg)
            state = False

        return super(NotificationWay, self).is_correct() and state
Exemplo n.º 18
0
class Realm(Itemgroup):
    """Realm class is used to implement realm. It is basically a set of Host or Service
    assigned to a specific set of Scheduler/Poller (other daemon are optional)

    """
    _id = 1  # zero is always a little bit special... like in database
    my_type = 'realm'

    properties = Itemgroup.properties.copy()
    properties.update({
        '_id': IntegerProp(default=0, fill_brok=['full_status']),
        'realm_name': StringProp(fill_brok=['full_status']),
        # No status_broker_name because it put hosts, not host_name
        'realm_members': ListProp(default=[], split_on_coma=True),
        'higher_realms': ListProp(default=[], split_on_coma=True),
        'default': BoolProp(default=False),
        'broker_complete_links': BoolProp(default=False),
        # 'alias': {'required':  True, 'fill_brok': ['full_status']},
        # 'notes': {'required': False, 'default':'', 'fill_brok': ['full_status']},
        # 'notes_url': {'required': False, 'default':'', 'fill_brok': ['full_status']},
        # 'action_url': {'required': False, 'default':'', 'fill_brok': ['full_status']},
    })

    running_properties = Item.running_properties.copy()
    running_properties.update({
        'serialized_confs': DictProp(default={}),
    })

    macros = {
        'REALMNAME': 'realm_name',
        'REALMMEMBERS': 'members',
    }

    potential_pollers = []
    potential_reactionners = []
    potential_brokers = []
    potential_receivers = []

    def get_name(self):
        """Accessor to realm_name attribute

        :return: realm name
        :rtype: str
        """
        return self.realm_name

    def get_realms(self):
        """
        Get list of members of this realm

        :return: list of realm (members)
        :rtype: list
        TODO: Duplicate of get_realm_members
        """
        return self.realm_members

    def add_string_member(self, member):
        """Add a realm to realm_members attribute

        :param member: realm name to add
        :type member:
        :return: None
        TODO : Clean this self.members != self.realm_members?
        """
        self.realm_members.append(member)

    def get_realm_members(self):
        """
        Get list of members of this realm

        :return: list of realm (members)
        :rtype: list
        """
        # TODO: consistency: a Realm instance should always have its real_members defined,
        if hasattr(self, 'realm_members'):
            # more over it should already be decoded/parsed to its final type:
            # a list of strings (being the names of the members)
            return [r.strip() for r in self.realm_members]
        else:
            return []

    def get_realms_by_explosion(self, realms):
        """Get all members of this realm including members of sub-realms

        :param realms: realms list, used to look for a specific one
        :type realms: alignak.objects.realm.Realms
        :return: list of members and add realm to realm_members attribute
        :rtype: list
        TODO: Clean this function that silently edit realm_members.
        """
        # First we tag the hg so it will not be explode
        # if a son of it already call it
        self.already_explode = True

        # Now the recursive part
        # rec_tag is set to False every HG we explode
        # so if True here, it must be a loop in HG
        # calls... not GOOD!
        if self.rec_tag:
            err = "Error: we've got a loop in realm definition %s" % self.get_name(
            )
            self.configuration_errors.append(err)
            if hasattr(self, 'members'):
                return self.members
            else:
                return []

        # Ok, not a loop, we tag it and continue
        self.rec_tag = True

        p_mbrs = self.get_realm_members()
        for p_mbr in p_mbrs:
            realm = realms.find_by_name(p_mbr.strip())
            if realm is not None:
                value = realm.get_realms_by_explosion(realms)
                if len(value) > 0:
                    self.add_string_member(value)

        if hasattr(self, 'members'):
            return self.members
        else:
            return []

    def get_all_subs_satellites_by_type(self, sat_type):
        """Get all satellites of the wated type in this realm recursively

        :param sat_type: satelitte type wanted (scheduler, poller ..)
        :type sat_type:
        :return: list of satellite in this realm
        :rtype: list
        TODO: Make this generic
        """
        res = copy.copy(getattr(self, sat_type))
        for member in self.realm_members:
            tmps = member.get_all_subs_satellites_by_type(sat_type)
            for mem in tmps:
                res.append(mem)
        return res

    def count_reactionners(self):
        """ Set the number of reactionners in this realm.

        :return: None
        TODO: Make this generic
        """
        self.nb_reactionners = 0
        for reactionner in self.reactionners:
            if not reactionner.spare:
                self.nb_reactionners += 1
        for realm in self.higher_realms:
            for reactionner in realm.reactionners:
                if not reactionner.spare and reactionner.manage_sub_realms:
                    self.nb_reactionners += 1

    def count_pollers(self):
        """ Set the number of pollers in this realm.

        :return: None
        """
        self.nb_pollers = 0
        for poller in self.pollers:
            if not poller.spare:
                self.nb_pollers += 1
        for realm in self.higher_realms:
            for poller in realm.pollers:
                if not poller.spare and poller.manage_sub_realms:
                    self.nb_pollers += 1

    def count_brokers(self):
        """ Set the number of brokers in this realm.

        :return: None
        TODO: Make this generic
        """
        self.nb_brokers = 0
        for broker in self.brokers:
            if not broker.spare:
                self.nb_brokers += 1
        for realm in self.higher_realms:
            for broker in realm.brokers:
                if not broker.spare and broker.manage_sub_realms:
                    self.nb_brokers += 1

    def count_receivers(self):
        """ Set the number of receivers in this realm.

        :return: None
        TODO: Make this generic
        """
        self.nb_receivers = 0
        for receiver in self.receivers:
            if not receiver.spare:
                self.nb_receivers += 1
        for realm in self.higher_realms:
            for receiver in realm.receivers:
                if not receiver.spare and receiver.manage_sub_realms:
                    self.nb_receivers += 1

    def get_satellties_by_type(self, s_type):
        """Generic function to access one of the satellite attribute
        ie : self.pollers, self.reactionners ...

        :param s_type: satellite type wanted
        :type s_type: str
        :return: self.*type*s
        :rtype: list
        """

        if hasattr(self, s_type + 's'):
            return getattr(self, s_type + 's')
        else:
            logger.debug("[realm] do not have this kind of satellites: %s",
                         s_type)
            return []

    def fill_potential_satellites_by_type(self, sat_type):
        """Edit potential_*sat_type* attribute to get potential satellite from upper level realms

        :param sat_type: satellite type wanted
        :type sat_type: str
        :return: None
        """
        setattr(self, 'potential_%s' % sat_type, [])
        for satellite in getattr(self, sat_type):
            getattr(self, 'potential_%s' % sat_type).append(satellite)
        for realm in self.higher_realms:
            for satellite in getattr(realm, sat_type):
                if satellite.manage_sub_realms:
                    getattr(self, 'potential_%s' % sat_type).append(satellite)

    def get_potential_satellites_by_type(self, s_type):
        """Generic function to access one of the potential satellite attribute
        ie : self.potential_pollers, self.potential_reactionners ...

        :param s_type: satellite type wanted
        :type s_type: str
        :return: self.potential_*type*s
        :rtype: list
        """
        if hasattr(self, 'potential_' + s_type + 's'):
            return getattr(self, 'potential_' + s_type + 's')
        else:
            logger.debug("[realm] do not have this kind of satellites: %s",
                         s_type)
            return []

    def get_nb_of_must_have_satellites(self, s_type):
        """Generic function to access one of the number satellite attribute
        ie : self.nb_pollers, self.nb_reactionners ...

        :param s_type: satellite type wanted
        :type s_type: str
        :return: self.nb_*type*s
        :rtype: int
        """
        if hasattr(self, 'nb_' + s_type + 's'):
            return getattr(self, 'nb_' + s_type + 's')
        else:
            logger.debug("[realm] do not have this kind of satellites: %s",
                         s_type)
            return 0

    # Fill dict of realms for managing the satellites confs
    def prepare_for_satellites_conf(self):
        """Init the following attributes::

        * to_satellites (with *satellite type* keys)
        * to_satellites_need_dispatch (with *satellite type* keys)
        * to_satellites_managed_by (with *satellite type* keys)
        * nb_*satellite type*s
        * self.potential_*satellite type*s

        (satellite type are reactionner, poller, broker and receiver)

        :return: None
        """
        self.to_satellites = {
            'reactionner': {},
            'poller': {},
            'broker': {},
            'receiver': {}
        }

        self.to_satellites_need_dispatch = {
            'reactionner': {},
            'poller': {},
            'broker': {},
            'receiver': {}
        }

        self.to_satellites_managed_by = {
            'reactionner': {},
            'poller': {},
            'broker': {},
            'receiver': {}
        }

        self.count_reactionners()
        self.fill_potential_satellites_by_type('reactionners')
        self.count_pollers()
        self.fill_potential_satellites_by_type('pollers')
        self.count_brokers()
        self.fill_potential_satellites_by_type('brokers')
        self.count_receivers()
        self.fill_potential_satellites_by_type('receivers')

        line = "%s: (in/potential) (schedulers:%d) (pollers:%d/%d)" \
               " (reactionners:%d/%d) (brokers:%d/%d) (receivers:%d/%d)" % \
            (self.get_name(),
             len(self.schedulers),
             self.nb_pollers, len(self.potential_pollers),
             self.nb_reactionners, len(self.potential_reactionners),
             self.nb_brokers, len(self.potential_brokers),
             self.nb_receivers, len(self.potential_receivers)
             )
        logger.info(line)

    def fill_broker_with_poller_reactionner_links(self, broker):
        """Fill brokerlink object with satellite data

        :param broker: broker link we want to fill
        :type broker: alignak.objects.brokerlink.Brokerlink
        :return: None
        """

        # TODO: find a better name...
        # TODO: and if he goes active?
        # First we create/void theses links
        broker.cfg['pollers'] = {}
        broker.cfg['reactionners'] = {}
        broker.cfg['receivers'] = {}

        # First our own level
        for poller in self.pollers:
            cfg = poller.give_satellite_cfg()
            broker.cfg['pollers'][poller._id] = cfg

        for reactionner in self.reactionners:
            cfg = reactionner.give_satellite_cfg()
            broker.cfg['reactionners'][reactionner._id] = cfg

        for receiver in self.receivers:
            cfg = receiver.give_satellite_cfg()
            broker.cfg['receivers'][receiver._id] = cfg

        # Then sub if we must to it
        if broker.manage_sub_realms:
            # Now pollers
            for poller in self.get_all_subs_satellites_by_type('pollers'):
                cfg = poller.give_satellite_cfg()
                broker.cfg['pollers'][poller._id] = cfg

            # Now reactionners
            for reactionner in self.get_all_subs_satellites_by_type(
                    'reactionners'):
                cfg = reactionner.give_satellite_cfg()
                broker.cfg['reactionners'][reactionner._id] = cfg

            # Now receivers
            for receiver in self.get_all_subs_satellites_by_type('receivers'):
                cfg = receiver.give_satellite_cfg()
                broker.cfg['receivers'][receiver._id] = cfg

    def get_satellites_links_for_scheduler(self):
        """Get a configuration dict with pollers and reactionners data

        :return: dict containing pollers and reactionners config (key is satellite id)
        :rtype: dict
        """

        # First we create/void theses links
        cfg = {'pollers': {}, 'reactionners': {}}

        # First our own level
        for poller in self.pollers:
            config = poller.give_satellite_cfg()
            cfg['pollers'][poller._id] = config

        for reactionner in self.reactionners:
            config = reactionner.give_satellite_cfg()
            cfg['reactionners'][reactionner._id] = config

        # print "***** Preparing a satellites conf for a scheduler", cfg
        return cfg
Exemplo n.º 19
0
class Timeperiod(Item):
    """
    Class to manage a timeperiod
    A timeperiod is defined with range time (hours) of week to do action
    and add day exceptions (like non working days)
    """
    my_type = 'timeperiod'

    properties = Item.properties.copy()
    properties.update({
        'timeperiod_name':
        StringProp(fill_brok=['full_status']),
        'alias':
        StringProp(default='', fill_brok=['full_status']),
        'use':
        ListProp(default=[]),
        'register':
        IntegerProp(default=1),

        # These are needed if a broker module calls methods on timeperiod objects
        'dateranges':
        ListProp(fill_brok=['full_status'], default=[]),
        'exclude':
        ListProp(fill_brok=['full_status'], default=[]),
        'unresolved':
        ListProp(fill_brok=['full_status'], default=[]),
        'invalid_entries':
        ListProp(fill_brok=['full_status'], default=[]),
        'is_active':
        BoolProp(default=False),
        'activated_once':
        BoolProp(default=False),
    })
    running_properties = Item.running_properties.copy()

    def __init__(self, params=None, parsing=True):

        if params is None:
            params = {}

        # Get standard params
        standard_params = dict([(k, v) for k, v in params.items()
                                if k in self.__class__.properties])
        # Get timeperiod params (monday, tuesday, ...)
        timeperiod_params = dict([(k, v) for k, v in params.items()
                                  if k not in self.__class__.properties])

        if 'dateranges' in standard_params and isinstance(standard_params['dateranges'], list) \
                and standard_params['dateranges'] \
                and isinstance(standard_params['dateranges'][0], dict):
            new_list = []
            for elem in standard_params['dateranges']:
                cls = get_alignak_class(elem['__sys_python_module__'])
                if cls:
                    new_list.append(cls(elem['content']))
            # We recreate the object
            self.dateranges = new_list
            # And remove prop, to prevent from being overridden
            del standard_params['dateranges']
        # Handle standard params
        super(Timeperiod, self).__init__(params=standard_params,
                                         parsing=parsing)
        self.cache = {}  # For tuning purpose only
        self.invalid_cache = {}  # same but for invalid search

        # We use the uuid presence to assume we are reserializing
        if 'uuid' in params:
            self.uuid = params['uuid']
        else:
            # Initial creation here, uuid already created in super
            self.unresolved = []
            self.dateranges = []
            self.exclude = []
            self.invalid_entries = []
            self.is_active = False
            self.activated_once = False

            # Handle timeperiod params
            for key, value in timeperiod_params.items():
                if isinstance(value, list):
                    if value:
                        value = value[-1]
                    else:
                        value = ''
                self.unresolved.append(key + ' ' + value)

    def serialize(self):
        """This function serialize into a simple dict object.
        It is used when transferring data to other daemons over the network (http)

        Here we directly return all attributes

        :return: json representation of a Timeperiod
        :rtype: dict
        """
        res = super(Timeperiod, self).serialize()

        res['dateranges'] = []
        for elem in self.dateranges:
            res['dateranges'].append({
                '__sys_python_module__':
                "%s.%s" % (elem.__module__, elem.__class__.__name__),
                'content':
                elem.serialize()
            })

        return res

    def get_name(self):
        """
        Get the name of the timeperiod

        :return: the timeperiod name string
        :rtype: str
        """
        return getattr(self, 'timeperiod_name', 'unknown_timeperiod')

    def get_raw_import_values(self):  # pragma: no cover, deprecation
        """
        Get some properties of timeperiod (timeperiod is a bit different
        from classic item)

        TODO: never called anywhere, still useful?

        :return: a dictionnary of some properties
        :rtype: dict
        """
        properties = ['timeperiod_name', 'alias', 'use', 'register']
        res = {}
        for prop in properties:
            if hasattr(self, prop):
                val = getattr(self, prop)
                res[prop] = val
        # Now the unresolved one. The only way to get ride of same key things is to put
        # directly the full value as the key
        for other in self.unresolved:
            res[other] = ''
        return res

    def is_time_valid(self, timestamp):
        """
        Check if a time is valid or not

        :return: time is valid or not
        :rtype: bool
        """
        if hasattr(self, 'exclude'):
            for daterange in self.exclude:
                if daterange.is_time_valid(timestamp):
                    return False
        for daterange in self.dateranges:
            if daterange.is_time_valid(timestamp):
                return True
        return False

    # will give the first time > t which is valid
    def get_min_from_t(self, timestamp):
        """
        Get the first time > timestamp which is valid

        :param timestamp: number of seconds
        :type timestamp: int
        :return: number of seconds
        :rtype: int
        TODO: not used, so delete it
        """
        mins_incl = []
        for daterange in self.dateranges:
            mins_incl.append(daterange.get_min_from_t(timestamp))
        return min(mins_incl)

    # will give the first time > t which is not valid
    def get_not_in_min_from_t(self, first):
        """

        :return: None
        TODO: not used, so delete it
        """
        pass

    def find_next_valid_time_from_cache(self, timestamp):
        """
        Get the next valid time from cache

        :param timestamp: number of seconds
        :type timestamp: int
        :return: Nothing or time in seconds
        :rtype: None or int
        """
        try:
            return self.cache[timestamp]
        except KeyError:
            return None

    def find_next_invalid_time_from_cache(self, timestamp):
        """
        Get the next invalid time from cache

        :param timestamp: number of seconds
        :type timestamp: int
        :return: Nothing or time in seconds
        :rtype: None or int
        """
        try:
            return self.invalid_cache[timestamp]
        except KeyError:
            return None

    def check_and_log_activation_change(self):
        """
        Will look for active/un-active change of timeperiod.
        In case it change, we log it like:
        [1327392000] TIMEPERIOD TRANSITION: <name>;<from>;<to>

        States of is_active:
        -1: default value when start
        0: when timeperiod end
        1: when timeperiod start

        :return: None or a brok if TP changed
        """
        now = int(time.time())

        was_active = self.is_active
        self.is_active = self.is_time_valid(now)

        # If we got a change, log it!
        if self.is_active != was_active:
            _from = 0
            _to = 0
            # If it's the start, get a special value for was
            if not self.activated_once:
                _from = -1
                self.activated_once = True
            if was_active:
                _from = 1
            if self.is_active:
                _to = 1

            # Now raise the log
            brok = make_monitoring_log(
                'info', 'TIMEPERIOD TRANSITION: %s;%d;%d' %
                (self.get_name(), _from, _to))
            return brok
        return None

    def clean_cache(self):
        """
        Clean cache with entries older than now because not used in future ;)

        :return: None
        """
        now = int(time.time())
        t_to_del = []
        for timestamp in self.cache:
            if timestamp < now:
                t_to_del.append(timestamp)
        for timestamp in t_to_del:
            del self.cache[timestamp]

        # same for the invalid cache
        t_to_del = []
        for timestamp in self.invalid_cache:
            if timestamp < now:
                t_to_del.append(timestamp)
        for timestamp in t_to_del:
            del self.invalid_cache[timestamp]

    def get_next_valid_time_from_t(self, timestamp):
        """
        Get next valid time. If it's in cache, get it, otherwise define it.
        The limit to find it is 1 year.

        :param timestamp: number of seconds
        :type timestamp: int or float
        :return: Nothing or time in seconds
        :rtype: None or int
        """
        timestamp = int(timestamp)
        original_t = timestamp

        res_from_cache = self.find_next_valid_time_from_cache(timestamp)
        if res_from_cache is not None:
            return res_from_cache

        still_loop = True

        # Loop for all minutes...
        while still_loop:
            local_min = None

            # Ok, not in cache...
            dr_mins = []

            for daterange in self.dateranges:
                dr_mins.append(daterange.get_next_valid_time_from_t(timestamp))

            s_dr_mins = sorted([d for d in dr_mins if d is not None])

            for t01 in s_dr_mins:
                if not self.exclude and still_loop:
                    # No Exclude so we are good
                    local_min = t01
                    still_loop = False
                else:
                    for timeperiod in self.exclude:
                        if not timeperiod.is_time_valid(t01) and still_loop:
                            # OK we found a date that is not valid in any exclude timeperiod
                            local_min = t01
                            still_loop = False

            if local_min is None:
                # Looking for next invalid date
                exc_mins = []
                if s_dr_mins != []:
                    for timeperiod in self.exclude:
                        exc_mins.append(
                            timeperiod.get_next_invalid_time_from_t(
                                s_dr_mins[0]))

                s_exc_mins = sorted([d for d in exc_mins if d is not None])

                if s_exc_mins != []:
                    local_min = s_exc_mins[0]

            if local_min is None:
                still_loop = False
            else:
                timestamp = local_min
                # No loop more than one year
                if timestamp > original_t + 3600 * 24 * 366 + 1:
                    still_loop = False
                    local_min = None

        # Ok, we update the cache...
        self.cache[original_t] = local_min
        return local_min

    def get_next_invalid_time_from_t(self, timestamp):
        """
        Get the next invalid time

        :param timestamp: timestamp in seconds (of course)
        :type timestamp: int or float
        :return: timestamp of next invalid time
        :rtype: int or float
        """
        timestamp = int(timestamp)
        original_t = timestamp

        dr_mins = []
        for daterange in self.dateranges:
            timestamp = original_t
            cont = True
            while cont:
                start = daterange.get_next_valid_time_from_t(timestamp)
                if start is not None:
                    end = daterange.get_next_invalid_time_from_t(start)
                    dr_mins.append((start, end))
                    timestamp = end
                else:
                    cont = False
                if timestamp > original_t + (3600 * 24 * 365):
                    cont = False
        periods = merge_periods(dr_mins)

        # manage exclude periods
        dr_mins = []
        for exclude in self.exclude:
            for daterange in exclude.dateranges:
                timestamp = original_t
                cont = True
                while cont:
                    start = daterange.get_next_valid_time_from_t(timestamp)
                    if start is not None:
                        end = daterange.get_next_invalid_time_from_t(start)
                        dr_mins.append((start, end))
                        timestamp = end
                    else:
                        cont = False
                    if timestamp > original_t + (3600 * 24 * 365):
                        cont = False
        if not dr_mins:
            periods_exclude = []
        else:
            periods_exclude = merge_periods(dr_mins)

        if len(periods) >= 1:
            # if first valid period is after original timestamp, the first invalid time
            # is the original timestamp
            if periods[0][0] > original_t:
                return original_t
            # check the first period + first period of exclude
            if len(periods_exclude) >= 1:
                if periods_exclude[0][0] < periods[0][1]:
                    return periods_exclude[0][0]
            return periods[0][1]
        return original_t

    def has(self, prop):
        """
        Check if self have prop attribute

        :param prop: property name
        :type prop: string
        :return: true if self has this attribute
        :rtype: bool
        """
        warnings.warn(
            "{s.__class__.__name__} is deprecated, please use "
            "`hasattr(your_object, attr)` instead. This has() method will "
            "be removed in a later version.".format(s=self),
            DeprecationWarning,
            stacklevel=2)
        return hasattr(self, prop)

    def is_correct(self):
        """
        Check if this object configuration is correct ::

        * Check if dateranges of timeperiod are valid
        * Call our parent class is_correct checker

        :return: True if the configuration is correct, otherwise False if at least one daterange
        is not correct
        :rtype: bool
        """
        state = True
        for daterange in self.dateranges:
            good = daterange.is_correct()
            if not good:
                msg = "[timeperiod::%s] invalid daterange '%s'" % (
                    self.get_name(), daterange)
                self.configuration_errors.append(msg)
            state &= good

        # Warn about non correct entries
        for entry in self.invalid_entries:
            msg = "[timeperiod::%s] invalid entry '%s'" % (self.get_name(),
                                                           entry)
            self.configuration_errors.append(msg)

        return super(Timeperiod, self).is_correct() and state

    def __str__(self):
        """
        Get readable object

        :return: this object in readable format
        :rtype: str
        """
        string = ''
        string += str(self.__dict__) + '\n'
        for elt in self.dateranges:
            string += str(elt)
            (start, end) = elt.get_start_and_end_time()
            start = time.asctime(time.localtime(start))
            end = time.asctime(time.localtime(end))
            string += "\nStart and end:" + str((start, end))
        string += '\nExclude'
        for elt in self.exclude:
            string += str(elt)

        return string

    def resolve_daterange(self, dateranges, entry):  # pylint: disable=R0911,R0915,R0912
        """
        Try to solve dateranges (special cases)

        :param dateranges: dateranges
        :type dateranges: list
        :param entry: property of timeperiod
        :type entry: string
        :return: None
        """
        res = re.search(
            r'(\d{4})-(\d{2})-(\d{2}) - (\d{4})-(\d{2})-(\d{2}) / (\d+)[\s\t]*([0-9:, -]+)',
            entry)
        if res is not None:
            (syear, smon, smday, eyear, emon, emday, skip_interval,
             other) = res.groups()
            data = {
                'syear': syear,
                'smon': smon,
                'smday': smday,
                'swday': 0,
                'swday_offset': 0,
                'eyear': eyear,
                'emon': emon,
                'emday': emday,
                'ewday': 0,
                'ewday_offset': 0,
                'skip_interval': skip_interval,
                'other': other
            }
            dateranges.append(CalendarDaterange(data))
            return

        res = re.search(r'(\d{4})-(\d{2})-(\d{2}) / (\d+)[\s\t]*([0-9:, -]+)',
                        entry)
        if res is not None:
            (syear, smon, smday, skip_interval, other) = res.groups()
            eyear = syear
            emon = smon
            emday = smday
            data = {
                'syear': syear,
                'smon': smon,
                'smday': smday,
                'swday': 0,
                'swday_offset': 0,
                'eyear': eyear,
                'emon': emon,
                'emday': emday,
                'ewday': 0,
                'ewday_offset': 0,
                'skip_interval': skip_interval,
                'other': other
            }
            dateranges.append(CalendarDaterange(data))
            return

        res = re.search(
            r'(\d{4})-(\d{2})-(\d{2}) - (\d{4})-(\d{2})-(\d{2})[\s\t]*([0-9:, -]+)',
            entry)
        if res is not None:
            (syear, smon, smday, eyear, emon, emday, other) = res.groups()
            data = {
                'syear': syear,
                'smon': smon,
                'smday': smday,
                'swday': 0,
                'swday_offset': 0,
                'eyear': eyear,
                'emon': emon,
                'emday': emday,
                'ewday': 0,
                'ewday_offset': 0,
                'skip_interval': 0,
                'other': other
            }
            dateranges.append(CalendarDaterange(data))
            return

        res = re.search(r'(\d{4})-(\d{2})-(\d{2})[\s\t]*([0-9:, -]+)', entry)
        if res is not None:
            (syear, smon, smday, other) = res.groups()
            eyear = syear
            emon = smon
            emday = smday
            data = {
                'syear': syear,
                'smon': smon,
                'smday': smday,
                'swday': 0,
                'swday_offset': 0,
                'eyear': eyear,
                'emon': emon,
                'emday': emday,
                'ewday': 0,
                'ewday_offset': 0,
                'skip_interval': 0,
                'other': other
            }
            dateranges.append(CalendarDaterange(data))
            return

        res = re.search(
            r'([a-z]*) ([\d-]+) ([a-z]*) - ([a-z]*) ([\d-]+) ([a-z]*) / (\d+)[\s\t]*([0-9:, -]+)',
            entry)
        if res is not None:
            (swday, swday_offset, smon, ewday, ewday_offset, emon,
             skip_interval, other) = res.groups()
            smon_id = Daterange.get_month_id(smon)
            emon_id = Daterange.get_month_id(emon)
            swday_id = Daterange.get_weekday_id(swday)
            ewday_id = Daterange.get_weekday_id(ewday)
            data = {
                'syear': 0,
                'smon': smon_id,
                'smday': 0,
                'swday': swday_id,
                'swday_offset': swday_offset,
                'eyear': 0,
                'emon': emon_id,
                'emday': 0,
                'ewday': ewday_id,
                'ewday_offset': ewday_offset,
                'skip_interval': skip_interval,
                'other': other
            }
            dateranges.append(MonthWeekDayDaterange(data))
            return

        res = re.search(
            r'([a-z]*) ([\d-]+) - ([a-z]*) ([\d-]+) / (\d+)[\s\t]*([0-9:, -]+)',
            entry)
        if res is not None:
            (t00, smday, t01, emday, skip_interval, other) = res.groups()
            if t00 in Daterange.weekdays and t01 in Daterange.weekdays:
                swday = Daterange.get_weekday_id(t00)
                ewday = Daterange.get_weekday_id(t01)
                swday_offset = smday
                ewday_offset = emday
                data = {
                    'syear': 0,
                    'smon': 0,
                    'smday': 0,
                    'swday': swday,
                    'swday_offset': swday_offset,
                    'eyear': 0,
                    'emon': 0,
                    'emday': 0,
                    'ewday': ewday,
                    'ewday_offset': ewday_offset,
                    'skip_interval': skip_interval,
                    'other': other
                }
                dateranges.append(WeekDayDaterange(data))
                return
            elif t00 in Daterange.months and t01 in Daterange.months:
                smon = Daterange.get_month_id(t00)
                emon = Daterange.get_month_id(t01)
                data = {
                    'syear': 0,
                    'smon': smon,
                    'smday': smday,
                    'swday': 0,
                    'swday_offset': 0,
                    'eyear': 0,
                    'emon': emon,
                    'emday': emday,
                    'ewday': 0,
                    'ewday_offset': 0,
                    'skip_interval': skip_interval,
                    'other': other
                }
                dateranges.append(MonthDateDaterange(data))
                return
            elif t00 == 'day' and t01 == 'day':
                data = {
                    'syear': 0,
                    'smon': 0,
                    'smday': smday,
                    'swday': 0,
                    'swday_offset': 0,
                    'eyear': 0,
                    'emon': 0,
                    'emday': emday,
                    'ewday': 0,
                    'ewday_offset': 0,
                    'skip_interval': skip_interval,
                    'other': other
                }
                dateranges.append(MonthDayDaterange(data))
                return

        res = re.search(
            r'([a-z]*) ([\d-]+) - ([\d-]+) / (\d+)[\s\t]*([0-9:, -]+)', entry)
        if res is not None:
            (t00, smday, emday, skip_interval, other) = res.groups()
            if t00 in Daterange.weekdays:
                swday = Daterange.get_weekday_id(t00)
                swday_offset = smday
                ewday = swday
                ewday_offset = emday
                data = {
                    'syear': 0,
                    'smon': 0,
                    'smday': 0,
                    'swday': swday,
                    'swday_offset': swday_offset,
                    'eyear': 0,
                    'emon': 0,
                    'emday': 0,
                    'ewday': ewday,
                    'ewday_offset': ewday_offset,
                    'skip_interval': skip_interval,
                    'other': other
                }
                dateranges.append(WeekDayDaterange(data))
                return
            elif t00 in Daterange.months:
                smon = Daterange.get_month_id(t00)
                emon = smon
                data = {
                    'syear': 0,
                    'smon': smon,
                    'smday': smday,
                    'swday': 0,
                    'swday_offset': 0,
                    'eyear': 0,
                    'emon': emon,
                    'emday': emday,
                    'ewday': 0,
                    'ewday_offset': 0,
                    'skip_interval': skip_interval,
                    'other': other
                }
                dateranges.append(MonthDateDaterange(data))
                return
            elif t00 == 'day':
                data = {
                    'syear': 0,
                    'smon': 0,
                    'smday': smday,
                    'swday': 0,
                    'swday_offset': 0,
                    'eyear': 0,
                    'emon': 0,
                    'emday': emday,
                    'ewday': 0,
                    'ewday_offset': 0,
                    'skip_interval': skip_interval,
                    'other': other
                }
                dateranges.append(MonthDayDaterange(data))
                return

        res = re.search(
            r'([a-z]*) ([\d-]+) ([a-z]*) - ([a-z]*) ([\d-]+) ([a-z]*) [\s\t]*([0-9:, -]+)',
            entry)
        if res is not None:
            (swday, swday_offset, smon, ewday, ewday_offset, emon,
             other) = res.groups()
            smon_id = Daterange.get_month_id(smon)
            emon_id = Daterange.get_month_id(emon)
            swday_id = Daterange.get_weekday_id(swday)
            ewday_id = Daterange.get_weekday_id(ewday)
            data = {
                'syear': 0,
                'smon': smon_id,
                'smday': 0,
                'swday': swday_id,
                'swday_offset': swday_offset,
                'eyear': 0,
                'emon': emon_id,
                'emday': 0,
                'ewday': ewday_id,
                'ewday_offset': ewday_offset,
                'skip_interval': 0,
                'other': other
            }
            dateranges.append(MonthWeekDayDaterange(data))
            return

        res = re.search(r'([a-z]*) ([\d-]+) - ([\d-]+)[\s\t]*([0-9:, -]+)',
                        entry)
        if res is not None:
            (t00, smday, emday, other) = res.groups()
            if t00 in Daterange.weekdays:
                swday = Daterange.get_weekday_id(t00)
                swday_offset = smday
                ewday = swday
                ewday_offset = emday
                data = {
                    'syear': 0,
                    'smon': 0,
                    'smday': 0,
                    'swday': swday,
                    'swday_offset': swday_offset,
                    'eyear': 0,
                    'emon': 0,
                    'emday': 0,
                    'ewday': ewday,
                    'ewday_offset': ewday_offset,
                    'skip_interval': 0,
                    'other': other
                }
                dateranges.append(WeekDayDaterange(data))
                return
            elif t00 in Daterange.months:
                smon = Daterange.get_month_id(t00)
                emon = smon
                data = {
                    'syear': 0,
                    'smon': smon,
                    'smday': smday,
                    'swday': 0,
                    'swday_offset': 0,
                    'eyear': 0,
                    'emon': emon,
                    'emday': emday,
                    'ewday': 0,
                    'ewday_offset': 0,
                    'skip_interval': 0,
                    'other': other
                }
                dateranges.append(MonthDateDaterange(data))
                return
            elif t00 == 'day':
                data = {
                    'syear': 0,
                    'smon': 0,
                    'smday': smday,
                    'swday': 0,
                    'swday_offset': 0,
                    'eyear': 0,
                    'emon': 0,
                    'emday': emday,
                    'ewday': 0,
                    'ewday_offset': 0,
                    'skip_interval': 0,
                    'other': other
                }
                dateranges.append(MonthDayDaterange(data))
                return

        res = re.search(
            r'([a-z]*) ([\d-]+) - ([a-z]*) ([\d-]+)[\s\t]*([0-9:, -]+)', entry)
        if res is not None:
            (t00, smday, t01, emday, other) = res.groups()
            if t00 in Daterange.weekdays and t01 in Daterange.weekdays:
                swday = Daterange.get_weekday_id(t00)
                ewday = Daterange.get_weekday_id(t01)
                swday_offset = smday
                ewday_offset = emday
                data = {
                    'syear': 0,
                    'smon': 0,
                    'smday': 0,
                    'swday': swday,
                    'swday_offset': swday_offset,
                    'eyear': 0,
                    'emon': 0,
                    'emday': 0,
                    'ewday': ewday,
                    'ewday_offset': ewday_offset,
                    'skip_interval': 0,
                    'other': other
                }
                dateranges.append(WeekDayDaterange(data))
                return
            elif t00 in Daterange.months and t01 in Daterange.months:
                smon = Daterange.get_month_id(t00)
                emon = Daterange.get_month_id(t01)
                data = {
                    'syear': 0,
                    'smon': smon,
                    'smday': smday,
                    'swday': 0,
                    'swday_offset': 0,
                    'eyear': 0,
                    'emon': emon,
                    'emday': emday,
                    'ewday': 0,
                    'ewday_offset': 0,
                    'skip_interval': 0,
                    'other': other
                }
                dateranges.append(MonthDateDaterange(data))
                return
            elif t00 == 'day' and t01 == 'day':
                data = {
                    'syear': 0,
                    'smon': 0,
                    'smday': smday,
                    'swday': 0,
                    'swday_offset': 0,
                    'eyear': 0,
                    'emon': 0,
                    'emday': emday,
                    'ewday': 0,
                    'ewday_offset': 0,
                    'skip_interval': 0,
                    'other': other
                }
                dateranges.append(MonthDayDaterange(data))
                return

        res = re.search(r'([a-z]*) ([\d-]+) ([a-z]*)[\s\t]*([0-9:, -]+)',
                        entry)
        if res is not None:
            (t00, t02, t01, other) = res.groups()
            if t00 in Daterange.weekdays and t01 in Daterange.months:
                swday = Daterange.get_weekday_id(t00)
                smon = Daterange.get_month_id(t01)
                emon = smon
                ewday = swday
                ewday_offset = t02
                data = {
                    'syear': 0,
                    'smon': smon,
                    'smday': 0,
                    'swday': swday,
                    'swday_offset': t02,
                    'eyear': 0,
                    'emon': emon,
                    'emday': 0,
                    'ewday': ewday,
                    'ewday_offset': ewday_offset,
                    'skip_interval': 0,
                    'other': other
                }
                dateranges.append(MonthWeekDayDaterange(data))
                return
            if not t01:
                if t00 in Daterange.weekdays:
                    swday = Daterange.get_weekday_id(t00)
                    swday_offset = t02
                    ewday = swday
                    ewday_offset = swday_offset
                    data = {
                        'syear': 0,
                        'smon': 0,
                        'smday': 0,
                        'swday': swday,
                        'swday_offset': swday_offset,
                        'eyear': 0,
                        'emon': 0,
                        'emday': 0,
                        'ewday': ewday,
                        'ewday_offset': ewday_offset,
                        'skip_interval': 0,
                        'other': other
                    }
                    dateranges.append(WeekDayDaterange(data))
                    return
                if t00 in Daterange.months:
                    smon = Daterange.get_month_id(t00)
                    emon = smon
                    emday = t02
                    data = {
                        'syear': 0,
                        'smon': smon,
                        'smday': t02,
                        'swday': 0,
                        'swday_offset': 0,
                        'eyear': 0,
                        'emon': emon,
                        'emday': emday,
                        'ewday': 0,
                        'ewday_offset': 0,
                        'skip_interval': 0,
                        'other': other
                    }
                    dateranges.append(MonthDateDaterange(data))
                    return
                if t00 == 'day':
                    emday = t02
                    data = {
                        'syear': 0,
                        'smon': 0,
                        'smday': t02,
                        'swday': 0,
                        'swday_offset': 0,
                        'eyear': 0,
                        'emon': 0,
                        'emday': emday,
                        'ewday': 0,
                        'ewday_offset': 0,
                        'skip_interval': 0,
                        'other': other
                    }
                    dateranges.append(MonthDayDaterange(data))
                    return

        res = re.search(r'([a-z]*)[\s\t]+([0-9:, -]+)', entry)
        if res is not None:
            (t00, other) = res.groups()
            if t00 in Daterange.weekdays:
                day = t00
                data = {'day': day, 'other': other}
                dateranges.append(StandardDaterange(data))
                return
        logger.info("[timeentry::%s] no match for %s", self.get_name(), entry)
        self.invalid_entries.append(entry)

    def apply_inheritance(self):
        """
        Inherite no properties and no custom variables for timeperiod

        :return: None
        """
        pass

    def explode(self):
        """
        Try to resolve all unresolved elements

        :return: None
        """
        for entry in self.unresolved:
            self.resolve_daterange(self.dateranges, entry)
        self.unresolved = []

    def linkify(self, timeperiods):
        """
        Will make timeperiod in exclude with id of the timeperiods

        :param timeperiods: Timeperiods object
        :type timeperiods:
        :return: None
        """
        new_exclude = []
        if hasattr(self, 'exclude') and self.exclude != []:
            logger.debug("[timeentry::%s] have excluded %s", self.get_name(),
                         self.exclude)
            excluded_tps = self.exclude
            for tp_name in excluded_tps:
                timepriod = timeperiods.find_by_name(tp_name.strip())
                if timepriod is not None:
                    new_exclude.append(timepriod.uuid)
                else:
                    msg = "[timeentry::%s] unknown %s timeperiod" % (
                        self.get_name(), tp_name)
                    self.configuration_errors.append(msg)
        self.exclude = new_exclude

    def check_exclude_rec(self):
        """
        Check if this timeperiod is tagged

        :return: if tagged return false, if not true
        :rtype: bool
        """
        if self.rec_tag:
            msg = "[timeentry::%s] is in a loop in exclude parameter" % (
                self.get_name())
            self.configuration_errors.append(msg)
            return False
        self.rec_tag = True
        for timeperiod in self.exclude:
            timeperiod.check_exclude_rec()
        return True

    def fill_data_brok_from(self, data, brok_type):
        """
        Add timeperiods from brok

        :param data: timeperiod dictionnary
        :type data: dict
        :param brok_type: brok type
        :type brok_type: string
        :return: None
        """
        cls = self.__class__
        # Now config properties
        for prop, entry in cls.properties.items():
            # Is this property intended for broking?
            # if 'fill_brok' in entry:
            if brok_type in entry.fill_brok:
                if hasattr(self, prop):
                    data[prop] = getattr(self, prop)
                elif entry.has_default:
                    data[prop] = entry.default
Exemplo n.º 20
0
class Realm(Itemgroup):
    """Realm class is used to implement realm.
    It is basically a group of Hosts assigned to a specific Scheduler/Poller
    (other daemon are optional)

    """
    my_type = 'realm'
    members_property = "members"
    group_members_property = "realm_members"

    properties = Itemgroup.properties.copy()
    properties.update({
        'realm_name':
        StringProp(default=u'', fill_brok=['full_status']),
        'alias':
        StringProp(default=u'', fill_brok=['full_status']),
        'realm_members':
        ListProp(default=[], split_on_comma=True),
        'group_members':
        ListProp(default=[], split_on_comma=True),
        'higher_realms':
        ListProp(default=[], split_on_comma=True),
        'default':
        BoolProp(default=False)
    })

    running_properties = Itemgroup.running_properties.copy()
    running_properties.update({
        # Indicate if some only passively or actively checks host exist in the realm
        'passively_checked_hosts': BoolProp(default=None),
        'actively_checked_hosts': BoolProp(default=None),
        # Those lists contain only the uuid of the satellite link, not the whole object!
        'arbiters': ListProp(default=[]),
        'schedulers': ListProp(default=[]),
        'brokers': ListProp(default=[]),
        'pollers': ListProp(default=[]),
        'reactionners': ListProp(default=[]),
        'receivers': ListProp(default=[]),
        'potential_schedulers': ListProp(default=[]),
        'potential_brokers': ListProp(default=[]),
        'potential_pollers': ListProp(default=[]),
        'potential_reactionners': ListProp(default=[]),
        'potential_receivers': ListProp(default=[]),
        # Once configuration is prepared, the count of the hosts in the realm
        'hosts_count': IntegerProp(default=0),
        'packs': DictProp(default={}),
        'parts': DictProp(default={}),
        # Realm level in the realms hierarchy
        'level': IntegerProp(default=-1),
        # All the sub realms (children and grand-children)
        'all_sub_members': ListProp(default=[]),
        'all_sub_members_names': ListProp(default=[]),
    })

    macros = {
        'REALMNAME': 'realm_name',
        'REALMDEFAULT': 'default',
        'REALMMEMBERS': 'members',
        'REALMREALM_MEMBERS': 'realm_members',
        'REALMGROUP_MEMBERS': 'group_members',
        'REALMHOSTS_COUNT': 'hosts_count',
    }

    def __init__(self, params=None, parsing=True):
        super(Realm, self).__init__(params, parsing)

        self.fill_default()

        # Define a packs list for the configuration preparation
        self.packs = []
        # Once the configuration got prepared, packs becomes a dictionary!
        # packs is a dictionary indexed with the configuration part
        # number and containing the list of hosts

        # List of satellites related to the realm
        self.to_satellites = {
            'reactionner': {},
            'poller': {},
            'broker': {},
            'receiver': {}
        }

        # List of satellites that need a configuration dispatch
        self.to_satellites_need_dispatch = {
            'reactionner': {},
            'poller': {},
            'broker': {},
            'receiver': {}
        }

        # List of satellites with their managed configuration
        self.to_satellites_managed_by = {
            'reactionner': {},
            'poller': {},
            'broker': {},
            'receiver': {}
        }

        # Attributes depending of the satellite type
        for sat_type in [
                'arbiter', 'scheduler', 'reactionner', 'poller', 'broker',
                'receiver'
        ]:
            # Minimum is to have one satellite
            setattr(self, "nb_%ss" % sat_type, 0)
            setattr(self, 'potential_%ss' % sat_type, [])

    def __repr__(self):
        res = '<%r %r (%d)' % (self.__class__.__name__, self.get_name(),
                               self.level)
        if self.realm_members:
            res = res + ', %d sub-realms: %r' \
                        % (len(self.realm_members), ', '.join([str(s) for s in self.realm_members]))
            if self.all_sub_members_names:
                res = res + ', %d all sub-realms: %r' \
                            % (len(self.all_sub_members_names),
                               ', '.join([str(s) for s in self.all_sub_members_names]))
        if self.hosts_count:
            res = res + ', %d hosts' % self.hosts_count
        if getattr(self, 'parts', None):
            res = res + ', %d parts' % len(self.parts)
        if getattr(self, 'packs', None):
            res = res + ', %d packs' % len(self.packs)
        return res + '/>'

    __str__ = __repr__

    @property
    def name(self):
        """Get the realm name"""
        return self.get_name()

    def get_name(self):
        """Accessor to realm_name attribute

        :return: realm name
        :rtype: str
        """
        return getattr(self, 'realm_name', 'unset')

    def add_group_members(self, members):
        """Add a new group member to the groups list

        :param members: member name
        :type members: str
        :return: None
        """
        if not isinstance(members, list):
            members = [members]

        if not getattr(self, 'group_members', None):
            self.group_members = members
        else:
            self.group_members.extend(members)

    def prepare_satellites(self, satellites):
        """Update the following attributes of a realm::

        * nb_*satellite type*s
        * self.potential_*satellite type*s

        (satellite types are scheduler, reactionner, poller, broker and receiver)

        :param satellites: dict of SatelliteLink objects
        :type satellites: dict
        :return: None
        """
        for sat_type in [
                "scheduler", "reactionner", "poller", "broker", "receiver"
        ]:
            # We get potential TYPE at realm level first
            for sat_link_uuid in getattr(self, "%ss" % sat_type):
                if sat_link_uuid not in satellites:
                    continue
                sat_link = satellites[sat_link_uuid]

                # Found our declared satellite in the provided satellites
                if sat_link.active and not sat_link.spare:
                    # Generic increment : realm.nb_TYPE += 1
                    setattr(self, "nb_%ss" % sat_type,
                            getattr(self, "nb_%ss" % sat_type) + 1)
                    break
                else:
                    self.add_error(
                        "Realm %s, satellite %s declared in the realm is not found "
                        "in the allowed satellites!" %
                        (self.name, sat_link.name))
                    logger.error(
                        "Satellite %s declared in the realm %s not found "
                        "in the allowed satellites!", sat_link.name, self.name)

        logger.info(
            " Realm %s: (in/potential) (schedulers:%d/%d) (pollers:%d/%d) "
            "(reactionners:%d/%d) (brokers:%d/%d) (receivers:%d/%d)",
            self.name, self.nb_schedulers, len(self.potential_schedulers),
            self.nb_pollers, len(self.potential_pollers), self.nb_reactionners,
            len(self.potential_reactionners), self.nb_brokers,
            len(self.potential_brokers), self.nb_receivers,
            len(self.potential_receivers))

    def get_realms_by_explosion(self, realms):
        """Get all members of this realm including members of sub-realms on multi-levels

        :param realms: realms list, used to look for a specific one
        :type realms: alignak.objects.realm.Realms
        :return: list of members and add realm to realm_members attribute
        :rtype: list
        """
        # If rec_tag is already set, then we detected a loop in the realms hierarchy!
        if getattr(self, 'rec_tag', False):
            self.add_error(
                "Error: there is a loop in the realm definition %s" %
                self.get_name())
            return None

        # Ok, not in a loop, we tag the realm and parse its members
        self.rec_tag = True

        # Order realm members list by name
        self.realm_members = sorted(self.realm_members)
        for member in self.realm_members:
            realm = realms.find_by_name(member)
            if not realm:
                self.add_unknown_members(member)
                continue

            children = realm.get_realms_by_explosion(realms)
            if children is None:
                # We got a loop in our children definition
                self.all_sub_members = []
                self.realm_members = []
                return None

        # Return the list of all unique members
        return self.all_sub_members

    def set_level(self, level, realms):
        """Set the realm level in the realms hierarchy

        :return: None
        """
        self.level = level
        if not self.level:
            logger.info("- %s", self.get_name())
        else:
            logger.info(" %s %s", '+' * self.level, self.get_name())
        self.all_sub_members = []
        self.all_sub_members_names = []
        for child in sorted(self.realm_members):
            child = realms.find_by_name(child)
            if not child:
                continue

            self.all_sub_members.append(child.uuid)
            self.all_sub_members_names.append(child.get_name())
            grand_children = child.set_level(self.level + 1, realms)
            for grand_child in grand_children:
                if grand_child in self.all_sub_members_names:
                    continue
                grand_child = realms.find_by_name(grand_child)
                if grand_child:
                    self.all_sub_members_names.append(grand_child.get_name())
                    self.all_sub_members.append(grand_child.uuid)
        return self.all_sub_members_names

    def get_all_subs_satellites_by_type(self, sat_type, realms):
        """Get all satellites of the wanted type in this realm recursively

        :param sat_type: satellite type wanted (scheduler, poller ..)
        :type sat_type:
        :param realms: all realms
        :type realms: list of realm object
        :return: list of satellite in this realm
        :rtype: list
        """
        res = copy.copy(getattr(self, sat_type))
        for member in self.all_sub_members:
            res.extend(realms[member].get_all_subs_satellites_by_type(
                sat_type, realms))
        return res

    def get_satellites_by_type(self, s_type):
        """Generic function to access one of the satellite attribute
        ie : self.pollers, self.reactionners ...

        :param s_type: satellite type wanted
        :type s_type: str
        :return: self.*type*s
        :rtype: list
        """

        if hasattr(self, s_type + 's'):
            return getattr(self, s_type + 's')

        logger.debug("[realm %s] do not have this kind of satellites: %s",
                     self.name, s_type)
        return []

    def get_potential_satellites_by_type(self, satellites, s_type):
        """Generic function to access one of the potential satellite attribute
        ie : self.potential_pollers, self.potential_reactionners ...

        :param satellites: list of SatelliteLink objects
        :type satellites: SatelliteLink list
        :param s_type: satellite type wanted
        :type s_type: str
        :return: self.potential_*type*s
        :rtype: list
        """
        if not hasattr(self, 'potential_' + s_type + 's'):
            logger.debug("[realm %s] do not have this kind of satellites: %s",
                         self.name, s_type)
            return []

        matching_satellites = []
        for sat_link in satellites:
            if sat_link.uuid in getattr(self, s_type + 's'):
                matching_satellites.append(sat_link)
        if matching_satellites:
            logger.debug("- found %ss: %s", s_type, matching_satellites)
            return matching_satellites

        for sat_link in satellites:
            if sat_link.uuid in getattr(self, 'potential_' + s_type + 's'):
                matching_satellites.append(sat_link)
                # Do not limit to one satellite!
                # break

        logger.debug("- potential %ss: %s", s_type, matching_satellites)
        return matching_satellites

    def get_nb_of_must_have_satellites(self, s_type):
        """Generic function to access one of the number satellite attribute
        ie : self.nb_pollers, self.nb_reactionners ...

        :param s_type: satellite type wanted
        :type s_type: str
        :return: self.nb_*type*s
        :rtype: int
        """
        if hasattr(self, 'nb_' + s_type + 's'):
            return getattr(self, 'nb_' + s_type + 's')

        logger.debug("[realm %s] do not have this kind of satellites: %s",
                     self.name, s_type)
        return 0

    def get_links_for_a_broker(self,
                               pollers,
                               reactionners,
                               receivers,
                               realms,
                               manage_sub_realms=False):
        """Get a configuration dictionary with pollers, reactionners and receivers links
        for a broker

        :param pollers: pollers
        :type pollers:
        :param reactionners: reactionners
        :type reactionners:
        :param receivers: receivers
        :type receivers:
        :param realms: realms
        :type realms:
        :param manage_sub_realms:
        :type manage_sub_realms: True if the borker manages sub realms

        :return: dict containing pollers, reactionners and receivers links (key is satellite id)
        :rtype: dict
        """

        # Create void satellite links
        cfg = {
            'pollers': {},
            'reactionners': {},
            'receivers': {},
        }

        # Our self.daemons are only identifiers... that we use to fill the satellite links
        for poller_id in self.pollers:
            poller = pollers[poller_id]
            cfg['pollers'][poller.uuid] = poller.give_satellite_cfg()

        for reactionner_id in self.reactionners:
            reactionner = reactionners[reactionner_id]
            cfg['reactionners'][
                reactionner.uuid] = reactionner.give_satellite_cfg()

        for receiver_id in self.receivers:
            receiver = receivers[receiver_id]
            cfg['receivers'][receiver.uuid] = receiver.give_satellite_cfg()

        # If the broker manages sub realms, fill the satellite links...
        if manage_sub_realms:
            # Now pollers
            for poller_id in self.get_all_subs_satellites_by_type(
                    'pollers', realms):
                poller = pollers[poller_id]
                cfg['pollers'][poller.uuid] = poller.give_satellite_cfg()

            # Now reactionners
            for reactionner_id in self.get_all_subs_satellites_by_type(
                    'reactionners', realms):
                reactionner = reactionners[reactionner_id]
                cfg['reactionners'][
                    reactionner.uuid] = reactionner.give_satellite_cfg()

            # Now receivers
            for receiver_id in self.get_all_subs_satellites_by_type(
                    'receivers', realms):
                receiver = receivers[receiver_id]
                cfg['receivers'][receiver.uuid] = receiver.give_satellite_cfg()

        return cfg

    def get_links_for_a_scheduler(self, pollers, reactionners, brokers):
        """Get a configuration dictionary with pollers, reactionners and brokers links
        for a scheduler

        :return: dict containing pollers, reactionners and brokers links (key is satellite id)
        :rtype: dict
        """

        # Create void satellite links
        cfg = {
            'pollers': {},
            'reactionners': {},
            'brokers': {},
        }

        # Our self.daemons are only identifiers... that we use to fill the satellite links
        try:
            for poller in self.pollers + self.get_potential_satellites_by_type(
                    pollers, "poller"):
                if poller in pollers:
                    poller = pollers[poller]
                cfg['pollers'][poller.uuid] = poller.give_satellite_cfg()

            for reactionner in self.reactionners + self.get_potential_satellites_by_type(
                    reactionners, "reactionner"):
                if reactionner in reactionners:
                    reactionner = reactionners[reactionner]
                cfg['reactionners'][
                    reactionner.uuid] = reactionner.give_satellite_cfg()

            for broker in self.brokers + self.get_potential_satellites_by_type(
                    brokers, "broker"):
                if broker in brokers:
                    broker = brokers[broker]
                cfg['brokers'][broker.uuid] = broker.give_satellite_cfg()
        except Exception as exp:  # pylint: disable=broad-except
            logger.exception("realm.get_links_for_a_scheduler: %s", exp)

            # for poller in self.get_potential_satellites_by_type(pollers, "poller"):
            #     logger.info("Poller: %s", poller)
            #     cfg['pollers'][poller.uuid] = poller.give_satellite_cfg()
            #
            # for reactionner in self.get_potential_satellites_by_type(reactionners, "reactionner"):
            #     cfg['reactionners'][reactionner.uuid] = reactionner.give_satellite_cfg()
            #
            # for broker in self.get_potential_satellites_by_type(brokers, "broker"):
            #     cfg['brokers'][broker.uuid] = broker.give_satellite_cfg()

        return cfg
Exemplo n.º 21
0
class ActionBase(AlignakObject):
    # pylint: disable=too-many-instance-attributes
    """
    This abstract class is used to have a common base for both actions (event handlers and
    notifications) and checks.

    The Action may be on internal one if it does require to use a Worker process to run the
    action because the Scheduler is able to resolve the action by itseld.

    This class is specialized according to the running OS. Currently, only Linux/Unix like OSes
    are tested
    """
    process = None

    properties = {
        'is_a': StringProp(default=u''),
        'type': StringProp(default=u''),
        'internal': BoolProp(default=False),
        'creation_time': FloatProp(default=0.0),
        '_is_orphan': BoolProp(default=False),
        '_in_timeout': BoolProp(default=False),
        'status': StringProp(default=ACT_STATUS_SCHEDULED),
        'exit_status': IntegerProp(default=3),
        'output': StringProp(default=u'', fill_brok=['full_status']),
        'long_output': StringProp(default=u'', fill_brok=['full_status']),
        'perf_data': StringProp(default=u'', fill_brok=['full_status']),
        't_to_go': FloatProp(default=0.0),
        'check_time': IntegerProp(default=0),
        'last_poll': IntegerProp(default=0),
        'execution_time': FloatProp(default=0.0),
        'wait_time': FloatProp(default=0.001),
        'u_time': FloatProp(default=0.0),
        's_time': FloatProp(default=0.0),
        'reactionner_tag': StringProp(default=u'None'),
        'env': DictProp(default={}),
        'module_type': StringProp(default=u'fork', fill_brok=['full_status']),
        'my_worker': StringProp(default=u'none'),
        'command': StringProp(default=''),
        'timeout': IntegerProp(default=10),
        'ref': StringProp(default=u'unset'),
        'ref_type': StringProp(default=u'unset'),
        'my_scheduler': StringProp(default=u'unassigned'),
    }

    def __init__(self, params=None, parsing=False):
        super(ActionBase, self).__init__(params, parsing=parsing)

        # Set a creation time only if not provided
        if not params or 'creation_time' not in params:
            self.creation_time = time.time()
        # Set actions log only if not provided
        if not params or 'log_actions' not in params:
            self.log_actions = 'ALIGNAK_LOG_ACTIONS' in os.environ

        # Fill default parameters
        self.fill_default()

    def is_launchable(self, timestamp):
        """Check if this action can be launched based on current time

        :param timestamp: time to compare
        :type timestamp: int
        :return: True if timestamp >= self.t_to_go, False otherwise
        :rtype: bool
        """
        if self.t_to_go is None:
            return False
        return timestamp >= self.t_to_go

    def get_local_environnement(self):
        """
        Mix the environment and the environment variables into a new local
        environment dictionary

        Note: We cannot just update the global os.environ because this
        would effect all other checks.

        :return: local environment variables
        :rtype: dict
        """
        # Do not use copy.copy() here, as the resulting copy still
        # changes the real environment (it is still a os._Environment
        # instance).
        local_env = os.environ.copy()
        for local_var in self.env:
            local_env[local_var] = self.env[local_var]
        return local_env

    def execute(self):
        """Start this action command in a subprocess.

        :raise: ActionError
            'toomanyopenfiles' if too many opened files on the system
            'no_process_launched' if arguments parsing failed
            'process_launch_failed': if the process launch failed

        :return: reference to the started process
        :rtype: psutil.Process
        """
        self.status = ACT_STATUS_LAUNCHED
        self.check_time = time.time()
        self.wait_time = 0.0001
        self.last_poll = self.check_time

        # Get a local env variables with our additional values
        self.local_env = self.get_local_environnement()

        # Initialize stdout and stderr.
        self.stdoutdata = ''
        self.stderrdata = ''

        logger.debug("Launch command: '%s', ref: %s, timeout: %s",
                     self.command, self.ref, self.timeout)
        if self.log_actions:
            if os.environ['ALIGNAK_LOG_ACTIONS'] == 'WARNING':
                logger.warning("Launch command: '%s'", self.command)
            else:
                logger.info("Launch command: '%s'", self.command)

        return self._execute()  # OS specific part

    def get_outputs(self, out, max_plugins_output_length):
        """Get check outputs from single output (split perfdata etc).

        Updates output, perf_data and long_output attributes.

        :param out: output data of a check
        :type out: str
        :param max_output: max plugin data length
        :type max_output: int
        :return: None
        """
        # Squeeze all output after max_plugins_output_length
        out = out[:max_plugins_output_length]
        # manage escaped pipes
        out = out.replace(r'\|', '___PROTECT_PIPE___')
        # Then cuts by lines
        elts = out.split('\n')
        # For perf data
        elts_line1 = elts[0].split('|')

        # First line before | is output, strip it
        self.output = elts_line1[0].strip().replace('___PROTECT_PIPE___', '|')
        try:
            self.output = self.output.decode('utf8', 'ignore')
        except UnicodeEncodeError:
            pass
        except AttributeError:
            pass

        # Init perfdata as empty
        self.perf_data = ''
        # After | it is perfdata, strip it
        if len(elts_line1) > 1:
            self.perf_data = elts_line1[1].strip().replace(
                '___PROTECT_PIPE___', '|')

        # Now manage others lines. Before the | it's long_output
        # And after it's all perf_data, \n joined
        long_output = []
        in_perfdata = False
        for line in elts[1:]:
            # if already in perfdata, direct append
            if in_perfdata:
                self.perf_data += ' ' + line.strip().replace(
                    '___PROTECT_PIPE___', '|')
            else:  # not already in perf_data, search for the | part :)
                elts = line.split('|', 1)
                # The first part will always be long_output
                long_output.append(elts[0].strip().replace(
                    '___PROTECT_PIPE___', '|'))
                if len(elts) > 1:
                    in_perfdata = True
                    self.perf_data += ' ' + elts[1].strip().replace(
                        '___PROTECT_PIPE___', '|')

        # long_output is all non output and performance data, joined with \n
        self.long_output = '\n'.join(long_output)
        # Get sure the performance data are stripped
        self.perf_data = self.perf_data.strip()

        logger.debug("Command result for '%s': %d, %s", self.command,
                     self.exit_status, self.output)

        if self.log_actions:
            if os.environ['ALIGNAK_LOG_ACTIONS'] == 'WARNING':
                logger.warning("Check result for '%s': %d, %s", self.command,
                               self.exit_status, self.output)
                if self.perf_data:
                    logger.warning("Performance data for '%s': %s",
                                   self.command, self.perf_data)
            else:
                logger.info("Check result for '%s': %d, %s", self.command,
                            self.exit_status, self.output)
                if self.perf_data:
                    logger.info("Performance data for '%s': %s", self.command,
                                self.perf_data)

    def check_finished(self, max_plugins_output_length):
        # pylint: disable=too-many-branches
        """Handle action if it is finished (get stdout, stderr, exit code...)

        :param max_plugins_output_length: max plugin data length
        :type max_plugins_output_length: int
        :return: None
        """
        self.last_poll = time.time()

        _, _, child_utime, child_stime, _ = os.times()

        # Not yet finished...
        if self.process.poll() is None:
            # We must wait, but checks are variable in time so we do not wait the same
            # for a little check or a long ping. So we do like TCP: slow start with a very
            # shot time (0.0001 s) increased *2 but do not wait more than 0.5 s.
            self.wait_time = min(self.wait_time * 2, 0.5)
            now = time.time()
            # This log is really spamming... uncomment if you really need this information :)
            # logger.debug("%s - Process pid=%d is still alive", now, self.process.pid)

            # Get standard outputs in non blocking mode from the process streams
            stdout = no_block_read(self.process.stdout)
            stderr = no_block_read(self.process.stderr)

            try:
                self.stdoutdata += stdout.decode("utf-8")
                self.stderrdata += stderr.decode("utf-8")
            except AttributeError:
                pass

            if (now - self.check_time) > self.timeout:
                logger.warning(
                    "Process pid=%d spent too much time: %.2f seconds",
                    self.process.pid, now - self.check_time)
                self._in_timeout = True
                self._kill()
                self.status = ACT_STATUS_TIMEOUT
                self.execution_time = now - self.check_time
                self.exit_status = 3

                if self.log_actions:
                    if os.environ['ALIGNAK_LOG_ACTIONS'] == 'WARNING':
                        logger.warning("Action '%s' exited on timeout (%d s)",
                                       self.command, self.timeout)
                    else:
                        logger.info("Action '%s' exited on timeout (%d s)",
                                    self.command, self.timeout)

                # Do not keep the process objcet
                del self.process

                # Replace stdout with stderr if stdout is empty
                self.stdoutdata = self.stdoutdata.strip()
                if not self.stdoutdata:
                    self.stdoutdata = self.stderrdata

                # Now grep what we want in the output
                self.get_outputs(self.stdoutdata, max_plugins_output_length)

                # We can clean the useless properties now
                del self.stdoutdata
                del self.stderrdata

                # Get the user and system time
                _, _, n_child_utime, n_child_stime, _ = os.times()
                self.u_time = n_child_utime - child_utime
                self.s_time = n_child_stime - child_stime

                return
            return

        logger.debug("Process pid=%d exited with %d", self.process.pid,
                     self.process.returncode)

        if fcntl:
            # Get standard outputs in non blocking mode from the process streams
            stdout = no_block_read(self.process.stdout)
            stderr = no_block_read(self.process.stderr)
        else:
            # Get standard outputs from the communicate function
            (stdout, stderr) = self.process.communicate()

        try:
            self.stdoutdata += stdout.decode("utf-8")
        except (UnicodeDecodeError, AttributeError):
            self.stdoutdata += stdout

        try:
            self.stderrdata += stderr.decode("utf-8")
        except (UnicodeDecodeError, AttributeError):
            self.stderrdata += stderr

        self.exit_status = self.process.returncode
        if self.log_actions:
            if os.environ['ALIGNAK_LOG_ACTIONS'] == 'WARNING':
                logger.warning("Action '%s' exited with code %d", self.command,
                               self.exit_status)
            else:
                logger.info("Action '%s' exited with code %d", self.command,
                            self.exit_status)

        # We do not need the process now
        del self.process

        # check for bad syntax in command line:
        if (self.stderrdata.find('sh: -c: line 0: unexpected EOF') >= 0 or
            (self.stderrdata.find('sh: -c: ') >= 0
             and self.stderrdata.find(': Syntax') >= 0 or
             self.stderrdata.find('Syntax error: Unterminated quoted string')
             >= 0)):
            logger.warning("Bad syntax in command line!")
            # Very, very ugly. But subprocess._handle_exitstatus does
            # not see a difference between a regular "exit 1" and a
            # bailing out shell. Strange, because strace clearly shows
            # a difference. (exit_group(1) vs. exit_group(257))
            self.stdoutdata = self.stdoutdata + self.stderrdata
            self.exit_status = 3

        # Make sure that exit code is a valid exit code
        if self.exit_status not in VALID_EXIT_STATUS:
            self.exit_status = 3

        # Replace stdout with stderr if stdout is empty
        self.stdoutdata = self.stdoutdata.strip()
        if not self.stdoutdata:
            self.stdoutdata = self.stderrdata

        # Now grep what we want in the output
        self.get_outputs(self.stdoutdata, max_plugins_output_length)

        # We can clean the useless properties now
        del self.stdoutdata
        del self.stderrdata

        self.status = ACT_STATUS_DONE
        self.execution_time = time.time() - self.check_time

        # Also get the system and user times
        _, _, n_child_utime, n_child_stime, _ = os.times()
        self.u_time = n_child_utime - child_utime
        self.s_time = n_child_stime - child_stime

    def copy_shell__(self, new_i):
        """Create all attributes listed in 'ONLY_COPY_PROP' and return `self` with these attributes.

        :param new_i: object to
        :type new_i: object
        :return: object with new properties added
        :rtype: object
        """
        for prop in ONLY_COPY_PROP:
            setattr(new_i, prop, getattr(self, prop))
        return new_i

    def got_shell_characters(self):
        """Check if the command_attribute (command line) has shell characters
        Shell characters are : '!', '$', '^', '&', '*', '(', ')', '~', '[', ']',
                               '|', '{', '}', ';', '<', '>', '?', '`'

        :return: True if one shell character is found, False otherwise
        :rtype: bool
        """
        return any(c in SHELLCHARS for c in self.command)

    def _execute(self, force_shell=False):
        """Execute action in a subprocess

        :return: None
        """
        pass

    def _kill(self):
        """Kill the action and close fds
        :return: None
        """
        pass
Exemplo n.º 22
0
class ContactDowntime(AlignakObject):
    """ContactDowntime class allows a contact to be in downtime. During this time
    the contact won't get notifications

    """

    properties = {
        'start_time': IntegerProp(default=0, fill_brok=['full_status']),
        'end_time': IntegerProp(default=0, fill_brok=['full_status']),
        'author': StringProp(default=u'', fill_brok=['full_status']),
        'comment': StringProp(default=u''),
        'is_in_effect': BoolProp(default=False),
        'can_be_deleted': BoolProp(default=False),
        'ref': StringProp(default=u''),
    }

    # Schedule a contact downtime. It's far more easy than a host/service
    # one because we got a beginning, and an end. That's all for running.
    # got also an author and a comment for logging purpose.
    def __init__(self, params, parsing=False):
        super(ContactDowntime, self).__init__(params, parsing=parsing)

        self.fill_default()

    def check_activation(self, contacts):
        """Enter or exit downtime if necessary

        :return: None
        """
        now = time.time()
        was_is_in_effect = self.is_in_effect
        self.is_in_effect = (self.start_time <= now <= self.end_time)

        # Raise a log entry when we get in the downtime
        if not was_is_in_effect and self.is_in_effect:
            self.enter(contacts)

        # Same for exit purpose
        if was_is_in_effect and not self.is_in_effect:
            self.exit(contacts)

    def in_scheduled_downtime(self):
        """Getter for is_in_effect attribute

        :return: True if downtime is active, False otherwise
        :rtype: bool
        """
        return self.is_in_effect

    def enter(self, contacts):
        """Wrapper to call raise_enter_downtime_log_entry for ref (host/service)

        :return: None
        """
        contact = contacts[self.ref]
        contact.raise_enter_downtime_log_entry()

    def exit(self, contacts):
        """Wrapper to call raise_exit_downtime_log_entry for ref (host/service)
        set can_be_deleted to True

        :return: None
        """
        contact = contacts[self.ref]
        contact.raise_exit_downtime_log_entry()
        self.can_be_deleted = True

    def cancel(self, contacts):
        """Wrapper to call raise_cancel_downtime_log_entry for ref (host/service)
        set can_be_deleted to True
        set is_in_effect to False

        :return: None
        """
        self.is_in_effect = False
        contact = contacts[self.ref]
        contact.raise_cancel_downtime_log_entry()
        self.can_be_deleted = True
Exemplo n.º 23
0
class Escalation(Item):
    """Escalation class is used to implement notification escalation

    """
    _id = 1  # zero is always special in database, so we do not take risk here
    my_type = 'escalation'

    properties = Item.properties.copy()
    properties.update({
        'escalation_name':
        StringProp(),
        'first_notification':
        IntegerProp(),
        'last_notification':
        IntegerProp(),
        'first_notification_time':
        IntegerProp(),
        'last_notification_time':
        IntegerProp(),
        # by default don't use the notification_interval defined in
        # the escalation, but the one defined by the object
        'notification_interval':
        IntegerProp(default=-1),
        'escalation_period':
        StringProp(default=''),
        'escalation_options':
        ListProp(default=['d', 'u', 'r', 'w', 'c'], split_on_coma=True),
        'contacts':
        ListProp(default=[], split_on_coma=True),
        'contact_groups':
        ListProp(default=[], split_on_coma=True),
    })

    running_properties = Item.running_properties.copy()
    running_properties.update({
        'time_based': BoolProp(default=False),
    })

    special_properties = ('contacts', 'contact_groups',
                          'first_notification_time', 'last_notification_time')
    special_properties_time_based = ('contacts', 'contact_groups',
                                     'first_notification', 'last_notification')

    def get_name(self):
        """Accessor to escalation_name attribute

        :return: escalation name
        :rtype: str
        """
        return self.escalation_name

    def is_eligible(self, timestamp, status, notif_number, in_notif_time,
                    interval):
        """Check if the escalation is eligible (notification is escalated or not)

        Escalation is NOT eligible in ONE of the following condition is fulfilled::

        * escalation is not time based and notification number not in range
          [first_notification;last_notification] (if last_notif == 0, it's infinity)
        * escalation is time based and notification time not in range
          [first_notification_time;last_notification_time] (if last_notif_time == 0, it's infinity)
        * status does not matches escalation_options ('WARNING' <=> 'w' ...)
        * escalation_period is not legit for this time (now usually)

        :param timestamp: timestamp to check if timeperiod is valid
        :type timestamp: int
        :param status: item status (one of the small_states key)
        :type status: str
        :param notif_number: current notification number
        :type notif_number: int
        :param in_notif_time: current notification time
        :type in_notif_time: int
        :param interval: time interval length
        :type interval: int
        :return: True if no condition has been fulfilled, otherwise False
        :rtype: bool
        """
        small_states = {
            'WARNING': 'w',
            'UNKNOWN': 'u',
            'CRITICAL': 'c',
            'RECOVERY': 'r',
            'FLAPPING': 'f',
            'DOWNTIME': 's',
            'DOWN': 'd',
            'UNREACHABLE': 'u',
            'OK': 'o',
            'UP': 'o'
        }

        # If we are not time based, we check notification numbers:
        if not self.time_based:
            # Begin with the easy cases
            if notif_number < self.first_notification:
                return False

            # self.last_notification = 0 mean no end
            if self.last_notification != 0 and notif_number > self.last_notification:
                return False
        # Else we are time based, we must check for the good value
        else:
            # Begin with the easy cases
            if in_notif_time < self.first_notification_time * interval:
                return False

            # self.last_notification = 0 mean no end
            if self.last_notification_time != 0 and \
                    in_notif_time > self.last_notification_time * interval:
                return False

        # If our status is not good, we bail out too
        if status in small_states and small_states[
                status] not in self.escalation_options:
            return False

        # Maybe the time is not in our escalation_period
        if self.escalation_period is not None and \
                not self.escalation_period.is_time_valid(timestamp):
            return False

        # Ok, I do not see why not escalade. So it's True :)
        return True

    def get_next_notif_time(self, t_wished, status, creation_time, interval):
        """Get the next notification time for the escalation
        Only legit for time based escalation

        :param t_wished: time we would like to send a new notification (usually now)
        :type t_wished:
        :param status: status of the host or service
        :type status:
        :param creation_time: time the notification was created
        :type creation_time:
        :param interval: time interval length
        :type interval: int
        :return: timestamp for next notification or None
        :rtype: int | None
        """
        small_states = {
            'WARNING': 'w',
            'UNKNOWN': 'u',
            'CRITICAL': 'c',
            'RECOVERY': 'r',
            'FLAPPING': 'f',
            'DOWNTIME': 's',
            'DOWN': 'd',
            'UNREACHABLE': 'u',
            'OK': 'o',
            'UP': 'o'
        }

        # If we are not time based, we bail out!
        if not self.time_based:
            return None

        # Check if we are valid
        if status in small_states and small_states[
                status] not in self.escalation_options:
            return None

        # Look for the min of our future validity
        start = self.first_notification_time * interval + creation_time

        # If we are after the classic next time, we are not asking for a smaller interval
        if start > t_wished:
            return None

        # Maybe the time we found is not a valid one....
        if self.escalation_period is not None and not self.escalation_period.is_time_valid(
                start):
            return None

        # Ok so I ask for my start as a possibility for the next notification time
        return start

    def is_correct(self):
        """Check if all elements got a good configuration

        :return: True if the configuration is correct, otherwise False
        :rtype: bool
        """
        state = True
        cls = self.__class__

        # If we got the _time parameters, we are time based. Unless, we are not :)
        if hasattr(self, 'first_notification_time') or hasattr(
                self, 'last_notification_time'):
            self.time_based = True
            special_properties = self.special_properties_time_based
        else:  # classic ones
            special_properties = self.special_properties

        for prop, entry in cls.properties.items():
            if prop not in special_properties:
                if not hasattr(self, prop) and entry.required:
                    logger.error('%s: I do not have %s', self.get_name(), prop)
                    state = False  # Bad boy...

        # Raised all previously saw errors like unknown contacts and co
        if self.configuration_errors != []:
            state = False
            for err in self.configuration_errors:
                logger.error(err)

        # Ok now we manage special cases...
        if not hasattr(self, 'contacts') and not hasattr(
                self, 'contact_groups'):
            logger.error('%s: I do not have contacts nor contact_groups',
                         self.get_name())
            state = False

        # If time_based or not, we do not check all properties
        if self.time_based:
            if not hasattr(self, 'first_notification_time'):
                logger.error('%s: I do not have first_notification_time',
                             self.get_name())
                state = False
            if not hasattr(self, 'last_notification_time'):
                logger.error('%s: I do not have last_notification_time',
                             self.get_name())
                state = False
        else:  # we check classical properties
            if not hasattr(self, 'first_notification'):
                logger.error('%s: I do not have first_notification',
                             self.get_name())
                state = False
            if not hasattr(self, 'last_notification'):
                logger.error('%s: I do not have last_notification',
                             self.get_name())
                state = False

        return state
Exemplo n.º 24
0
class NotificationWay(Item):
    """NotificationWay class is used to implement way of sending notifications (command, periods..)

    """
    _id = 1  # zero is always special in database, so we do not take risk here
    my_type = 'notificationway'

    properties = Item.properties.copy()
    properties.update({
        'notificationway_name':
        StringProp(fill_brok=['full_status']),
        'host_notifications_enabled':
        BoolProp(default=True, fill_brok=['full_status']),
        'service_notifications_enabled':
        BoolProp(default=True, fill_brok=['full_status']),
        'host_notification_period':
        StringProp(fill_brok=['full_status']),
        'service_notification_period':
        StringProp(fill_brok=['full_status']),
        'host_notification_options':
        ListProp(default=[''], fill_brok=['full_status'], split_on_coma=True),
        'service_notification_options':
        ListProp(default=[''], fill_brok=['full_status'], split_on_coma=True),
        'host_notification_commands':
        ListProp(fill_brok=['full_status']),
        'service_notification_commands':
        ListProp(fill_brok=['full_status']),
        'min_business_impact':
        IntegerProp(default=0, fill_brok=['full_status']),
    })

    running_properties = Item.running_properties.copy()

    # This tab is used to transform old parameters name into new ones
    # so from Nagios2 format, to Nagios3 ones.
    # Or Alignak deprecated names like criticity
    old_properties = {
        'min_criticity': 'min_business_impact',
    }

    macros = {}

    special_properties = ('service_notification_commands',
                          'host_notification_commands',
                          'service_notification_period',
                          'host_notification_period')

    def get_name(self):
        """Accessor to notificationway_name attribute

        :return: notificationway name
        :rtype: str
        """
        return self.notificationway_name

    def want_service_notification(self,
                                  timestamp,
                                  state,
                                  n_type,
                                  business_impact,
                                  cmd=None):
        """Check if notification options match the state of the service
        Notification is NOT wanted in ONE of the following case::

        * service notifications are disabled
        * cmd is not in service_notification_commands
        * business_impact < self.min_business_impact
        * service_notification_period is not valid
        * state does not match service_notification_options for problem, recovery and flapping
        * state does not match host_notification_options for downtime

        :param timestamp: time we want to notify the contact (usually now)
        :type timestamp: int
        :param state: host or service state ("WARNING", "CRITICAL" ..)
        :type state: str
        :param n_type: type of notification ("PROBLEM", "RECOVERY" ..)
        :type n_type: str
        :param business_impact: impact of this service
        :type business_impact: int
        :param cmd: command launched to notify the contact
        :type cmd: str
        :return: True if no condition is matched, otherwise False
        :rtype: bool
        TODO: Simplify function
        """
        if not self.service_notifications_enabled:
            return False

        # Maybe the command we ask for are not for us, but for another notification ways
        # on the same contact. If so, bail out
        if cmd and cmd not in self.service_notification_commands:
            return False

        # If the business_impact is not high enough, we bail out
        if business_impact < self.min_business_impact:
            return False

        valid = self.service_notification_period.is_time_valid(timestamp)
        if 'n' in self.service_notification_options:
            return False
        timestamp = {
            'WARNING': 'w',
            'UNKNOWN': 'u',
            'CRITICAL': 'c',
            'RECOVERY': 'r',
            'FLAPPING': 'f',
            'DOWNTIME': 's'
        }
        if n_type == 'PROBLEM':
            if state in timestamp:
                return valid and timestamp[
                    state] in self.service_notification_options
        elif n_type == 'RECOVERY':
            if n_type in timestamp:
                return valid and timestamp[
                    n_type] in self.service_notification_options
        elif n_type == 'ACKNOWLEDGEMENT':
            return valid
        elif n_type in ('FLAPPINGSTART', 'FLAPPINGSTOP', 'FLAPPINGDISABLED'):
            return valid and 'f' in self.service_notification_options
        elif n_type in ('DOWNTIMESTART', 'DOWNTIMEEND', 'DOWNTIMECANCELLED'):
            # No notification when a downtime was cancelled. Is that true??
            # According to the documentation we need to look at _host_ options
            return valid and 's' in self.host_notification_options

        return False

    def want_host_notification(self,
                               timestamp,
                               state,
                               n_type,
                               business_impact,
                               cmd=None):
        """Check if notification options match the state of the host
        Notification is NOT wanted in ONE of the following case::

        * host notifications are disabled
        * cmd is not in host_notification_commands
        * business_impact < self.min_business_impact
        * host_notification_period is not valid
        * state does not match host_notification_options for problem, recovery, flapping and dt


        :param timestamp: time we want to notify the contact (usually now)
        :type timestamp: int
        :param state: host or service state ("WARNING", "CRITICAL" ..)
        :type state: str
        :param n_type: type of notification ("PROBLEM", "RECOVERY" ..)
        :type n_type: str
        :param business_impact: impact of this service
        :type business_impact: int
        :param cmd: command launched to notify the contact
        :type cmd: str
        :return: True if no condition is matched, otherwise False
        :rtype: bool
        TODO: Simplify function
        """
        if not self.host_notifications_enabled:
            return False

        # If the business_impact is not high enough, we bail out
        if business_impact < self.min_business_impact:
            return False

        # Maybe the command we ask for are not for us, but for another notification ways
        # on the same contact. If so, bail out
        if cmd and cmd not in self.host_notification_commands:
            return False

        valid = self.host_notification_period.is_time_valid(timestamp)
        if 'n' in self.host_notification_options:
            return False
        timestamp = {
            'DOWN': 'd',
            'UNREACHABLE': 'u',
            'RECOVERY': 'r',
            'FLAPPING': 'f',
            'DOWNTIME': 's'
        }
        if n_type == 'PROBLEM':
            if state in timestamp:
                return valid and timestamp[
                    state] in self.host_notification_options
        elif n_type == 'RECOVERY':
            if n_type in timestamp:
                return valid and timestamp[
                    n_type] in self.host_notification_options
        elif n_type == 'ACKNOWLEDGEMENT':
            return valid
        elif n_type in ('FLAPPINGSTART', 'FLAPPINGSTOP', 'FLAPPINGDISABLED'):
            return valid and 'f' in self.host_notification_options
        elif n_type in ('DOWNTIMESTART', 'DOWNTIMEEND', 'DOWNTIMECANCELLED'):
            return valid and 's' in self.host_notification_options

        return False

    def get_notification_commands(self, o_type):
        """Get notification commands for object type

        :param o_type: object type (host or service)
        :type o_type: str
        :return: command list
        :rtype: list[alignak.objects.command.Command]
        """
        # service_notification_commands for service
        notif_commands_prop = o_type + '_notification_commands'
        notif_commands = getattr(self, notif_commands_prop)
        return notif_commands

    def is_correct(self):
        """Check if this host configuration is correct ::

        * All required parameter are specified
        * Go through all configuration warnings and errors that could have been raised earlier

        :return: True if the configuration is correct, otherwise False
        :rtype: bool
        """
        state = True
        cls = self.__class__

        # Raised all previously saw errors like unknown commands or timeperiods
        if self.configuration_errors != []:
            state = False
            for err in self.configuration_errors:
                logger.error("[item::%s] %s", self.get_name(), err)

        # A null notif way is a notif way that will do nothing (service = n, hot =n)
        is_null_notifway = False
        if (hasattr(self, 'service_notification_options')
                and self.service_notification_options == ['n']):
            if (hasattr(self, 'host_notification_options')
                    and self.host_notification_options == ['n']):
                is_null_notifway = True
                return True

        for prop, entry in cls.properties.items():
            if prop not in self.special_properties:
                if not hasattr(self, prop) and entry.required:
                    logger.error("[notificationway::%s] %s property not set",
                                 self.get_name(), prop)
                    state = False  # Bad boy...

        # Ok now we manage special cases...
        # Service part
        if not hasattr(self, 'service_notification_commands'):
            logger.error(
                "[notificationway::%s] do not have any "
                "service_notification_commands defined", self.get_name())
            state = False
        else:
            for cmd in self.service_notification_commands:
                if cmd is None:
                    logger.error(
                        "[notificationway::%s] a "
                        "service_notification_command is missing",
                        self.get_name())
                    state = False
                if not cmd.is_valid():
                    logger.error(
                        "[notificationway::%s] a "
                        "service_notification_command is invalid",
                        self.get_name())
                    state = False

        if getattr(self, 'service_notification_period', None) is None:
            logger.error(
                "[notificationway::%s] the "
                "service_notification_period is invalid", self.get_name())
            state = False

        # Now host part
        if not hasattr(self, 'host_notification_commands'):
            logger.error(
                "[notificationway::%s] do not have any "
                "host_notification_commands defined", self.get_name())
            state = False
        else:
            for cmd in self.host_notification_commands:
                if cmd is None:
                    logger.error(
                        "[notificationway::%s] a "
                        "host_notification_command is missing",
                        self.get_name())
                    state = False
                if not cmd.is_valid():
                    logger.error(
                        "[notificationway::%s] a host_notification_command "
                        "is invalid (%s)", cmd.get_name(), str(cmd.__dict__))
                    state = False

        if getattr(self, 'host_notification_period', None) is None:
            logger.error(
                "[notificationway::%s] the host_notification_period "
                "is invalid", self.get_name())
            state = False

        return state
Exemplo n.º 25
0
class Acknowledge(AlignakObject):  # pylint: disable=R0903
    """
    Allows you to acknowledge the current problem for the specified service.
    By acknowledging the current problem, future notifications (for the same
    service state) are disabled.

    If the acknowledge is "sticky", the acknowledgement will remain until
    the service returns to an OK state. Otherwise the acknowledgement will automatically
    be removed when the service state changes.

    If the acknowledge is "notify", a notification will be sent out to contacts
    indicating that the current service problem has been acknowledged and when the
    acknowledge is cleared.
    """

    my_type = 'acknowledge'
    properties = {
        'sticky':
            BoolProp(default=True),
        'notify':
            BoolProp(default=False),
        'end_time':
            IntegerProp(default=0),
        'author':
            StringProp(default=u'Alignak'),
        'comment':
            StringProp(default=u''),
        'comment_id':
            StringProp(default=u'')
    }

    def __init__(self, params=None, parsing=False):
        super(Acknowledge, self).__init__(params, parsing=parsing)

        self.fill_default()

    def serialize(self):
        """This function serialize into a simple dict object.
        It is used when transferring data to other daemons over the network (http)

        Here we directly return all attributes

        :return: json representation of a Acknowledge
        :rtype: dict
        """
        return {'uuid': self.uuid, 'ref': self.ref, 'sticky': self.sticky, 'notify': self.notify,
                'end_time': self.end_time, 'author': self.author, 'comment': self.comment}

    def get_raise_brok(self, host_name, service_name=''):
        """Get a start acknowledge brok

        :param comment_type: 1 = host, 2 = service
        :param host_name:
        :param service_name:
        :return: brok with wanted data
        :rtype: alignak.brok.Brok
        """
        data = self.serialize()
        data['host'] = host_name
        if service_name != '':
            data['service'] = service_name

        return Brok({'type': 'acknowledge_raise', 'data': data})

    def get_expire_brok(self, host_name, service_name=''):
        """Get an expire acknowledge brok

        :type item: item
        :return: brok with wanted data
        :rtype: alignak.brok.Brok
        """
        data = self.serialize()
        data['host'] = host_name
        if service_name != '':
            data['service'] = service_name

        return Brok({'type': 'acknowledge_expire', 'data': data})
Exemplo n.º 26
0
class Check(Action):
    """Check class implements monitoring concepts of checks :(status, state, output)
    Check instance are used to store monitoring plugins data (exit status, output)
    and used by schedule to raise alert, reschedule check etc.

    """
    # AutoSlots create the __slots__ with properties and
    # running_properties names

    # FIXME : reenable AutoSlots if possible
    # __metaclass__ = AutoSlots

    my_type = 'check'

    properties = Action.properties.copy()
    properties.update({
        'is_a': StringProp(default='check'),
        'state': IntegerProp(default=0),
        'long_output': StringProp(default=''),
        'ref': IntegerProp(default=-1),
        'depend_on': ListProp(default=[]),
        'dep_check': ListProp(default=[]),
        'perf_data': StringProp(default=''),
        'check_type': IntegerProp(default=0),
        'poller_tag': StringProp(default='None'),
        'internal': BoolProp(default=False),
        'from_trigger': BoolProp(default=False),
    })

    def __init__(self,
                 status,
                 command,
                 ref,
                 t_to_go,
                 dep_check=None,
                 _id=None,
                 timeout=10,
                 poller_tag='None',
                 reactionner_tag='None',
                 env={},
                 module_type='fork',
                 from_trigger=False,
                 dependency_check=False):

        self.is_a = 'check'
        self.type = ''
        if _id is None:  # id != None is for copy call only
            self._id = Action._id
            Action._id += 1
        self._in_timeout = False
        self.timeout = timeout
        self.status = status
        self.exit_status = 3
        self.command = command
        self.output = ''
        self.long_output = ''
        self.ref = ref
        # self.ref_type = ref_type
        self.t_to_go = t_to_go
        self.depend_on = []
        if dep_check is None:
            self.depend_on_me = []
        else:
            self.depend_on_me = [dep_check]
        self.check_time = 0
        self.execution_time = 0.0
        self.u_time = 0.0  # user executon time
        self.s_time = 0.0  # system execution time
        self.perf_data = ''
        self.check_type = 0  # which kind of check result? 0=active 1=passive
        self.poller_tag = poller_tag
        self.reactionner_tag = reactionner_tag
        self.module_type = module_type
        self.env = env
        # we keep the reference of the poller that will take us
        self.worker = 'none'
        # If it's a business rule, manage it as a special check
        if ref and ref.got_business_rule or command.startswith('_internal'):
            self.internal = True
        else:
            self.internal = False
        self.from_trigger = from_trigger
        self.dependency_check = dependency_check

    def copy_shell(self):
        """return a copy of the check but just what is important for execution
        So we remove the ref and all

        :return: a copy of check
        :rtype: object
        """
        # We create a dummy check with nothing in it, just defaults values
        return self.copy_shell__(Check('', '', '', '', '', _id=self._id))

    def get_return_from(self, check):
        """Update check data from action (notification for instance)

        :param check: action to get data from
        :type check: alignak.action.Action
        :return: None
        """
        self.exit_status = check.exit_status
        self.output = check.output
        self.long_output = check.long_output
        self.check_time = check.check_time
        self.execution_time = check.execution_time
        self.perf_data = check.perf_data
        self.u_time = check.u_time
        self.s_time = check.s_time

    def is_launchable(self, timestamp):
        """Check if the check can be launched

        :param timestamp: time to compare with t_to_go attribute
        :type timestamp: int
        :return: True if t > self.t_to_go, False otherwise
        :rtype: bool
        """
        return timestamp > self.t_to_go

    def __str__(self):
        return "Check %d status:%s command:%s ref:%s" % \
               (self._id, self.status, self.command, self.ref)

    def get_id(self):
        """Getter for id attribute

        :return: id
        :rtype: int
        """
        return self._id

    def set_type_active(self):
        """Set check_type attribute to 0

        :return: None
        """
        self.check_type = 0

    def set_type_passive(self):
        """Set check_type attribute to 1

        :return: None
        """
        self.check_type = 1

    def is_dependent(self):
        """Getter for dependency_check attribute

        :return: True if this check was created for dependent one, False otherwise
        :rtype: bool
        """
        return self.dependency_check
Exemplo n.º 27
0
class Hostgroup(Itemgroup):
    """
    Class to manage a group of host
    A Hostgroup is used to manage a group of hosts
    """
    my_type = 'hostgroup'
    members_property = "members"
    group_members_property = "hostgroup_members"

    properties = Itemgroup.properties.copy()
    properties.update({
        'hostgroup_name':
        StringProp(fill_brok=['full_status']),
        'alias':
        StringProp(default=u'', fill_brok=['full_status']),
        'hostgroup_members':
        ListProp(default=[],
                 fill_brok=['full_status'],
                 merging='join',
                 split_on_comma=True),
        'notes':
        StringProp(default=u'', fill_brok=['full_status']),
        'notes_url':
        StringProp(default=u'', fill_brok=['full_status']),
        'action_url':
        StringProp(default=u'', fill_brok=['full_status']),

        # Realm stuff
        'realm':
        StringProp(default=u'', fill_brok=['full_status']),
    })

    # properties set only for running purpose
    running_properties = Itemgroup.running_properties.copy()
    running_properties.update({
        # Realm stuff
        'realm_name': StringProp(default=u''),
        'got_default_realm': BoolProp(default=False),
    })

    macros = {
        'HOSTGROUPNAME': 'hostgroup_name',
        'HOSTGROUPALIAS': 'alias',
        'HOSTGROUPMEMBERS': 'members',
        'HOSTGROUPGROUPMEMBERS': 'hostgroup_members',
        'HOSTGROUPNOTES': 'notes',
        'HOSTGROUPNOTESURL': 'notes_url',
        'HOSTGROUPACTIONURL': 'action_url',
        'HOSTGROUPREALM': 'realm_name'
    }

    def get_name(self):
        """Get the group name"""
        return getattr(self, 'hostgroup_name', 'Unnamed')

    def get_hosts(self):
        """Get the hosts of the group

        :return: list of hosts
        :rtype: list
        """
        return super(Hostgroup, self).get_members()

    def get_hostgroup_members(self):
        """Get the groups members of the group

        :return: list of hosts
        :rtype: list
        """
        return getattr(self, 'hostgroup_members', [])

    def get_hosts_by_explosion(self, hostgroups):
        # pylint: disable=access-member-before-definition
        """
        Get hosts of this group

        :param hostgroups: Hostgroup object
        :type hostgroups: alignak.objects.hostgroup.Hostgroups
        :return: list of hosts of this group
        :rtype: list
        """
        # First we tag the hg so it will not be explode
        # if a son of it already call it
        self.already_exploded = True

        # Now the recursive part
        # rec_tag is set to False every HG we explode
        # so if True here, it must be a loop in HG
        # calls... not GOOD!
        if self.rec_tag:
            logger.error("[hostgroup::%s] got a loop in hostgroup definition",
                         self.get_name())
            return self.get_hosts()

        # Ok, not a loop, we tag it and continue
        self.rec_tag = True

        hg_mbrs = self.get_hostgroup_members()
        for hg_mbr in hg_mbrs:
            hostgroup = hostgroups.find_by_name(hg_mbr.strip())
            if hostgroup is not None:
                value = hostgroup.get_hosts_by_explosion(hostgroups)
                if value is not None:
                    self.add_members(value)

        return self.get_hosts()
Exemplo n.º 28
0
class SatelliteLink(Item):
    # pylint: disable=too-many-instance-attributes
    """SatelliteLink is a common Class for links between
    Arbiter and other satellites. Used by the Dispatcher object.

    """
    # Next value used for auto generated instance_id
    _next_id = 1

    # All the class properties that are 'to_send' are stored in the 'global'
    # configuration to be pushed to the satellite when the configuration is dispatched
    properties = Item.properties.copy()
    properties.update({
        'instance_id':
            StringProp(to_send=True),

        # When this property is set, the Arbiter will launch the corresponding daemon
        'alignak_launched':
            BoolProp(default=False, fill_brok=['full_status'], to_send=True),
        # This property is set by the Arbiter when it detects that this daemon
        # is needed but not declared in the configuration
        'missing_daemon':
            BoolProp(default=False, fill_brok=['full_status']),

        # Sent to the satellites and used to check the managed configuration
        # Those are not to_send=True because they are updated by the configuration Dispatcher
        # and set when the daemon receives its configuration
        'managed_conf_id':
            StringProp(default=u''),
        'push_flavor':
            StringProp(default=u''),
        'hash':
            StringProp(default=u''),

        # A satellite link has the type/name of the daemon it is related to
        'type':
            StringProp(default=u'', fill_brok=['full_status'], to_send=True),
        'name':
            StringProp(default=u'', fill_brok=['full_status'], to_send=True),

        # Listening interface and address used by the other daemons
        'host':
            StringProp(default=u'0.0.0.0', to_send=True),
        'address':
            StringProp(default=u'127.0.0.1', fill_brok=['full_status'], to_send=True),
        'active':
            BoolProp(default=True, fill_brok=['full_status'], to_send=True),
        'short_timeout':
            IntegerProp(default=3, fill_brok=['full_status'], to_send=True),
        'long_timeout':
            IntegerProp(default=120, fill_brok=['full_status'], to_send=True),

        # the delay (seconds) between two ping retries
        'ping_period':
            IntegerProp(default=5),

        # The maximum number of retries before setting the daemon as dead
        'max_check_attempts':
            IntegerProp(default=3, fill_brok=['full_status']),

        # For a spare daemon link
        'spare':
            BoolProp(default=False, fill_brok=['full_status'], to_send=True),
        'spare_check_interval':
            IntegerProp(default=5, fill_brok=['full_status']),
        'spare_max_check_attempts':
            IntegerProp(default=3, fill_brok=['full_status']),

        'manage_sub_realms':
            BoolProp(default=True, fill_brok=['full_status'], to_send=True),
        'manage_arbiters':
            BoolProp(default=False, fill_brok=['full_status'], to_send=True),
        'modules':
            ListProp(default=[''], split_on_comma=True),
        'polling_interval':
            IntegerProp(default=5, fill_brok=['full_status'], to_send=True),
        'use_timezone':
            StringProp(default=u'NOTSET', to_send=True),
        'realm':
            StringProp(default=u'', fill_brok=['full_status'],
                       brok_transformation=get_obj_name_two_args_and_void),
        'realm_name':
            StringProp(default=u''),
        'satellite_map':
            DictProp(default={}, elts_prop=AddrProp, to_send=True, override=True),
        'use_ssl':
            BoolProp(default=False, fill_brok=['full_status'], to_send=True),
        'hard_ssl_name_check':
            BoolProp(default=True, fill_brok=['full_status'], to_send=True),
        'passive':
            BoolProp(default=False, fill_brok=['full_status'], to_send=True),
    })

    running_properties = Item.running_properties.copy()
    running_properties.update({
        'con':
            StringProp(default=None),
        'uri':
            StringProp(default=None),

        'reachable':    # Can be reached - assumed True as default ;)
            BoolProp(default=False, fill_brok=['full_status']),
        'alive':        # Is alive (attached process s launched...)
            BoolProp(default=False, fill_brok=['full_status']),
        'valid':        # Is valid (the daemon is the expected one)
            BoolProp(default=False, fill_brok=['full_status']),
        'need_conf':    # The daemon needs to receive a configuration
            BoolProp(default=True, fill_brok=['full_status']),
        'have_conf':    # The daemon has received a configuration
            BoolProp(default=False, fill_brok=['full_status']),

        'stopping':     # The daemon is requested to stop
            BoolProp(default=False, fill_brok=['full_status']),

        'running_id':   # The running identifier of my related daemon
            FloatProp(default=0, fill_brok=['full_status']),

        # the number of poll attempt from the arbiter dispatcher
        'attempt':
            IntegerProp(default=0, fill_brok=['full_status']),

        # the last connection attempt timestamp
        'last_connection':
            IntegerProp(default=0, fill_brok=['full_status']),
        # the number of failed attempt for the connection
        'connection_attempt':
            IntegerProp(default=0, fill_brok=['full_status']),

        'last_check':
            IntegerProp(default=0, fill_brok=['full_status']),
        'cfg_managed':
            DictProp(default=None),
        'cfg_to_manage':
            DictProp(default={}),
        'configuration_sent':
            BoolProp(default=False),
        'statistics':
            DictProp(default={}),
    })

    def __init__(self, params=None, parsing=True):
        """Initialize a SatelliteLink

        If parsing is True, we are initializing from a configuration, else we are initializing
        from a copy of another satellite link data. This is used when the daemons receive their
        configuration from the arbiter.

        When initializing from an arbiter configuration, an instance_id property must exist else
        a LinkError exception is raised!

        If a satellite_map property exists in the provided parameters, it will update
        the default existing one
        """
        super(SatelliteLink, self).__init__(params, parsing)

        logger.debug("Initialize a %s, params: %s", self.__class__.__name__, params)

        # My interface context
        self.broks = []
        self.actions = {}
        self.wait_homerun = {}
        self.pushed_commands = []

        self.init_running_properties()

        if parsing:
            # Create a new satellite link identifier
            self.instance_id = u'%s_%d' % (self.__class__.__name__, self.__class__._next_id)
            self.__class__._next_id += 1
        elif 'instance_id' not in params:
            raise LinkError("When not parsing a configuration, "
                            "an instance_id must exist in the provided parameters")

        self.fill_default()

        # Hack for ascending compatibility with Shinken configuration
        try:
            # We received a configuration with a 'name' property...
            if self.name:
                setattr(self, "%s_name" % self.type, self.name)
            else:
                # We received a configuration without a 'name' property... old form!
                if getattr(self, "%s_name" % self.type, None):
                    setattr(self, 'name', getattr(self, "%s_name" % self.type))
                else:
                    self.name = "Unnamed %s" % self.type
                    setattr(self, "%s_name" % self.type, self.name)
        except KeyError:
            setattr(self, 'name', getattr(self, "%s_name" % self.type))

        # Initialize our satellite map, and update if required
        self.set_arbiter_satellite_map(params.get('satellite_map', {}))

        self.cfg = {
            'self_conf': {},
            'schedulers': {},
            'arbiters': {}
        }

        # Create the daemon connection
        self.create_connection()

    def __repr__(self):  # pragma: no cover
        return '<%s - %s/%s, %s//%s:%s, rid: %s, spare: %s, realm: %s, sub-realms: %s, ' \
               'managing: %s (%s) />' \
               % (self.instance_id, self.type, self.name,
                  self.scheme, self.address, self.port, self.running_id,
                  self.spare, self.realm, self.manage_sub_realms,
                  self.managed_conf_id, self.push_flavor)
    __str__ = __repr__

    @property
    def scheme(self):
        """Daemon interface scheme

        :return: http or https if the daemon uses SSL
        :rtype: str
        """
        _scheme = 'http'
        if self.use_ssl:
            _scheme = 'https'
        return _scheme

    @staticmethod
    def get_a_satellite_link(sat_type, sat_dict):
        """Get a SatelliteLink object for a given satellite type and a dictionary

        :param sat_type: type of satellite
        :param sat_dict: satellite configuration data
        :return:
        """
        cls = get_alignak_class('alignak.objects.%slink.%sLink' % (sat_type, sat_type.capitalize()))
        return cls(params=sat_dict, parsing=False)

    def get_livestate(self):
        """Get the SatelliteLink live state.

        The live state is a tuple information containing a state identifier and a message, where:
            state is:
            - 0 for an up and running satellite
            - 1 if the satellite is not reachale
            - 2 if the satellite is dead
            - 3 else (not active)

        :return: tuple
        """
        livestate = 0
        if self.active:
            if not self.reachable:
                livestate = 1
            elif not self.alive:
                livestate = 2
        else:
            livestate = 3

        livestate_output = "%s/%s is %s" % (self.type, self.name, [
            "up and running.",
            "warning because not reachable.",
            "critical because not responding.",
            "not active by configuration."
        ][livestate])

        return (livestate, livestate_output)

    def set_arbiter_satellite_map(self, satellite_map=None):
        """
            satellite_map is the satellites map in current context:
                - A SatelliteLink is owned by an Arbiter
                - satellite_map attribute of a SatelliteLink is the map defined
                IN THE satellite configuration but for creating connections,
                we need to have the satellites map from the Arbiter point of view

        :return: None
        """
        self.satellite_map = {
            'address': self.address, 'port': self.port,
            'use_ssl': self.use_ssl, 'hard_ssl_name_check': self.hard_ssl_name_check
        }
        if satellite_map:
            self.satellite_map.update(satellite_map)

    def get_and_clear_context(self):
        """Get and clean all of our broks, actions, external commands and homerun

        :return: list of all broks of the satellite link
        :rtype: list
        """
        res = (self.broks, self.actions, self.wait_homerun, self.pushed_commands)
        self.broks = []
        self.actions = {}
        self.wait_homerun = {}
        self.pushed_commands = []
        return res

    def get_and_clear_broks(self):
        """Get and clean all of our broks

        :return: list of all broks of the satellite link
        :rtype: list
        """
        res = self.broks
        self.broks = []
        return res

    def prepare_for_conf(self):
        """Initialize the pushed configuration dictionary
        with the inner properties that are to be propagated to the satellite link.

        :return: None
        """
        logger.debug("- preparing: %s", self)
        self.cfg = {
            'self_conf': self.give_satellite_cfg(),
            'schedulers': {},
            'arbiters': {}
        }
        logger.debug("- prepared: %s", self.cfg)

    def give_satellite_cfg(self):
        """Get the default information for a satellite.

        Overridden by the specific satellites links

        :return: dictionary of information common to all the links
        :rtype: dict
        """
        # All the satellite link class properties that are 'to_send' are stored in a
        # dictionary to be pushed to the satellite when the configuration is dispatched
        res = {}
        properties = self.__class__.properties
        for prop, entry in list(properties.items()):
            if hasattr(self, prop) and entry.to_send:
                res[prop] = getattr(self, prop)
        return res

    def give_satellite_json(self):
        """Get the json information for a satellite.

        This to provide information that will be exposed by a daemon on its HTTP interface.

        :return: dictionary of information common to all the links
        :rtype: dict
        """
        daemon_properties = ['type', 'name', 'uri', 'spare', 'configuration_sent',
                             'realm_name', 'manage_sub_realms',
                             'active', 'reachable', 'alive', 'passive',
                             'last_check', 'polling_interval', 'max_check_attempts']

        (livestate, livestate_output) = self.get_livestate()
        res = {
            "livestate": livestate,
            "livestate_output": livestate_output
        }
        for sat_prop in daemon_properties:
            res[sat_prop] = getattr(self, sat_prop, 'not_yet_defined')
        return res

    def manages(self, cfg_part):
        """Tell if the satellite is managing this configuration part

        The managed configuration is formed as a dictionary indexed on the link instance_id:
         {
            u'SchedulerLink_1': {
                u'hash': u'4d08630a3483e1eac7898e7a721bd5d7768c8320',
                u'push_flavor': u'4d08630a3483e1eac7898e7a721bd5d7768c8320',
                u'managed_conf_id': [u'Config_1']
            }
        }

        Note that the managed configuration is a string array rather than a simple string...
        no special for this reason, probably due to the serialization when the configuration is
        pushed :/

        :param cfg_part: configuration part as prepare by the Dispatcher
        :type cfg_part: Conf
        :return: True if the satellite manages this configuration
        :rtype: bool
        """
        logger.debug("Do I (%s/%s) manage: %s, my managed configuration(s): %s",
                     self.type, self.name, cfg_part, self.cfg_managed)

        # If we do not yet manage a configuration
        if not self.cfg_managed:
            logger.info("I (%s/%s) do not manage (yet) any configuration!", self.type, self.name)
            return False

        # Check in the schedulers list configurations
        for managed_cfg in list(self.cfg_managed.values()):
            # If not even the cfg_id in the managed_conf, bail out
            if managed_cfg['managed_conf_id'] == cfg_part.instance_id \
                    and managed_cfg['push_flavor'] == cfg_part.push_flavor:
                logger.debug("I do manage this configuration: %s", cfg_part)
                break
        else:
            logger.warning("I (%s/%s) do not manage this configuration: %s",
                           self.type, self.name, cfg_part)
            return False

        return True

    def create_connection(self):
        """Initialize HTTP connection with a satellite (con attribute) and
        set its uri attribute

        This is called on the satellite link initialization

        :return: None
        """
        # Create the HTTP client for the connection
        try:
            self.con = HTTPClient(address=self.satellite_map['address'],
                                  port=self.satellite_map['port'],
                                  short_timeout=self.short_timeout, long_timeout=self.long_timeout,
                                  use_ssl=self.satellite_map['use_ssl'],
                                  strong_ssl=self.satellite_map['hard_ssl_name_check'])
            self.uri = self.con.uri
        except HTTPClientException as exp:
            # logger.error("Error with '%s' when creating client: %s", self.name, str(exp))
            # Set the satellite as dead
            self.set_dead()
            raise LinkError("Error with '%s' when creating client: %s" % (self.name, str(exp)))

    def set_alive(self):
        """Set alive, reachable, and reset attempts.
        If we change state, raise a status brok update

        alive, means the daemon is prenset in the system
        reachable, means that the HTTP connection is valid

        With this function we confirm that the daemon is reachable and, thus, we assume it is alive!

        :return: None
        """
        was_alive = self.alive
        self.alive = True
        self.reachable = True
        self.attempt = 0

        # We came from dead to alive! We must propagate the good news
        if not was_alive:
            logger.info("Setting %s satellite as alive :)", self.name)
            self.broks.append(self.get_update_status_brok())

    def set_dead(self):
        """Set the satellite into dead state:
        If we change state, raise a status brok update

        :return:None
        """
        was_alive = self.alive
        self.alive = False
        self.reachable = False
        self.attempt = 0
        # We will have to create a new connection...
        self.con = None

        # We are dead now! We must propagate the sad news...
        if was_alive and not self.stopping:
            logger.warning("Setting the satellite %s as dead :(", self.name)
            self.broks.append(self.get_update_status_brok())

    def add_failed_check_attempt(self, reason=''):
        """Set the daemon as unreachable and add a failed attempt
        if we reach the maximum attempts, set the daemon as dead

        :param reason: the reason of adding an attempts (stack trace sometimes)
        :type reason: str
        :return: None
        """
        self.reachable = False
        self.attempt = self.attempt + 1

        logger.debug("Failed attempt for %s (%d/%d), reason: %s",
                     self.name, self.attempt, self.max_check_attempts, reason)
        # Don't need to warn again and again if the satellite is already dead
        # Only warn when it is alive
        if self.alive:
            if not self.stopping:
                logger.warning("Add failed attempt for %s (%d/%d) - %s",
                               self.name, self.attempt, self.max_check_attempts, reason)
            else:
                logger.info("Stopping... failed attempt for %s (%d/%d) - also probably stopping",
                            self.name, self.attempt, self.max_check_attempts)

        # If we reached the maximum attempts, set the daemon as dead
        if self.attempt >= self.max_check_attempts:
            if not self.stopping:
                logger.warning("Set %s as dead, too much failed attempts (%d), last problem is: %s",
                               self.name, self.max_check_attempts, reason)
            else:
                logger.info("Stopping... set %s as dead, too much failed attempts (%d)",
                            self.name, self.max_check_attempts)

            self.set_dead()

    def valid_connection(*outer_args, **outer_kwargs):
        # pylint: disable=unused-argument, no-method-argument
        """Check if the daemon connection is established and valid"""
        def decorator(func):  # pylint: disable=missing-docstring
            def decorated(*args, **kwargs):  # pylint: disable=missing-docstring
                # outer_args and outer_kwargs are the decorator arguments
                # args and kwargs are the decorated function arguments
                link = args[0]
                if not link.con:
                    raise LinkError("The connection is not created for %s" % link.name)
                if not link.running_id:
                    raise LinkError("The connection is not initialized for %s" % link.name)

                return func(*args, **kwargs)
            return decorated
        return decorator

    def communicate(*outer_args, **outer_kwargs):
        # pylint: disable=unused-argument, no-method-argument
        """Check if the daemon connection is authorized and valid"""
        def decorator(func):  # pylint: disable=missing-docstring
            def decorated(*args, **kwargs):  # pylint: disable=missing-docstring
                # outer_args and outer_kwargs are the decorator arguments
                # args and kwargs are the decorated function arguments
                fn_name = func.__name__
                link = args[0]
                if not link.alive:
                    logger.warning("%s is not alive for %s", link.name, fn_name)
                    return None

                try:
                    if not link.reachable:
                        raise LinkError("The %s %s is not reachable" % (link.type, link.name))

                    logger.debug("[%s] Calling: %s, %s, %s", link.name, fn_name, args, kwargs)
                    return func(*args, **kwargs)
                except HTTPClientConnectionException as exp:
                    # A Connection error is raised when the daemon connection cannot be established
                    # No way with the configuration parameters!
                    if not link.stopping:
                        logger.warning("A daemon (%s/%s) that we must be related with "
                                       "cannot be connected: %s", link.type, link.name, exp)
                    else:
                        logger.info("Stopping... daemon (%s/%s) cannot be connected. "
                                    "It is also probably stopping or yet stopped.",
                                    link.type, link.name)
                    link.set_dead()
                except (LinkError, HTTPClientTimeoutException) as exp:
                    link.add_failed_check_attempt("Connection timeout "
                                                  "with '%s': %s" % (fn_name, str(exp)))
                    return False
                except HTTPClientDataException as exp:
                    # A Data error is raised when the daemon HTTP reponse is not 200!
                    # No way with the communication if some problems exist in the daemon interface!
                    # Abort all
                    err = "Some daemons that we must be related with " \
                          "have some interface problems. Sorry, I bail out"
                    logger.error(err)
                    os.sys.exit(err)
                except HTTPClientException as exp:
                    link.add_failed_check_attempt("Error with '%s': %s" % (fn_name, str(exp)))

                return None

            return decorated
        return decorator

    @communicate()
    def get_running_id(self):
        """Send a HTTP request to the satellite (GET /identity)
        Used to get the daemon running identifier that allows to know if the daemon got restarted

        This is called on connection initialization or re-connection

        If the daemon is notreachable, this function will raise an exception and the caller
        will receive a False as return

        :return: Boolean indicating if the running id was received
        :type: bool
        """
        former_running_id = self.running_id

        logger.info("  get the running identifier for %s %s.", self.type, self.name)
        # An exception is raised in this function if the daemon is not reachable
        self.running_id = self.con.get('identity')
        if isinstance(self.running_id, dict):
            self.running_id = self.running_id['running_id']

        if former_running_id == 0:
            if self.running_id:
                logger.info("  -> got: %s.", self.running_id)
                former_running_id = self.running_id

        # If the daemon has just started or has been restarted: it has a new running_id.
        if former_running_id != self.running_id:
            if former_running_id:
                logger.info("  -> The %s %s running identifier changed: %s. "
                            "The daemon was certainly restarted!",
                            self.type, self.name, self.running_id)
            # So we clear all verifications, they are obsolete now.
            logger.info("The running id of the %s %s changed (%s), "
                        "we must clear its context.",
                        self.type, self.name, self.running_id)
            (_, _, _, _) = self.get_and_clear_context()

        # Set the daemon as alive
        self.set_alive()

        return True

    @valid_connection()
    @communicate()
    def stop_request(self, stop_now=False):
        """Send a stop request to the daemon

        :param stop_now: stop now or go to stop wait mode
        :type stop_now: bool
        :return: the daemon response (True)
        """
        logger.debug("Sending stop request to %s, stop now: %s", self.name, stop_now)

        res = self.con.get('stop_request', {'stop_now': '1' if stop_now else '0'})
        return res

    @valid_connection()
    @communicate()
    def update_infos(self, forced=False, test=False):
        """Update satellite info each self.polling_interval seconds
        so we smooth arbiter actions for just useful actions.

        Raise a satellite update status Brok

        If forced is True, then ignore the ping period. This is used when the configuration
        has not yet been dispatched to the Arbiter satellites.

        If test is True, do not really ping the daemon (useful for the unit tests only)

        :param forced: ignore the ping smoothing
        :type forced: bool
        :param test:
        :type test: bool
        :return:
        None if the last request is too recent,
        False if a timeout was raised during the request,
        else the managed configurations dictionary
        """
        logger.debug("Update informations, forced: %s", forced)

        # First look if it's not too early to ping
        now = time.time()
        if not forced and self.last_check and self.last_check + self.polling_interval > now:
            logger.debug("Too early to ping %s, ping period is %ds!, last check: %d, now: %d",
                         self.name, self.polling_interval, self.last_check, now)
            return None

        self.get_conf(test=test)

        # Update the daemon last check timestamp
        self.last_check = time.time()

        # Update the state of this element
        self.broks.append(self.get_update_status_brok())

        return self.cfg_managed

    @valid_connection()
    @communicate()
    def get_daemon_stats(self, details=False):
        """Send a HTTP request to the satellite (GET /get_daemon_stats)

        :return: Daemon statistics
        :rtype: dict
        """
        logger.debug("Get daemon statistics for %s, %s %s", self.name, self.alive, self.reachable)
        return self.con.get('stats%s' % ('?details=1' if details else ''))

    @valid_connection()
    @communicate()
    def get_initial_broks(self, broker_name):
        """Send a HTTP request to the satellite (GET /_initial_broks)

        Used to build the initial broks for a broker connecting to a scheduler

        :param broker_name: the concerned broker name
        :type broker_name: str
        :return: Boolean indicating if the running id changed
        :type: bool
        """
        logger.debug("Getting initial broks for %s, %s %s", self.name, self.alive, self.reachable)
        return self.con.get('_initial_broks', {'broker_name': broker_name}, wait=True)

    @valid_connection()
    @communicate()
    def wait_new_conf(self):
        """Send a HTTP request to the satellite (GET /wait_new_conf)

        :return: True if wait new conf, otherwise False
        :rtype: bool
        """
        logger.debug("Wait new configuration for %s, %s %s", self.name, self.alive, self.reachable)
        return self.con.get('_wait_new_conf')

    @valid_connection()
    @communicate()
    def put_conf(self, configuration, test=False):
        """Send the configuration to the satellite
        HTTP request to the satellite (POST /push_configuration)

        If test is True, store the configuration internally

        :param configuration: The conf to send (data depend on the satellite)
        :type configuration:
        :return: None
        """
        logger.debug("Sending configuration to %s, %s %s", self.name, self.alive, self.reachable)
        # ----------
        if test:
            setattr(self, 'unit_test_pushed_configuration', configuration)
            # print("*** unit tests - sent configuration %s: %s" % (self.name, configuration))
            return True
        # ----------

        return self.con.post('_push_configuration', {'conf': configuration}, wait=True)

    @valid_connection()
    @communicate()
    def has_a_conf(self, magic_hash=None):  # pragma: no cover
        """Send a HTTP request to the satellite (GET /have_conf)
        Used to know if the satellite has a conf

        :param magic_hash: Config hash. Only used for HA arbiter communication
        :type magic_hash: int
        :return: Boolean indicating if the satellite has a (specific) configuration
        :type: bool
        """
        logger.debug("Have a configuration for %s, %s %s", self.name, self.alive, self.reachable)
        self.have_conf = self.con.get('_have_conf', {'magic_hash': magic_hash})
        return self.have_conf

    @valid_connection()
    @communicate()
    def get_conf(self, test=False):
        """Send a HTTP request to the satellite (GET /managed_configurations)
        and update the cfg_managed attribute with the new information
        Set to {} on failure

        the managed configurations are a dictionary which keys are the scheduler link instance id
        and the values are the push_flavor

        If test is True, returns the unit test internally stored configuration

        Returns False if a timeout is raised

        :return: see @communicate, or the managed configuration
        """
        logger.debug("Get managed configuration for %s, %s %s",
                     self.name, self.alive, self.reachable)
        # ----------
        if test:
            self.cfg_managed = {}
            self.have_conf = True
            logger.debug("Get managed configuration test ...")
            if getattr(self, 'unit_test_pushed_configuration', None) is not None:
                # Note this is a dict not a SatelliteLink object !
                for scheduler_link in self.unit_test_pushed_configuration['schedulers'].values():
                    self.cfg_managed[scheduler_link['instance_id']] = {
                        'hash': scheduler_link['hash'],
                        'push_flavor': scheduler_link['push_flavor'],
                        'managed_conf_id': scheduler_link['managed_conf_id']
                    }
            # print("*** unit tests - get managed configuration %s: %s"
            #       % (self.name, self.cfg_managed))
        # ----------
        else:
            self.cfg_managed = self.con.get('managed_configurations')
            logger.debug("My (%s) fresh managed configuration: %s", self.name, self.cfg_managed)

        self.have_conf = (self.cfg_managed != {})

        return self.cfg_managed

    @valid_connection()
    @communicate()
    def push_broks(self, broks):
        """Send a HTTP request to the satellite (POST /push_broks)
        Send broks to the satellite

        :param broks: Brok list to send
        :type broks: list
        :return: True on success, False on failure
        :rtype: bool
        """
        logger.debug("[%s] Pushing %d broks", self.name, len(broks))
        return self.con.post('_push_broks', {'broks': broks}, wait=True)

    @valid_connection()
    @communicate()
    def push_actions(self, actions, scheduler_instance_id):
        """Post the actions to execute to the satellite.
        Indeed, a scheduler post its checks to a poller and its actions to a reactionner.

        :param actions: Action list to send
        :type actions: list
        :param scheduler_instance_id: Scheduler instance identifier
        :type scheduler_instance_id: uuid
        :return: True on success, False on failure
        :rtype: bool
        """
        logger.debug("Pushing %d actions from %s", len(actions), scheduler_instance_id)
        return self.con.post('_push_actions', {'actions': actions,
                                               'scheduler_instance_id': scheduler_instance_id},
                             wait=True)

    @valid_connection()
    @communicate()
    def push_results(self, results, scheduler_name):
        """Send a HTTP request to the satellite (POST /put_results)
        Send actions results to the satellite

        :param results: Results list to send
        :type results: list
        :param scheduler_name: Scheduler name
        :type scheduler_name: uuid
        :return: True on success, False on failure
        :rtype: bool
        """
        logger.debug("Pushing %d results", len(results))
        result = self.con.post('put_results', {'results': results, 'from': scheduler_name},
                               wait=True)
        return result

    @valid_connection()
    @communicate()
    def push_external_commands(self, commands):
        """Send a HTTP request to the satellite (POST /r_un_external_commands)
        to send the external commands to the satellite

        :param results: Results list to send
        :type results: list
        :return: True on success, False on failure
        :rtype: bool
        """
        logger.debug("Pushing %d external commands", len(commands))
        return self.con.post('_run_external_commands', {'cmds': commands}, wait=True)

    @valid_connection()
    @communicate()
    def get_external_commands(self):
        """Send a HTTP request to the satellite (GET /_external_commands) to
        get the external commands from the satellite.

        :return: External Command list on success, [] on failure
        :rtype: list
        """
        res = self.con.get('_external_commands', wait=False)
        logger.debug("Got %d external commands from %s: %s", len(res), self.name, res)
        return unserialize(res, True)

    @valid_connection()
    @communicate()
    def get_broks(self, broker_name):
        """Send a HTTP request to the satellite (GET /_broks)
        Get broks from the satellite.
        Un-serialize data received.

        :param broker_name: the concerned broker link
        :type broker_name: BrokerLink
        :return: Broks list on success, [] on failure
        :rtype: list
        """
        res = self.con.get('_broks', {'broker_name': broker_name}, wait=False)
        logger.debug("Got broks from %s: %s", self.name, res)
        return unserialize(res, True)

    @valid_connection()
    @communicate()
    def get_events(self):
        """Send a HTTP request to the satellite (GET /_events)
        Get monitoring events from the satellite.

        :return: Broks list on success, [] on failure
        :rtype: list
        """
        res = self.con.get('_events', wait=False)
        logger.debug("Got events from %s: %s", self.name, res)
        return unserialize(res, True)

    @valid_connection()
    def get_results(self, scheduler_instance_id):
        """Send a HTTP request to the satellite (GET /_results)
        Get actions results from satellite (only passive satellites expose this method.

        :param scheduler_instance_id: scheduler instance identifier
        :type scheduler_instance_id: str
        :return: Results list on success, [] on failure
        :rtype: list
        """
        res = self.con.get('_results', {'scheduler_instance_id': scheduler_instance_id}, wait=True)
        logger.debug("Got %d results from %s: %s", len(res), self.name, res)
        return res

    @valid_connection()
    def get_actions(self, params):
        """Send a HTTP request to the satellite (GET /_checks)
        Get actions from the scheduler.
        Un-serialize data received.

        :param params: the request parameters
        :type params: str
        :return: Actions list on success, [] on failure
        :rtype: list
        """
        res = self.con.get('_checks', params, wait=True)
        logger.debug("Got checks to execute from %s: %s", self.name, res)
        return unserialize(res, True)
Exemplo n.º 29
0
class ActionBase(AlignakObject):
    """
    This abstract class is used just for having a common id for both
    actions and checks.
    """
    process = None

    properties = {
        'is_a': StringProp(default=''),
        'type': StringProp(default=''),
        'creation_time': FloatProp(default=0.0),
        '_in_timeout': BoolProp(default=False),
        'status': StringProp(default='scheduled'),
        'exit_status': IntegerProp(default=3),
        'output': StringProp(default='', fill_brok=['full_status']),
        't_to_go': FloatProp(default=0.0),
        'check_time': IntegerProp(default=0),
        'execution_time': FloatProp(default=0.0),
        'u_time': FloatProp(default=0.0),
        's_time': FloatProp(default=0.0),
        'reactionner_tag': StringProp(default='None'),
        'env': DictProp(default={}),
        'module_type': StringProp(default='fork', fill_brok=['full_status']),
        'worker_id': StringProp(default='none'),
        'command': StringProp(),
        'timeout': IntegerProp(default=10),
        'ref': StringProp(default=''),
    }

    def __init__(self, params=None, parsing=True):
        super(ActionBase, self).__init__(params, parsing=parsing)

        # Set a creation time only if not provided
        if not params or 'creation_time' not in params:
            self.creation_time = time.time()
        # Set actions log only if not provided
        if not params or 'log_actions' not in params:
            self.log_actions = 'TEST_LOG_ACTIONS' in os.environ

        # Fill default parameters
        self.fill_default()

    def set_type_active(self):
        """Dummy function, only useful for checks"""
        pass

    def set_type_passive(self):
        """Dummy function, only useful for checks"""
        pass

    def get_local_environnement(self):
        """
        Mix the environment and the environment variables into a new local
        environment dictionary

        Note: We cannot just update the global os.environ because this
        would effect all other checks.

        :return: local environment variables
        :rtype: dict
        """
        # Do not use copy.copy() here, as the resulting copy still
        # changes the real environment (it is still a os._Environment
        # instance).
        local_env = os.environ.copy()
        for local_var in self.env:
            local_env[local_var] = self.env[local_var].encode('utf8')
        return local_env

    def execute(self):
        """Start this action command. The command will be executed in a
        subprocess.

        :return: None or str 'toomanyopenfiles'
        :rtype: None | str
        """
        self.status = 'launched'
        self.check_time = time.time()
        self.wait_time = 0.0001
        self.last_poll = self.check_time
        # Get a local env variables with our additional values
        self.local_env = self.get_local_environnement()

        # Initialize stdout and stderr. we will read them in small parts
        # if the fcntl is available
        self.stdoutdata = ''
        self.stderrdata = ''

        logger.debug("Launch command: '%s', ref: %s", self.command, self.ref)
        if self.log_actions:
            if os.environ['TEST_LOG_ACTIONS'] == 'WARNING':
                logger.warning("Launch command: '%s'", self.command)
            else:
                logger.info("Launch command: '%s'", self.command)

        return self.execute__()  # OS specific part

    def get_outputs(self, out, max_plugins_output_length):
        """Get outputs from single output (split perfdata etc).
        Edit output, perf_data and long_output attributes.

        :param out: output data of a check
        :type out: str
        :param max_plugins_output_length: max plugin data length
        :type max_plugins_output_length: int
        :return: None
        """
        # Squeeze all output after max_plugins_output_length
        out = out[:max_plugins_output_length]
        # manage escaped pipes
        out = out.replace(r'\|', '___PROTECT_PIPE___')
        # Then cuts by lines
        elts = out.split('\n')
        # For perf data
        elts_line1 = elts[0].split('|')

        # First line before | is output, strip it
        self.output = elts_line1[0].strip().replace('___PROTECT_PIPE___', '|')
        try:
            self.output = self.output.decode('utf8', 'ignore')
        except UnicodeEncodeError:
            pass

        # Init perfdata as empty
        self.perf_data = ''
        # After | it is perfdata, strip it
        if len(elts_line1) > 1:
            self.perf_data = elts_line1[1].strip().replace(
                '___PROTECT_PIPE___', '|')

        # Now manage others lines. Before the | it's long_output
        # And after it's all perf_data, \n joined
        long_output = []
        in_perfdata = False
        for line in elts[1:]:
            # if already in perfdata, direct append
            if in_perfdata:
                self.perf_data += ' ' + line.strip().replace(
                    '___PROTECT_PIPE___', '|')
            else:  # not already in perf_data, search for the | part :)
                elts = line.split('|', 1)
                # The first part will always be long_output
                long_output.append(elts[0].strip().replace(
                    '___PROTECT_PIPE___', '|'))
                if len(elts) > 1:
                    in_perfdata = True
                    self.perf_data += ' ' + elts[1].strip().replace(
                        '___PROTECT_PIPE___', '|')

        # long_output is all non output and performance data, joined with \n
        self.long_output = '\n'.join(long_output)
        # Get sure the performance data are stripped
        self.perf_data = self.perf_data.strip()

        logger.debug("Command result for '%s': %d, %s", self.command,
                     self.exit_status, self.output)
        if self.log_actions:
            if os.environ['TEST_LOG_ACTIONS'] == 'WARNING':
                logger.warning("Check result for '%s': %d, %s", self.command,
                               self.exit_status, self.output)
                if self.perf_data:
                    logger.warning("Performance data for '%s': %s",
                                   self.command, self.perf_data)
            else:
                logger.info("Check result for '%s': %d, %s", self.command,
                            self.exit_status, self.output)
                if self.perf_data:
                    logger.info("Performance data for '%s': %s", self.command,
                                self.perf_data)

    def check_finished(self, max_plugins_output_length):
        """Handle action if it is finished (get stdout, stderr, exit code...)

        :param max_plugins_output_length: max plugin data length
        :type max_plugins_output_length: int
        :return: None
        """
        # We must wait, but checks are variable in time
        # so we do not wait the same for an little check
        # than a long ping. So we do like TCP: slow start with *2
        # but do not wait more than 0.1s.
        self.last_poll = time.time()

        _, _, child_utime, child_stime, _ = os.times()

        if self.process.poll() is None:
            logger.debug("Process pid=%d is still alive", self.process.pid)
            # polling every 1/2 s ... for a timeout in seconds, this is enough
            self.wait_time = min(self.wait_time * 2, 0.5)
            now = time.time()

            # If the fcntl is available (unix) we try to read in a
            # asynchronous mode, so we won't block the PIPE at 64K buffer
            # (deadlock...)
            if fcntl:
                self.stdoutdata += no_block_read(self.process.stdout)
                self.stderrdata += no_block_read(self.process.stderr)

            if (now - self.check_time) > self.timeout:
                logger.warning("Process pid=%d spent too much time: %d s",
                               self.process.pid, now - self.check_time)
                self.kill__()
                self.status = 'timeout'
                self.execution_time = now - self.check_time
                self.exit_status = 3
                # Do not keep a pointer to the process
                # todo: ???
                del self.process
                # Get the user and system time
                _, _, n_child_utime, n_child_stime, _ = os.times()
                self.u_time = n_child_utime - child_utime
                self.s_time = n_child_stime - child_stime
                if self.log_actions:
                    if os.environ['TEST_LOG_ACTIONS'] == 'WARNING':
                        logger.warning("Action '%s' exited on timeout (%d s)",
                                       self.command, self.timeout)
                    else:
                        logger.info("Action '%s' exited on timeout (%d s)",
                                    self.command, self.timeout)
                return
            return

        logger.debug("Process pid=%d exited with %d", self.process.pid,
                     self.process.returncode)
        # Get standards outputs from the communicate function if we do
        # not have the fcntl module (Windows, and maybe some special
        # unix like AIX)
        if not fcntl:
            (self.stdoutdata, self.stderrdata) = self.process.communicate()
        else:
            # The command was too quick and finished even before we can
            # poll it first. So finish the read.
            self.stdoutdata += no_block_read(self.process.stdout)
            self.stderrdata += no_block_read(self.process.stderr)

        self.exit_status = self.process.returncode
        if self.log_actions:
            if os.environ['TEST_LOG_ACTIONS'] == 'WARNING':
                logger.warning("Action '%s' exited with return code %d",
                               self.command, self.exit_status)
            else:
                logger.info("Action '%s' exited with return code %d",
                            self.command, self.exit_status)

        # we should not keep the process now
        # todo: ???
        del self.process

        if (  # check for bad syntax in command line:
                'sh: -c: line 0: unexpected EOF while looking for matching'
                in self.stderrdata or
            ('sh: -c:' in self.stderrdata and ': Syntax' in self.stderrdata) or
                'Syntax error: Unterminated quoted string' in self.stderrdata):
            logger.warning("Return bad syntax in command line!")
            # Very, very ugly. But subprocess._handle_exitstatus does
            # not see a difference between a regular "exit 1" and a
            # bailing out shell. Strange, because strace clearly shows
            # a difference. (exit_group(1) vs. exit_group(257))
            self.stdoutdata = self.stdoutdata + self.stderrdata
            self.exit_status = 3

        if self.exit_status not in VALID_EXIT_STATUS:
            self.exit_status = 3

        if not self.stdoutdata.strip():
            self.stdoutdata = self.stderrdata

        # Now grep what we want in the output
        self.get_outputs(self.stdoutdata, max_plugins_output_length)

        # We can clean the useless properties now
        del self.stdoutdata
        del self.stderrdata

        self.status = 'done'
        self.execution_time = time.time() - self.check_time
        # Also get the system and user times
        _, _, n_child_utime, n_child_stime, _ = os.times()
        self.u_time = n_child_utime - child_utime
        self.s_time = n_child_stime - child_stime

    def copy_shell__(self, new_i):
        """Copy all attributes listed in 'only_copy_prop' from `self` to
        `new_i`.

        :param new_i: object to
        :type new_i: object
        :return: object with new properties added
        :rtype: object
        """
        for prop in ONLY_COPY_PROP:
            setattr(new_i, prop, getattr(self, prop))
        return new_i

    def got_shell_characters(self):
        """Check if the command_attribute (command line) has shell characters
        Shell characters are : '!', '$', '^', '&', '*', '(', ')', '~', '[', ']',
                               '|', '{', '}', ';', '<', '>', '?', '`'

        :return: True if one shell character is found, False otherwise
        :rtype: bool
        """
        for character in SHELLCHARS:
            if character in self.command:
                return True
        return False

    def execute__(self, force_shell=False):
        """Execute action in a subprocess

        :return: None
        """
        pass

    def kill__(self):
        """Kill the action and close fds
        :return: None
        """
        pass
Exemplo n.º 30
0
class Timeperiod(Item):
    """
    Class to manage a timeperiod
    A timeperiod is defined with range time (hours) of week to do action
    and add day exceptions (like non working days)
    """
    _id = 1
    my_type = 'timeperiod'

    properties = Item.properties.copy()
    properties.update({
        'timeperiod_name':
        StringProp(fill_brok=['full_status']),
        'alias':
        StringProp(default='', fill_brok=['full_status']),
        'use':
        StringProp(default=None),
        'register':
        IntegerProp(default=1),

        # These are needed if a broker module calls methods on timeperiod objects
        'dateranges':
        ListProp(fill_brok=['full_status'], default=[]),
        'exclude':
        ListProp(fill_brok=['full_status'], default=[]),
        'is_active':
        BoolProp(default=False)
    })
    running_properties = Item.running_properties.copy()

    def __init__(self, params={}):
        self._id = Timeperiod._id
        Timeperiod._id += 1
        self.unresolved = []
        self.dateranges = []
        self.exclude = []

        self.invalid_entries = []
        self.cache = {}  # For tunning purpose only
        self.invalid_cache = {}  # same but for invalid search
        self.is_active = None
        self.tags = set()

        # Get standard params
        standard_params = dict([(k, v) for k, v in params.items()
                                if k in self.__class__.properties])
        # Get timeperiod params (monday, tuesday, ...)
        timeperiod_params = dict([(k, v) for k, v in params.items()
                                  if k not in self.__class__.properties])
        # Handle standard params
        super(Timeperiod, self).__init__(params=standard_params)
        # Handle timeperiod params
        for key, value in timeperiod_params.items():
            if isinstance(value, list):
                if value:
                    value = value[-1]
                else:
                    value = ''
            self.unresolved.append(key + ' ' + value)

    def get_name(self):
        """
        Get the name of the timeperiod

        :return: the timeperiod name string
        :rtype: str
        """
        return getattr(self, 'timeperiod_name', 'unknown_timeperiod')

    def get_unresolved_properties_by_inheritance(self, items):
        """
        Fill full properties with template if needed for the
        unresolved values (example: sunday ETCETC)

        :param items: The Timeperiods object.
        :type items: object
        :return: None
        """
        # Ok, I do not have prop, Maybe my templates do?
        # Same story for plus
        for i in self.templates:
            self.unresolved.extend(i.unresolved)

    def get_raw_import_values(self):
        """
        Get some properties of timeperiod (timeperiod is a bit different
        from classic item)

        :return: a dictionnary of some properties
        :rtype: dict
        """
        properties = ['timeperiod_name', 'alias', 'use', 'register']
        res = {}
        for prop in properties:
            if hasattr(self, prop):
                val = getattr(self, prop)
                print prop, ":", val
                res[prop] = val
        # Now the unresolved one. The only way to get ride of same key things is to put
        # directly the full value as the key
        for other in self.unresolved:
            res[other] = ''
        return res

    def is_time_valid(self, timestamp):
        """
        Check if a time is valid or not

        :return: time is valid or not
        :rtype: bool
        """
        if hasattr(self, 'exclude'):
            for daterange in self.exclude:
                if daterange.is_time_valid(timestamp):
                    return False
        for daterange in self.dateranges:
            if daterange.is_time_valid(timestamp):
                return True
        return False

    # will give the first time > t which is valid
    def get_min_from_t(self, timestamp):
        """
        Get the first time > timestamp which is valid

        :param timestamp: number of seconds
        :type timestamp: int
        :return: number of seconds
        :rtype: int
        TODO: not used, so delete it
        """
        mins_incl = []
        for daterange in self.dateranges:
            mins_incl.append(daterange.get_min_from_t(timestamp))
        return min(mins_incl)

    # will give the first time > t which is not valid
    def get_not_in_min_from_t(self, first):
        """

        :return: None
        TODO: not used, so delete it
        """
        pass

    def find_next_valid_time_from_cache(self, timestamp):
        """
        Get the next valid time from cache

        :param timestamp: number of seconds
        :type timestamp: int
        :return: Nothing or time in seconds
        :rtype: None or int
        """
        try:
            return self.cache[timestamp]
        except KeyError:
            return None

    def find_next_invalid_time_from_cache(self, timestamp):
        """
        Get the next invalid time from cache

        :param timestamp: number of seconds
        :type timestamp: int
        :return: Nothing or time in seconds
        :rtype: None or int
        """
        try:
            return self.invalid_cache[timestamp]
        except KeyError:
            return None

    def check_and_log_activation_change(self):
        """
        Will look for active/un-active change of timeperiod.
        In case it change, we log it like:
        [1327392000] TIMEPERIOD TRANSITION: <name>;<from>;<to>

        States of is_active:
        -1: default value when start
        0: when timeperiod end
        1: when timeperiod start

        :return: None
        """
        now = int(time.time())

        was_active = self.is_active
        self.is_active = self.is_time_valid(now)

        # If we got a change, log it!
        if self.is_active != was_active:
            _from = 0
            _to = 0
            # If it's the start, get a special value for was
            if was_active is None:
                _from = -1
            if was_active:
                _from = 1
            if self.is_active:
                _to = 1

            # Now raise the log
            naglog_result(
                'info', 'TIMEPERIOD TRANSITION: %s;%d;%d' %
                (self.get_name(), _from, _to))

    def clean_cache(self):
        """
        Clean cache with entries older than now because not used in future ;)

        :return: None
        """
        now = int(time.time())
        t_to_del = []
        for timestamp in self.cache:
            if timestamp < now:
                t_to_del.append(timestamp)
        for timestamp in t_to_del:
            del self.cache[timestamp]

        # same for the invalid cache
        t_to_del = []
        for timestamp in self.invalid_cache:
            if timestamp < now:
                t_to_del.append(timestamp)
        for timestamp in t_to_del:
            del self.invalid_cache[timestamp]

    def get_next_valid_time_from_t(self, timestamp):
        """
        Get next valid time from the cache

        :param timestamp: number of seconds
        :type timestamp: int
        :return: Nothing or time in seconds
        :rtype: None or int
        """
        timestamp = int(timestamp)
        original_t = timestamp

        # logger.debug("[%s] Check valid time for %s" %
        #  ( self.get_name(), time.asctime(time.localtime(timestamp)))

        res_from_cache = self.find_next_valid_time_from_cache(timestamp)
        if res_from_cache is not None:
            return res_from_cache

        still_loop = True

        # Loop for all minutes...
        while still_loop:
            local_min = None

            # Ok, not in cache...
            dr_mins = []
            s_dr_mins = []

            for datarange in self.dateranges:
                dr_mins.append(datarange.get_next_valid_time_from_t(timestamp))

            s_dr_mins = sorted([d for d in dr_mins if d is not None])

            for t01 in s_dr_mins:
                if not self.exclude and still_loop is True:
                    # No Exclude so we are good
                    local_min = t01
                    still_loop = False
                else:
                    for timeperiod in self.exclude:
                        if not timeperiod.is_time_valid(
                                t01) and still_loop is True:
                            # OK we found a date that is not valid in any exclude timeperiod
                            local_min = t01
                            still_loop = False

            if local_min is None:
                # print "Looking for next valid date"
                exc_mins = []
                if s_dr_mins != []:
                    for timeperiod in self.exclude:
                        exc_mins.append(
                            timeperiod.get_next_invalid_time_from_t(
                                s_dr_mins[0]))

                s_exc_mins = sorted([d for d in exc_mins if d is not None])

                if s_exc_mins != []:
                    local_min = s_exc_mins[0]

            if local_min is None:
                still_loop = False
            else:
                timestamp = local_min
                # No loop more than one year
                if timestamp > original_t + 3600 * 24 * 366 + 1:
                    still_loop = False
                    local_min = None

        # Ok, we update the cache...
        self.cache[original_t] = local_min
        return local_min

    def get_next_invalid_time_from_t(self, timestamp):
        """
        Get next invalid time from the cache

        :param timestamp: number of seconds
        :type timestamp: int
        :return: Nothing or time in seconds
        :rtype: None or int
        """
        # time.asctime(time.localtime(timestamp)), timestamp
        timestamp = int(timestamp)
        original_t = timestamp
        still_loop = True

        # First try to find in cache
        res_from_cache = self.find_next_invalid_time_from_cache(timestamp)
        if res_from_cache is not None:
            return res_from_cache

        # Then look, maybe timestamp is already invalid
        if not self.is_time_valid(timestamp):
            return timestamp

        local_min = timestamp
        res = None
        # Loop for all minutes...
        while still_loop:
            # print "Invalid loop with", time.asctime(time.localtime(local_min))

            dr_mins = []
            # val_valids = []
            # val_inval = []
            # But maybe we can find a better solution with next invalid of standard dateranges
            # print self.get_name(),
            # "After valid of exclude, local_min =", time.asctime(time.localtime(local_min))
            for daterange in self.dateranges:
                # print self.get_name(),
                # "Search a next invalid from DR", time.asctime(time.localtime(local_min))
                # print dr.__dict__
                next_t = daterange.get_next_invalid_time_from_t(local_min)

                # print self.get_name(), "Dr", dr.__dict__,
                # "give me next invalid", time.asctime(time.localtime(m))
                if next_t is not None:
                    # But maybe it's invalid for this dr, but valid for other ones.
                    # if not self.is_time_valid(m):
                    #     print "Final: Got a next invalid at", time.asctime(time.localtime(m))
                    dr_mins.append(next_t)
                    # if not self.is_time_valid(m):
                    #    val_inval.append(m)
                    # else:
                    #    val_valids.append(m)
                    #    print "Add a m", time.asctime(time.localtime(m))
                    # else:
                    #     print dr.__dict__
                    #     print "F**K bad result\n\n\n"
            # print "Inval"
            # for v in val_inval:
            #    print "\timestamp", time.asctime(time.localtime(v))
            # print "Valid"
            # for v in val_valids:
            #    print "\timestamp", time.asctime(time.localtime(v))

            if dr_mins != []:
                local_min = min(dr_mins)
                # Take the minimum valid as lower for next search
                # local_min_valid = 0
                # if val_valids != []:
                #    local_min_valid = min(val_valids)
                # if local_min_valid != 0:
                #    local_min = local_min_valid
                # else:
                #    local_min = min(dr_mins)
                # print "UPDATE After dr: found invalid local min:",
                #  time.asctime(time.localtime(local_min)),
                #  "is valid", self.is_time_valid(local_min)

            # print self.get_name(),
            # 'Invalid: local min', local_min #time.asctime(time.localtime(local_min))
            # We do not loop unless the local_min is not valid
            if not self.is_time_valid(local_min):
                still_loop = False
            else:  # continue until we reach too far..., in one minute
                # After one month, go quicker...
                if local_min > original_t + 3600 * 24 * 30:
                    local_min += 3600
                else:  # else search for 1min precision
                    local_min += 60
                # after one year, stop.
                if local_min > original_t + 3600 * 24 * 366 + 1:  # 60*24*366 + 1:
                    still_loop = False
            # print "Loop?", still_loop
            # if we've got a real value, we check it with the exclude
            if local_min is not None:
                # Now check if local_min is not valid
                for timeperiod in self.exclude:
                    # print self.get_name(),
                    # "we check for invalid",
                    # time.asctime(time.localtime(local_min)), 'with tp', tp.name
                    if timeperiod.is_time_valid(local_min):
                        still_loop = True
                        # local_min + 60
                        local_min = timeperiod.get_next_invalid_time_from_t(
                            local_min + 60)
                        # No loop more than one year
                        if local_min > original_t + 60 * 24 * 366 + 1:
                            still_loop = False
                            res = None

            if not still_loop:  # We find a possible value
                # We take the result the minimal possible
                if res is None or local_min < res:
                    res = local_min

        # print "Finished Return the next invalid", time.asctime(time.localtime(local_min))
        # Ok, we update the cache...
        self.invalid_cache[original_t] = local_min
        return local_min

    def has(self, prop):
        """
        Check if self have prop attribute

        :param prop: property name
        :type prop: string
        :return: true if self has this attribute
        :rtype: bool
        """
        warnings.warn(
            "{s.__class__.__name__} is deprecated, please use "
            "`hasattr(your_object, attr)` instead. This has() method will "
            "be removed in a later version.".format(s=self),
            DeprecationWarning,
            stacklevel=2)
        return hasattr(self, prop)

    def is_correct(self):
        """
        Check if dateranges of timeperiod are valid

        :return: false if at least one datarange is invalid
        :rtype: bool
        """
        valid = True
        for daterange in self.dateranges:
            good = daterange.is_correct()
            if not good:
                logger.error("[timeperiod::%s] invalid daterange ",
                             self.get_name())
            valid &= good

        # Warn about non correct entries
        for entry in self.invalid_entries:
            logger.warning("[timeperiod::%s] invalid entry '%s'",
                           self.get_name(), entry)
        return valid

    def __str__(self):
        """
        Get readable object

        :return: this object in readable format
        :rtype: str
        """
        string = ''
        string += str(self.__dict__) + '\n'
        for elt in self.dateranges:
            string += str(elt)
            (start, end) = elt.get_start_and_end_time()
            start = time.asctime(time.localtime(start))
            end = time.asctime(time.localtime(end))
            string += "\nStart and end:" + str((start, end))
        string += '\nExclude'
        for elt in self.exclude:
            string += str(elt)

        return string

    def resolve_daterange(self, dateranges, entry):
        """
        Try to solve dateranges (special cases)

        :param dateranges: dateranges
        :type dateranges: list
        :param entry: property of timeperiod
        :type entry: string
        :return: None
        """
        res = re.search(
            r'(\d{4})-(\d{2})-(\d{2}) - (\d{4})-(\d{2})-(\d{2}) / (\d+)[\s\t]*([0-9:, -]+)',
            entry)
        if res is not None:
            # print "Good catch 1"
            (syear, smon, smday, eyear, emon, emday, skip_interval,
             other) = res.groups()
            dateranges.append(
                CalendarDaterange(syear, smon, smday, 0, 0, eyear, emon, emday,
                                  0, 0, skip_interval, other))
            return

        res = re.search(r'(\d{4})-(\d{2})-(\d{2}) / (\d+)[\s\t]*([0-9:, -]+)',
                        entry)
        if res is not None:
            # print "Good catch 2"
            (syear, smon, smday, skip_interval, other) = res.groups()
            eyear = syear
            emon = smon
            emday = smday
            dateranges.append(
                CalendarDaterange(syear, smon, smday, 0, 0, eyear, emon, emday,
                                  0, 0, skip_interval, other))
            return

        res = re.search(
            r'(\d{4})-(\d{2})-(\d{2}) - (\d{4})-(\d{2})-(\d{2})[\s\t]*([0-9:, -]+)',
            entry)
        if res is not None:
            # print "Good catch 3"
            (syear, smon, smday, eyear, emon, emday, other) = res.groups()
            dateranges.append(
                CalendarDaterange(syear, smon, smday, 0, 0, eyear, emon, emday,
                                  0, 0, 0, other))
            return

        res = re.search(r'(\d{4})-(\d{2})-(\d{2})[\s\t]*([0-9:, -]+)', entry)
        if res is not None:
            # print "Good catch 4"
            (syear, smon, smday, other) = res.groups()
            eyear = syear
            emon = smon
            emday = smday
            dateranges.append(
                CalendarDaterange(syear, smon, smday, 0, 0, eyear, emon, emday,
                                  0, 0, 0, other))
            return

        res = re.search(
            r'([a-z]*) ([\d-]+) ([a-z]*) - ([a-z]*) ([\d-]+) ([a-z]*) / (\d+)[\s\t]*([0-9:, -]+)',
            entry)
        if res is not None:
            # print "Good catch 5"
            (swday, swday_offset, smon, ewday, ewday_offset, emon,
             skip_interval, other) = res.groups()
            smon_id = Daterange.get_month_id(smon)
            emon_id = Daterange.get_month_id(emon)
            swday_id = Daterange.get_weekday_id(swday)
            ewday_id = Daterange.get_weekday_id(ewday)
            dateranges.append(
                MonthWeekDayDaterange(0, smon_id, 0, swday_id, swday_offset, 0,
                                      emon_id, 0, ewday_id, ewday_offset,
                                      skip_interval, other))
            return

        res = re.search(
            r'([a-z]*) ([\d-]+) - ([a-z]*) ([\d-]+) / (\d+)[\s\t]*([0-9:, -]+)',
            entry)
        if res is not None:
            # print "Good catch 6"
            (t00, smday, t01, emday, skip_interval, other) = res.groups()
            if t00 in Daterange.weekdays and t01 in Daterange.weekdays:
                swday = Daterange.get_weekday_id(t00)
                ewday = Daterange.get_weekday_id(t01)
                swday_offset = smday
                ewday_offset = emday
                dateranges.append(
                    WeekDayDaterange(0, 0, 0, swday, swday_offset, 0, 0, 0,
                                     ewday, ewday_offset, skip_interval,
                                     other))
                return
            elif t00 in Daterange.months and t01 in Daterange.months:
                smon = Daterange.get_month_id(t00)
                emon = Daterange.get_month_id(t01)
                dateranges.append(
                    MonthDateDaterange(0, smon, smday, 0, 0, 0, emon, emday, 0,
                                       0, skip_interval, other))
                return
            elif t00 == 'day' and t01 == 'day':
                dateranges.append(
                    MonthDayDaterange(0, 0, smday, 0, 0, 0, 0, emday, 0, 0,
                                      skip_interval, other))
                return

        res = re.search(
            r'([a-z]*) ([\d-]+) - ([\d-]+) / (\d+)[\s\t]*([0-9:, -]+)', entry)
        if res is not None:
            # print "Good catch 7"
            (t00, smday, emday, skip_interval, other) = res.groups()
            if t00 in Daterange.weekdays:
                swday = Daterange.get_weekday_id(t00)
                swday_offset = smday
                ewday = swday
                ewday_offset = emday
                dateranges.append(
                    WeekDayDaterange(0, 0, 0, swday, swday_offset, 0, 0, 0,
                                     ewday, ewday_offset, skip_interval,
                                     other))
                return
            elif t00 in Daterange.months:
                smon = Daterange.get_month_id(t00)
                emon = smon
                dateranges.append(
                    MonthDateDaterange(0, smon, smday, 0, 0, 0, emon, emday, 0,
                                       0, skip_interval, other))
                return
            elif t00 == 'day':
                dateranges.append(
                    MonthDayDaterange(0, 0, smday, 0, 0, 0, 0, emday, 0, 0,
                                      skip_interval, other))
                return

        res = re.search(
            r'([a-z]*) ([\d-]+) ([a-z]*) - ([a-z]*) ([\d-]+) ([a-z]*) [\s\t]*([0-9:, -]+)',
            entry)
        if res is not None:
            # print "Good catch 8"
            (swday, swday_offset, smon, ewday, ewday_offset, emon,
             other) = res.groups()
            smon_id = Daterange.get_month_id(smon)
            emon_id = Daterange.get_month_id(emon)
            swday_id = Daterange.get_weekday_id(swday)
            ewday_id = Daterange.get_weekday_id(ewday)
            dateranges.append(
                MonthWeekDayDaterange(0, smon_id, 0, swday_id, swday_offset, 0,
                                      emon_id, 0, ewday_id, ewday_offset, 0,
                                      other))
            return

        res = re.search(r'([a-z]*) ([\d-]+) - ([\d-]+)[\s\t]*([0-9:, -]+)',
                        entry)
        if res is not None:
            # print "Good catch 9"
            (t00, smday, emday, other) = res.groups()
            if t00 in Daterange.weekdays:
                swday = Daterange.get_weekday_id(t00)
                swday_offset = smday
                ewday = swday
                ewday_offset = emday
                dateranges.append(
                    WeekDayDaterange(0, 0, 0, swday, swday_offset, 0, 0, 0,
                                     ewday, ewday_offset, 0, other))
                return
            elif t00 in Daterange.months:
                smon = Daterange.get_month_id(t00)
                emon = smon
                dateranges.append(
                    MonthDateDaterange(0, smon, smday, 0, 0, 0, emon, emday, 0,
                                       0, 0, other))
                return
            elif t00 == 'day':
                dateranges.append(
                    MonthDayDaterange(0, 0, smday, 0, 0, 0, 0, emday, 0, 0, 0,
                                      other))
                return

        res = re.search(
            r'([a-z]*) ([\d-]+) - ([a-z]*) ([\d-]+)[\s\t]*([0-9:, -]+)', entry)
        if res is not None:
            # print "Good catch 10"
            (t00, smday, t01, emday, other) = res.groups()
            if t00 in Daterange.weekdays and t01 in Daterange.weekdays:
                swday = Daterange.get_weekday_id(t00)
                ewday = Daterange.get_weekday_id(t01)
                swday_offset = smday
                ewday_offset = emday
                dateranges.append(
                    WeekDayDaterange(0, 0, 0, swday, swday_offset, 0, 0, 0,
                                     ewday, ewday_offset, 0, other))
                return
            elif t00 in Daterange.months and t01 in Daterange.months:
                smon = Daterange.get_month_id(t00)
                emon = Daterange.get_month_id(t01)
                dateranges.append(
                    MonthDateDaterange(0, smon, smday, 0, 0, 0, emon, emday, 0,
                                       0, 0, other))
                return
            elif t00 == 'day' and t01 == 'day':
                dateranges.append(
                    MonthDayDaterange(0, 0, smday, 0, 0, 0, 0, emday, 0, 0, 0,
                                      other))
                return

        res = re.search(r'([a-z]*) ([\d-]+) ([a-z]*)[\s\t]*([0-9:, -]+)',
                        entry)
        if res is not None:
            # print "Good catch 11"
            (t00, t02, t01, other) = res.groups()
            if t00 in Daterange.weekdays and t01 in Daterange.months:
                swday = Daterange.get_weekday_id(t00)
                smon = Daterange.get_month_id(t01)
                emon = smon
                ewday = swday
                ewday_offset = t02
                dateranges.append(
                    MonthWeekDayDaterange(0, smon, 0, swday, t02, 0, emon, 0,
                                          ewday, ewday_offset, 0, other))
                return
            if not t01:
                # print "Good catch 12"
                if t00 in Daterange.weekdays:
                    swday = Daterange.get_weekday_id(t00)
                    swday_offset = t02
                    ewday = swday
                    ewday_offset = swday_offset
                    dateranges.append(
                        WeekDayDaterange(0, 0, 0, swday, swday_offset, 0, 0, 0,
                                         ewday, ewday_offset, 0, other))
                    return
                if t00 in Daterange.months:
                    smon = Daterange.get_month_id(t00)
                    emon = smon
                    emday = t02
                    dateranges.append(
                        MonthDateDaterange(0, smon, t02, 0, 0, 0, emon, emday,
                                           0, 0, 0, other))
                    return
                if t00 == 'day':
                    emday = t02
                    dateranges.append(
                        MonthDayDaterange(0, 0, t02, 0, 0, 0, 0, emday, 0, 0,
                                          0, other))
                    return

        res = re.search(r'([a-z]*)[\s\t]+([0-9:, -]+)', entry)
        if res is not None:
            # print "Good catch 13"
            (t00, other) = res.groups()
            if t00 in Daterange.weekdays:
                day = t00
                dateranges.append(StandardDaterange(day, other))
                return
        logger.info("[timeentry::%s] no match for %s", self.get_name(), entry)
        self.invalid_entries.append(entry)

    def apply_inheritance(self):
        """
        Inherite no properties and no custom variables for timeperiod

        :return: None
        """
        pass

    def explode(self):
        """
        Try to resolv all unresolved elements

        :param timeperiods: Timeperiods object
        :type timeperiods:
        :return: None
        """
        for entry in self.unresolved:
            self.resolve_daterange(self.dateranges, entry)
        self.unresolved = []

    def linkify(self, timeperiods):
        """
        Will make timeperiod in exclude with id of the timeperiods

        :param timeperiods: Timeperiods object
        :type timeperiods:
        :return: None
        """
        new_exclude = []
        if hasattr(self, 'exclude') and self.exclude != []:
            logger.debug("[timeentry::%s] have excluded %s", self.get_name(),
                         self.exclude)
            excluded_tps = self.exclude
            # print "I will exclude from:", excluded_tps
            for tp_name in excluded_tps:
                timepriod = timeperiods.find_by_name(tp_name.strip())
                if timepriod is not None:
                    new_exclude.append(timepriod)
                else:
                    logger.error("[timeentry::%s] unknown %s timeperiod",
                                 self.get_name(), tp_name)
        self.exclude = new_exclude

    def check_exclude_rec(self):
        """
        Check if this timeperiod is tagged

        :return: if tagged return false, if not true
        :rtype: bool
        """
        if self.rec_tag:
            logger.error("[timeentry::%s] is in a loop in exclude parameter",
                         self.get_name())
            return False
        self.rec_tag = True
        for timeperiod in self.exclude:
            timeperiod.check_exclude_rec()
        return True

    def fill_data_brok_from(self, data, brok_type):
        """
        Add timeperiods from brok

        :param data: timeperiod dictionnary
        :type data: dict
        :param brok_type: brok type
        :type brok_type: string
        :return: None
        """
        cls = self.__class__
        # Now config properties
        for prop, entry in cls.properties.items():
            # Is this property intended for broking?
            # if 'fill_brok' in entry:
            if brok_type in entry.fill_brok:
                if hasattr(self, prop):
                    data[prop] = getattr(self, prop)
                elif entry.has_default:
                    data[prop] = entry.default