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)
def make_correvent(self, info_dictionary): """ Récupère dans le contexte les informations transmises par les règles, crée les événements corrélés (agrégats d'événements) nécessaires dans la base de données et les transmet au bus. Permet de satisfaire les exigences suivantes : - VIGILO_EXIG_VIGILO_COR_0040, @param info_dictionary: Dictionnaire contenant les informations concernant l'événement en cours de trairement. @type info_dictionary: C{dict} @return: Instance de l'événement corrélé créé ou mis à jour ou C{None} si le traitement n'a pas engendré la création ni la mise à jour d'un événement corrélé. @rtype: L{CorrEvent} """ ctx = self.context_factory(info_dictionary["id"], transaction=False) raw_event_id = yield ctx.get('raw_event_id') # Il peut y avoir plusieurs raisons à l'absence d'un ID brut : # - l'alerte brute portait sur un HLS; dans ce cas il ne s'agit pas # vraiment d'une erreur (on n'enregistre pas d'événement corrélé). # - l'alerte brute portait sur un élément non configuré; dans ce cas il # s'agit d'une véritable erreur, mais le log est déjà enregistré dans # db_insertion.py. Inutile de répéter les logs ici. # - l'alerte indique un état UP/OK et aucun événement déjà ouvert n'a # pu être trouvé. Inutile d'alerter l'opérateur car tout va bien. # Un log est enregistré dans db_insertion.py et on peut ignorer le # problème ici. if raw_event_id is None: defer.returnValue(None) state = info_dictionary['state'] timestamp = info_dictionary['timestamp'] # Si une règle ou un callback demande explicitement qu'aucune # alerte ne soit générée pour cet événement, on lui obéit ici. stop = yield ctx.get('no_alert') if stop: hostname = info_dictionary['host'] servicename = info_dictionary['service'] LOGGER.info(_( 'Ignoring event #%(idevent)d on (%(host)r, %(service)r) ' 'as requested by the correlation rules') % { 'idevent': raw_event_id, 'host': hostname, 'service': servicename, }) defer.returnValue(None) item_id = yield ctx.get('idsupitem') # Identifiant de l'événement corrélé à mettre à jour. update_id = yield self._get_update_id(item_id) correvent = None if update_id is not None: # On récupère l'événement corrélé existant pour mise à jour. correvent = yield self._get_updated_correvent(update_id, timestamp) # Il s'agit d'une création ou bien l'événement corrélé # indiqué n'existe pas. if correvent is None: update_id = None # Si l'état de l'alerte brute est 'OK' ou 'UP', on ne fait rien. if state in ("OK", "UP"): LOGGER.info(_('Raw event ignored. Reason: status = %r'), state) defer.returnValue(None) aggregated = yield self._aggregate_topologically(ctx, correvent, raw_event_id, item_id) if aggregated: LOGGER.debug("Event #%d masked due to topological aggregation", raw_event_id) defer.returnValue(None) if correvent is None: # Lorsqu'un nouvel agrégat doit être créé, il se peut que la cause # ait anciennement fait partie d'autres agrégats désormais OK/UP. # On doit supprimer ces associations avant de continuer (cf. #1027). yield remove_from_all_aggregates(raw_event_id, self.database) # Création du nouvel agrégat à partir de son événement cause. LOGGER.debug(_('Creating a new correlated event')) correvent = CorrEvent() correvent.idcause = raw_event_id # On remplit l'événement corrélé et le dictionnaire d'infos # à partir du contexte de corrélation. yield self._fill_with_context(ctx, info_dictionary, correvent, timestamp) if correvent.ack == CorrEvent.ACK_CLOSED: yield self._handle_closed_correvent(ctx, correvent, state, timestamp, item_id) # On sauvegarde l'événement corrélé dans la base de données. yield self.database.run(DBSession.add, correvent, transaction=False) yield self.database.run(DBSession.flush, transaction=False) # Récupération de l'identifiant de l'agrégat pour plus tard. # Doit être fait via db_thread. En pratique, cela signifie qu'on # doit faire un merge() pour fetcher à nouveau tous les attributs # avant de pouvoir y accéder depuis le thread principal. correvent = yield self.database.run( DBSession.merge, correvent, transaction=False) idcorrevent = correvent.idcorrevent # Ajout de l'alerte brute dans l'agrégat. yield add_to_aggregate( raw_event_id, idcorrevent, self.database, ctx, item_id, merging=False ) if update_id is None: info_dictionary['update'] = False info_dictionary['idcorrevent'] = idcorrevent else: info_dictionary['update'] = True info_dictionary['idcorrevent'] = update_id if state in ('OK', 'UP'): # La cause de l'événement corrélé n'est plus en panne, # on tente de désagréger les événements bruts associés. yield self._disaggregate(ctx, correvent, update_id, timestamp) # On envoie le message correvent correspondant sur le bus # et on enregistre une trace dans les logs. yield self.publisher.sendMessage(info_dictionary) self._log_correvent(info_dictionary) yield self._aggregate_successors(ctx, idcorrevent) yield self.database.run(DBSession.flush, transaction=False) defer.returnValue(correvent)