예제 #1
0
    def _fetch_db(result):
        LOGGER.debug('Open aggregate for item #%d: %r', item_id, result)
        # Si l'info se trouvait dans le cache,
        # on utilise cette valeur là.
        if result is not None:
            # La valeur 0 est utilisée à la place de None
            # dans le cache. On fait la conversion inverse ici.
            if not result:
                return None
            return result

        # Sinon on récupère l'information
        # depuis la base de données...
        state_ok = StateName.statename_to_value('OK')
        state_up = StateName.statename_to_value('UP')
        aggregate = database.run(
            DBSession.query(
                CorrEvent.idcorrevent
            ).join(
                (Event, CorrEvent.idcause == Event.idevent)
            ).filter(
                # Ici, on ne prend pas en compte l'état d'acquittement :
                # on n'agrège jamais une alerte dans un agrégat OK/UP
                # (voir le ticket #1027 pour plus d'information).
                not_(Event.current_state.in_([state_ok, state_up]))
            ).filter(Event.idsupitem == item_id
            ).scalar)

        # ...et on met à jour le cache avant de retourner l'ID.
        # NB: la valeur 0 est utilisée à la place de None pour que
        # le cache puisse réellement servir à l'appel suivant.
        ctx.setShared('open_aggr:%d' % item_id, aggregate or 0)
        return aggregate
예제 #2
0
def close_green(logger, options):
    """
    Cette fonction ferme les événements qui apparaissent
    en vert dans VigiBoard (c'est-à-dire ceux dans l'état
    "OK" ou "UP").

    @param logger: Logger à utiliser pour afficher des messages.
    @type logger: C{logging.Logger}
    @param options: Liste d'options demandées par l'utilisateur
        du script.
    @type options: C{optparse.Values}
    @return: Nombre d'événements qui ont été fermés automatiquement.
    @rtype: C{int}
    """
    from vigilo.models.session import DBSession
    from vigilo.models.tables import Event, CorrEvent, StateName, EventHistory

    sought_states = []
    if options.state_up:
        sought_states.append(StateName.statename_to_value(u'UP'))
    if options.state_ok:
        sought_states.append(StateName.statename_to_value(u'OK'))

    query = DBSession.query(
            CorrEvent
        ).join(
            (Event, Event.idevent == CorrEvent.idcause),
        ).filter(Event.current_state.in_(sought_states)
        ).filter(CorrEvent.ack != CorrEvent.ACK_CLOSED)
    if options.days is not None and options.days > 0:
        # Génère une date qui se trouve options.days jours dans le passé.
        old_date = datetime.fromtimestamp(time.time() - options.days * 86400)
        query = query.filter(CorrEvent.timestamp_active <= old_date)

    events = query.all()
    username = unicode(pwd.getpwuid(os.getuid())[0])
    for event in events:
        logger.info(_('closing event on %s') % event.cause.supitem)

        # On ajoute un message dans l'historique pour la traçabilité.
        history = EventHistory(
                type_action=u"Acknowledgement change state",
                idevent=event.idcause,
                value=u"",
                text=u"Automatically marked the event as closed",
                username=username,
                timestamp=datetime.now(),
            )
        DBSession.add(history)

        # On referme l'événement.
        event.ack = CorrEvent.ACK_CLOSED
        DBSession.flush()
    return len(events)
예제 #3
0
def insert_hls_history(info_dictionary):
    """
    Insère le nouvel état du service de haut niveau dans HLSHistory
    afin de conserver une trace.

    @param info_dictionary: Dictionnaire contenant les informations
        extraites du message d'alerte reçu par le rule dispatcher.
    @type info_dictionary: C{dict}
    """

    if not info_dictionary['idsupitem']:
        LOGGER.error(_('Got a reference to a non configured high-level '
                        'service (%(service)r)'), {
                            "service": info_dictionary["service"],
                        })
        return None

    history = HLSHistory()
    history.idhls = info_dictionary['idsupitem']
    # On enregistre l'heure à laquelle le message a
    # été traité plutôt que le timestamp du message.
    history.timestamp = datetime.now()
    history.idstatename = StateName.statename_to_value(
                            info_dictionary['state'])
    DBSession.add(history)
예제 #4
0
def insert_deps():
    """Insère les dépendances nécessaires aux tests."""
    timestamp = datetime.now()

    host = Host(
        name=u'bar',
        description=u'',
        hosttpl=u'',
        address=u'127.0.0.1',
        snmpport=42,
        snmpcommunity=u'public',
        snmpversion=u'3',
        weight=42,
    )
    DBSession.add(host)
    DBSession.flush()

    hostgroup = SupItemGroup(name=u'foo', parent=None)
    hostgroup.supitems.append(host)
    DBSession.add(hostgroup)
    DBSession.flush()

    event = Event(
        supitem=host,
        timestamp=timestamp,
        current_state=StateName.statename_to_value(u'WARNING'),
        message=u'Hello world',
    )
    DBSession.add(event)
    DBSession.flush()

    correvent = CorrEvent(
        priority=42,
        trouble_ticket=u'FOO BAR BAZ éçà',
        ack=CorrEvent.ACK_NONE,
        occurrence=42,
        timestamp_active=timestamp,
        cause=event,
    )
    correvent.events.append(event)
    DBSession.add(correvent)
    DBSession.flush()

    # On donne l'accès aux données.
    usergroup = UserGroup.by_group_name(u'users')
    DBSession.add(DataPermission(
        group=hostgroup,
        usergroup=usergroup,
        access=u'r',
    ))
    DBSession.flush()
    return timestamp
예제 #5
0
    def test_reuse_event_with_no_correvent(self):
        """Ne pas créer de nouvel événement brut sans CorrEvent (#908)."""
        self.make_dependencies()
        host = DBSession.query(Host).first()
        ts = int(time.time())

        # On crée un événement brut, qu'on ne rattache
        # à aucun événement corrélé.
        DBSession.add(
            Event(
                supitem=host,
                current_state=StateName.statename_to_value(u"WARNING"),
                message="WARNING: ping",
                timestamp=datetime.fromtimestamp(ts - 42),
            )
        )
        DBSession.flush()

        # Préparation des informations du messages
        # et mise à jour de l'événement brut en base.
        info_dictionary = {
            "timestamp": datetime.fromtimestamp(ts),
            "host": host.name,
            "service": None,
            "state": u"CRITICAL",
            "message": u"CRITICAL: even worse",
            "idsupitem": SupItem.get_supitem(host.name, None),
        }
        insert_event(info_dictionary)

        # Aucun nouvel événement brut ne doit avoir été créé.
        event = DBSession.query(Event).one()
        # À la place, l'événement initial doit avoir été mis à jour.
        self.assertEquals(datetime.fromtimestamp(ts), event.timestamp)
        self.assertEquals(StateName.statename_to_value(u"CRITICAL"), event.current_state)
        self.assertEquals(StateName.statename_to_value(u"CRITICAL"), event.peak_state)
        self.assertEquals(StateName.statename_to_value(u"WARNING"), event.initial_state)
        self.assertEquals(info_dictionary["message"], event.message)
예제 #6
0
    def check_connectors_freshness(self):
        """
        Vérifie dans la base de données la date de dernière mise à jour de
        l'état des connecteurs Nagios. Retourne la liste des machines dont le
        connecteur Nagios n'a pas donné signe de vie depuis plus d'une certaine
        durée (modifiable dans le fichier de settings de l'application).
        """

        threshold = int(config['freshness_threshold'])
        if threshold <= 0:
            return []

        # On récupère dans la BDD la liste des connecteurs nagios
        # avec leur état et leur date de dernière mise à jour
        collectors = DBSession.query(
            Host.name,
            State.state,
            State.timestamp
        ).join(
            (LowLevelService, Host.idhost == LowLevelService.idhost)
        ).join(
            (State, LowLevelService.idservice == State.idsupitem)
        ).filter(
            LowLevelService.servicename == 'vigilo-connector-nagios'
        ).all()

        # En partant de cette première liste on en construit une
        # seconde ne contenant que les noms des connecteurs en panne
        collectors_down = []
        for c in collectors :
            if c.state not in [
                StateName.statename_to_value('OK'),
                StateName.statename_to_value('WARNING')
            ] or c.timestamp <= datetime.now() - timedelta(seconds=threshold):
                collectors_down.append(c.name)

        # On retourne cette seconde liste
        return collectors_down
예제 #7
0
    def process(self, link, msg_id):
        """
        Traitement du message par la règle.

        @param link: Objet servant de lien avec le dispatcher et pouvant
            par exemple être utilisé pour envoyer des messages sur le bus.
        @type link: C{vigilo.correlator.actors.rule_runner.RuleRunner}
        @param msg_id: Identifiant de l'alerte brute traitée.
        @type  msg_id: C{unicode}
        """
        ctx = self._get_context(msg_id)
        priority = ctx.get('priority')
        item_id = ctx.get('idsupitem')

        state_ok = StateName.statename_to_value(u'OK')
        state_up = StateName.statename_to_value(u'UP')
        curr_priority = self._database.run(
            DBSession.query(
                CorrEvent.priority
            ).join(
                (Event, CorrEvent.idcause == Event.idevent),
                (SupItem, SupItem.idsupitem == Event.idsupitem),
            ).filter(SupItem.idsupitem == item_id
            ).filter(
                not_(and_(
                    Event.current_state.in_([state_ok, state_up]),
                    CorrEvent.ack == CorrEvent.ACK_CLOSED
                ))
            ).scalar)

        if curr_priority is None or priority is None:
            return

        if settings['correlator']['priority_order'] == 'asc':
            priority = min(priority, curr_priority)
        else:
            priority = max(priority, curr_priority)
        ctx.set('priority', priority)
예제 #8
0
def insert_state(info_dictionary):
    """
    Insère l'état fourni par un message d'événement dans la BDD.

    Retourne cet état instancié.

    @param info_dictionary: Dictionnaire contenant les informations
    extraites du message d'alerte reçu par le rule dispatcher.
    @type info_dictionary: C{dict}
    """

    if not info_dictionary['idsupitem']:
        LOGGER.error(_('Got a reference to a non configured item '
                       '(%(host)r, %(service)r), skipping state'), {
                            "host": info_dictionary["host"],
                            "service": info_dictionary["service"],
                        })
        return None

    # On vérifie s'il existe déjà un état
    # enregistré dans la BDD pour cet item.
    state = DBSession.query(State).get(info_dictionary['idsupitem'])

    # Le cas échéant, on le crée.
    if not state:
        state = State(idsupitem=info_dictionary['idsupitem'])
    elif state and state.timestamp > info_dictionary["timestamp"]:
        return OldStateReceived(state.timestamp, info_dictionary["timestamp"])

    previous_state = state.state

    # On met à jour l'état dans la BDD
    state.message = info_dictionary["message"]
    state.timestamp = info_dictionary["timestamp"]
    state.state = StateName.statename_to_value(info_dictionary["state"])

    DBSession.add(state)
    return previous_state
예제 #9
0
    def test_reuse_correvent_if_possible(self):
        """Privilégier la réutilisation des CorrEvents (#908)."""
        self.make_dependencies()
        host = DBSession.query(Host).first()
        ts = int(time.time())

        # On crée un événement brut, qu'on ne rattache
        # à aucun événement corrélé.
        DBSession.add(
            Event(
                supitem=host,
                current_state=StateName.statename_to_value(u"WARNING"),
                message="WARNING: ping",
                timestamp=datetime.fromtimestamp(ts - 42),
            )
        )
        DBSession.flush()

        # On crée un deuxième événement brut correspondant au même
        # élément supervisé, cette fois rattaché à un événement corrélé.
        event = Event(
            supitem=host,
            current_state=StateName.statename_to_value(u"WARNING"),
            message="WARNING: ping2",
            timestamp=datetime.fromtimestamp(ts - 21),
        )
        correvent = CorrEvent(
            cause=event,
            priority=1,
            trouble_ticket=u"azerty1234",
            ack=CorrEvent.ACK_CLOSED,
            occurrence=1,
            timestamp_active=datetime.fromtimestamp(ts - 21),
        )
        correvent.events = [event]
        DBSession.add(event)
        DBSession.add(correvent)
        DBSession.flush()

        # Préparation des informations du messages
        # et mise à jour de l'événement brut en base.
        info_dictionary = {
            "timestamp": datetime.fromtimestamp(ts),
            "host": host.name,
            "service": None,
            "state": u"CRITICAL",
            "message": u"CRITICAL: even worse",
            "idsupitem": SupItem.get_supitem(host.name, None),
        }
        insert_event(info_dictionary)

        # On doit toujours avoir 2 événements bruts en base.
        self.assertEquals(2, DBSession.query(Event).count())
        # On doit avoir un seul événement corrélé.
        correvent = DBSession.query(CorrEvent).one()
        # La cause de cet événement corrélé
        # doit toujours être la même.
        DBSession.refresh(event)
        self.assertEquals(event, correvent.cause)

        # L'événement brut associé à l'événement
        # corrélé doit avoir été mis à jour.
        self.assertEquals(datetime.fromtimestamp(ts), event.timestamp)
        self.assertEquals(StateName.statename_to_value(u"CRITICAL"), event.current_state)
        self.assertEquals(StateName.statename_to_value(u"CRITICAL"), event.peak_state)
        self.assertEquals(StateName.statename_to_value(u"WARNING"), event.initial_state)
        self.assertEquals(info_dictionary["message"], event.message)

        # L'autre événement brut ne doit pas avoir changé.
        event = DBSession.query(Event).filter(Event.idevent != event.idevent).one()
        self.assertEquals(datetime.fromtimestamp(ts - 42), event.timestamp)
        self.assertEquals(StateName.statename_to_value(u"WARNING"), event.current_state)
        self.assertEquals(StateName.statename_to_value(u"WARNING"), event.peak_state)
        self.assertEquals(StateName.statename_to_value(u"WARNING"), event.initial_state)
        self.assertEquals("WARNING: ping", event.message)
예제 #10
0
 def do_get_dependencies(self):
     """Création des dépendances du test."""
     super(TestHLSHistory, self).do_get_dependencies()
     hls = functions.add_highlevelservice(u"HLS")
     idstatename = StateName.statename_to_value(u"WARNING")
     return dict(hls=hls, idstatename=idstatename)
예제 #11
0
def clean_vigiboard(logger, options, url):
    """
    Cette fonction supprime les événements les plus anciens et
    correspondant à des agrégats fermés des tables utilisées
    par VigiBoard.

    @param logger: Logger à utiliser pour afficher des messages.
    @type logger: C{logging.Logger}
    @param options: Liste d'options demandées par l'utilisateur
        du script.
    @type options: C{optparse.Values}
    @param url: Adresse de la base de données, sous la forme d'une URL
        déjà pré-traitée par SQLAlchemy.
    @type url: C{sqlalchemy.engine.url.URL}
    """
    from vigilo.models.session import DBSession
    from vigilo.models.tables import Event, CorrEvent, StateName, HLSHistory

    sought_states = [
        StateName.statename_to_value(u'OK'),
        StateName.statename_to_value(u'UP'),
    ]

    if options.days is not None:
        if options.days >= 0:
            # Génère une date qui se trouve options.days jours dans le passé.
            old_date = datetime.fromtimestamp(
                time.time() - options.days * 86400)

            # On supprime tous les événements dont l'idevent correspond à
            # un événement corrélé dont la dernière date d'activation est
            # plus vieille que old_date.
            # Comme les relations/FK sont créées en CASCADE, la suppression
            # des événements entraine aussi la suppression des événements
            # corrélés, des agrégats et de l'historique des événements.
            ids = DBSession.query(
                    Event.idevent
                ).join(
                    (CorrEvent, Event.idevent == CorrEvent.idcause)
                ).filter(Event.current_state.in_(sought_states)
                ).filter(CorrEvent.ack == CorrEvent.ACK_CLOSED
                ).filter(CorrEvent.timestamp_active <= old_date).all()
            ids = [event.idevent for event in ids]
            if ids:
                nb_deleted = DBSession.query(Event
                            ).filter(Event.idevent.in_(ids)
                            ).delete(synchronize_session='fetch')
            else:
                nb_deleted = 0
            logger.info(_("Deleted %(nb_deleted)d closed events which were "
                            "at least %(days)d days old.") % {
                            'nb_deleted': nb_deleted,
                            'days': options.days,
                        })

            # On supprime les entrées d'historique concernant les changements
            # d'état des services de haut niveau qui ont été ajoutées avant
            # old_date.
            nb_deleted = DBSession.query(
                                HLSHistory
                            ).filter(HLSHistory.timestamp <= old_date
                            ).delete(synchronize_session='fetch')
            logger.info(_("Deleted %(nb_deleted)d entries in the history "
                        "for high level services which were at least "
                        "%(days)d days old.") % {
                            'nb_deleted': nb_deleted,
                            'days': options.days,
                        })

    if options.size is not None:
        if options.size >= 0:
            # Calcule la taille actuelle de la base de données Vigilo.
            dbsize = DBSession.query(pg_database_size(url.database)).scalar()

            if dbsize > options.size:
                logger.info(_("The database is %(size)d bytes big, which is "
                    "more than the limit (%(limit)d bytes). I will now delete "
                    "old closed events and history entries to make room for "
                    "new ones.") % {
                        'size': dbsize,
                        'limit': options.size,
                    })

            # On supprime les événements clos en commençant par
            # les plus anciens.
            total_deleted = 0
            while dbsize > options.size:
                idevent = DBSession.query(
                        Event.idevent
                    ).join(
                        (CorrEvent, Event.idevent == CorrEvent.idcause)
                    ).filter(Event.current_state.in_(sought_states)
                    ).filter(CorrEvent.ack == CorrEvent.ACK_CLOSED
                    ).order_by(CorrEvent.timestamp_active.asc()).scalar()

                # Il n'y a plus aucun événement corrélé pouvant être supprimé.
                if idevent is None:
                    break

                nb_deleted = DBSession.query(Event
                                ).filter(Event.idevent == idevent
                                ).delete(synchronize_session='fetch')
                logger.info(_("Deleted closed event #%d to make room for "
                                "new events.") % idevent)
                if not nb_deleted:
                    break
                total_deleted += nb_deleted

                # On met à jour la taille connue de la base de données.
                dbsize = DBSession.query(
                            pg_database_size(url.database)
                        ).scalar()


            # Affiche quelques statistiques.
            logger.info(_("Deleted %(nb_deleted)d closed events. "
                "The database is now %(size)d bytes big (limit: "
                "%(limit)d bytes)") % {
                    'nb_deleted': total_deleted,
                    'size': dbsize,
                    'limit': options.size,
                })

            # On supprime les entrées d'historique concernant les changements
            # d'état des services de haut niveau, en commençant par les plus
            # anciens.
            total_deleted = 0
            while dbsize > options.size:
                idhistory = DBSession.query(HLSHistory.idhistory
                                ).order_by(HLSHistory.timestamp.asc()
                                ).scalar()

                # Il n'y a plus d'entrée d'historique concernant un service
                # de haut niveau pouvant être supprimée.
                if idhistory is None:
                    break

                nb_deleted = DBSession.query(HLSHistory
                                ).filter(HLSHistory.idhistory == idhistory
                                ).delete(synchronize_session='fetch')
                if not nb_deleted:
                    break
                total_deleted += nb_deleted

                # On met à jour la taille connue de la base de données.
                dbsize = DBSession.query(
                            pg_database_size(url.database)
                        ).scalar()

            # Affichage de statistiques actualisées.
            logger.info(_("Deleted %(nb_deleted)d history entries on "
                        "high level services. The database is now %(size)d "
                        "bytes big (limit: %(limit)d bytes)") % {
                            'nb_deleted': total_deleted,
                            'size': dbsize,
                            'limit': options.size,
                        })
    DBSession.flush()
예제 #12
0
def insert_event(info_dictionary):
    """
    Insère un événement dans la BDD.
    Retourne l'identifiant de cet événement.

    @param info_dictionary: Dictionnaire contenant les informations
    extraites du message d'alerte reçu par le rule dispatcher.
    @type info_dictionary: C{dict}
    @return: L'identifiant de l'événement dans la BDD.
    @rtype: C{int}
    """

    # S'il s'agit d'un événement concernant un HLS.
    if not info_dictionary["host"]:
        LOGGER.error(_('Received request to add an event on HLS "%s"'),
                            info_dictionary["service"])
        return None

    if not info_dictionary['idsupitem']:
        LOGGER.error(_('Got a reference to a non configured item '
                       '(%(host)r, %(service)r), skipping event'), {
                            "host": info_dictionary["host"],
                            "service": info_dictionary["service"],
                        })
        return None

    # On recherche un éventuel évènement brut concernant cet item.
    # L'événement doit être associé à un événement corrélé ouvert
    # ou bien ne pas être à rattaché à un événement corrélé du tout.
    cause_event = aliased(Event)
    current_event = aliased(Event)
    # On privilégie les événements bruts associés à un événement corrélé.
    order_clause = (CorrEvent.idcorrevent != None)
    event = DBSession.query(
                current_event,
                order_clause,   # Doit être présent dans le SELECT
                                # pour satisfaire PostgreSQL.
            ).outerjoin(
                (EVENTSAGGREGATE_TABLE, EVENTSAGGREGATE_TABLE.c.idevent ==
                    current_event.idevent),
                (CorrEvent, CorrEvent.idcorrevent ==
                    EVENTSAGGREGATE_TABLE.c.idcorrevent),
                (cause_event, cause_event.idevent == CorrEvent.idcause),
            ).filter(current_event.idsupitem == info_dictionary['idsupitem']
            ).filter(
                or_(
                    # Soit l'événement brut n'est pas
                    # rattaché à un événement corrélé.
                    CorrEvent.idcorrevent == None,

                    # Soit l'événement corrélé auquel
                    # il est rattaché est toujours ouvert.
                    not_(
                        and_(
                            cause_event.current_state.in_([
                                StateName.statename_to_value(u'OK'),
                                StateName.statename_to_value(u'UP')
                            ]),
                            CorrEvent.ack == CorrEvent.ACK_CLOSED
                        )
                    )
                )
            ).order_by(order_clause.desc()
            ).distinct().limit(2).all()

    # Si aucun événement correpondant à cet item ne figure dans la base
    if not event:
        # Si l'état de cette alerte est 'OK', on l'ignore
        if info_dictionary["state"] == "OK" or \
            info_dictionary["state"] == "UP":
            LOGGER.info(_('Ignoring request to create a new event '
                            'with state "%s" (nothing alarming here)'),
                            info_dictionary['state'])
            raise NoProblemException(info_dictionary.copy())
        # Sinon, il s'agit d'un nouvel incident, on le prépare.
        event = Event()
        event.idsupitem = info_dictionary['idsupitem']
        LOGGER.debug(_('Creating new event'))

    # Si plusieurs événements ont été trouvés
    else:
        if len(event) > 1:
            LOGGER.warning(_('Multiple raw events found, '
                             'using the first one available.'))
        # On sélectionne le premier Event parmi la liste
        # des tuples (Event, CorrEvent.idcorrevent != None).
        event = event[0][0]
        LOGGER.debug(_('Updating event %r'), event.idevent)

    # Nouvel état.
    new_state_value = StateName.statename_to_value(info_dictionary['state'])
    is_new_event = event.idevent is None

    # S'agit-il d'un événement important ?
    # Un événement est important s'il s'agit d'un nouvel événement
    # ou si un champ autre que le timestamp ou le message a changé.
    info_dictionary['important'] = is_new_event or \
                                    event.current_state != new_state_value or \
                                    event.message != info_dictionary['message']


    # Mise à jour de l'évènement.
    event.timestamp = info_dictionary['timestamp']
    event.current_state = new_state_value
    event.message = info_dictionary['message']

    # Sauvegarde de l'évènement.
    DBSession.add(event)

    # Les événements importants donnent lieu à l'ajout
    # d'une entrée dans l'historique.
    if info_dictionary['important']:
        history = EventHistory()

        history.type_action = is_new_event and \
                                u'New occurrence' or \
                                u'Nagios update state'

        try:
            history.state = \
                StateName.statename_to_value(info_dictionary['state'])
        except KeyError:
            # Si le nom d'état n'est pas reconnu, on ne fait rien.
            pass

        history.value = info_dictionary['state']
        history.text = info_dictionary['message']
        history.timestamp = info_dictionary['timestamp']
        history.username = None
        history.event = event
        DBSession.add(history)

    DBSession.flush()
    return event.idevent