Ejemplo n.º 1
0
 def setUp(self):
     """Initialisation avant chaque test."""
     super(TestAggregates, self).setUp()
     helpers.setup_db()
     helpers.populate_statename()
     self.forwarder = helpers.RuleDispatcherStub()
     self.context_factory = helpers.ContextStubFactory()
     self.corrbuilder = CorrEventBuilder(Mock(), DummyDatabaseWrapper(True))
     self.corrbuilder.context_factory = self.context_factory
     return defer.succeed(None)
Ejemplo n.º 2
0
 def setUp(self):
     """Initialise la BDD au début de chaque test."""
     super(TestCorrevents5, self).setUp()
     helpers.setup_db()
     helpers.populate_statename()
     self.forwarder = helpers.RuleDispatcherStub()
     self.context_factory = helpers.ContextStubFactory()
     self.corrbuilder = CorrEventBuilder(Mock(), DummyDatabaseWrapper(True))
     self.corrbuilder.context_factory = self.context_factory
     self.make_deps()
     self.ts = int(time.time()) - 10
     return defer.succeed(None)
Ejemplo n.º 3
0
 def setUp(self):
     """Initialise la BDD au début de chaque test."""
     super(TestCorrevents6, self).setUp()
     helpers.setup_db()
     helpers.populate_statename()
     self.forwarder = helpers.RuleDispatcherStub()
     self.context_factory = helpers.ContextStubFactory()
     self.corrbuilder = CorrEventBuilder(Mock(), DummyDatabaseWrapper(True))
     self.corrbuilder.context_factory = self.context_factory
     self.hosts = {
         1: functions.add_host(u'Host 1'),
         2: functions.add_host(u'Host 2'),
         3: functions.add_host(u'Host 3'),
     }
     return defer.succeed(None)
Ejemplo n.º 4
0
    def simulate_message_reception(self,
        new_state, host_name, service_name=None):
        """
        Génère un message de changement d'état concernant l'item passé en
        paramètre, réalise les mêmes traitements que ceux du rule_dispatcher
        et des règles, et déclenche l'exécution de la fonction make_correvent.
        """

        # On incrémente l'identifiant du message
        self.msgid += 1

        # On génère un timestamp à partir de la date courante
        timestamp = datetime.now()

        infos = {
            'type': "event",
            'id': self.msgid,
            'timestamp': timestamp,
            'service': service_name,
            'state': new_state,
            'message': new_state,
        }

        if host_name:
            infos['host'] = host_name
        else:
            infos['host'] = helpers.settings['correlator']['nagios_hls_host']

        idsupitem = SupItem.get_supitem(host_name, service_name)

        # On ajoute les données nécessaires dans le contexte.
        ctx = self.context_factory(self.msgid)
        yield ctx.set('hostname', host_name)
        yield ctx.set('servicename', service_name)
        yield ctx.set('statename', new_state)
        yield ctx.set('idsupitem', idsupitem)

        # On insère les données nécessaires dans la BDD:
        info_dictionary = {
            "id": self.msgid,
            "host": host_name,
            "service": service_name,
            "state": new_state,
            "timestamp": timestamp,
            "message": new_state,
            "idsupitem": idsupitem,
        }

        # - D'abord l'évènement ;
        LOGGER.info("Inserting event")
        raw_id = insert_event(info_dictionary)
        yield ctx.set('raw_event_id', raw_id)
        # - Et ensuite l'état.
        LOGGER.info("Inserting state")
        # Si le timestamp est trop récent insert_state ne fera rien
        DBSession.query(State).get(idsupitem).timestamp = timestamp
        insert_state(info_dictionary)
        DBSession.flush()

        # On force le traitement du message, par la fonction make_correvent,
        # comme s'il avait été traité au préalable par le rule_dispatcher.
        corrbuilder = CorrEventBuilder(Mock(), DummyDatabaseWrapper(True))
        corrbuilder.context_factory = self.context_factory

        LOGGER.info('Creating a new correlated event')
        yield corrbuilder.make_correvent(info_dictionary)
Ejemplo n.º 5
0
class TestCorrevents6(unittest.TestCase):
    @deferred(timeout=60)
    def setUp(self):
        """Initialise la BDD au début de chaque test."""
        super(TestCorrevents6, self).setUp()
        helpers.setup_db()
        helpers.populate_statename()
        self.forwarder = helpers.RuleDispatcherStub()
        self.context_factory = helpers.ContextStubFactory()
        self.corrbuilder = CorrEventBuilder(Mock(), DummyDatabaseWrapper(True))
        self.corrbuilder.context_factory = self.context_factory
        self.hosts = {
            1: functions.add_host(u'Host 1'),
            2: functions.add_host(u'Host 2'),
            3: functions.add_host(u'Host 3'),
        }
        return defer.succeed(None)

    @deferred(timeout=60)
    def tearDown(self):
        """Nettoie la BDD à la fin de chaque test."""
        super(TestCorrevents6, self).tearDown()
        helpers.teardown_db()
        self.context_factory.reset()
        return defer.succeed(None)


    @deferred(timeout=60)
    @defer.inlineCallbacks
    def test_no_predecessors(self):
        """Agrégation topologique : pas de prédécesseurs."""
        ctx = self.context_factory(42)
        event1 = functions.add_event(self.hosts[1], 'DOWN', u'foo')
        ctx.set('predecessors_aggregates', [])
        res = yield self.corrbuilder._aggregate_topologically(
                    ctx, None, event1.idevent, self.hosts[1].idhost)
        self.assertEquals(False, res)


    @deferred(timeout=60)
    @defer.inlineCallbacks
    def test_predecessor_and_no_aggregate(self):
        """Agrégation topologique : prédécesseur et pas d'agrégat."""
        ctx = self.context_factory(42)
        event1 = functions.add_event(self.hosts[1], 'DOWN', u'foo')
        event2 = functions.add_event(self.hosts[2], 'UNREACHABLE', u'foo')
        aggr1 = functions.add_correvent([event1])
        ctx.set('predecessors_aggregates', [aggr1.idcorrevent])
        ctx.set('successors_aggregates', [])
        res = yield self.corrbuilder._aggregate_topologically(
                    ctx, None, event2.idevent, self.hosts[2].idhost)
        self.assertEquals(True, res)
        self.assertEquals(1, DBSession.query(tables.CorrEvent).count())
        self.assertEquals(2, DBSession.query(tables.Event).count())


    @deferred(timeout=60)
    @defer.inlineCallbacks
    def test_nonexisting_predecessor(self):
        """Agrégation topologique : agrégat inexistant."""
        ctx = self.context_factory(42)
        event1 = functions.add_event(self.hosts[1], 'DOWN', u'foo')
        event2 = functions.add_event(self.hosts[2], 'UNREACHABLE', u'foo')
        ctx.set('predecessors_aggregates', [1])
        res = yield self.corrbuilder._aggregate_topologically(
                    ctx, None, event2.idevent, self.hosts[2].idhost)
        self.assertEquals(False, res)
        self.assertEquals(0, DBSession.query(tables.CorrEvent).count())
        self.assertEquals(2, DBSession.query(tables.Event).count())


    @deferred(timeout=60)
    @defer.inlineCallbacks
    def test_predecessor_and_aggregate(self):
        """Agrégation topologique : totale."""
        ctx = self.context_factory(42)
        event1 = functions.add_event(self.hosts[1], 'DOWN', u'foo')
        event2 = functions.add_event(self.hosts[2], 'UNREACHABLE', u'foo')
        event3 = functions.add_event(self.hosts[3], 'UNREACHABLE', u'foo')
        aggr1 = functions.add_correvent([event1])
        aggr2 = functions.add_correvent([event2])
        aggr3 = functions.add_correvent([event3])
        ctx.set('predecessors_aggregates', [aggr1.idcorrevent])
        ctx.set('successors_aggregates', [aggr3.idcorrevent])
        res = yield self.corrbuilder._aggregate_topologically(
                    ctx, aggr2, event2.idevent, self.hosts[2].idhost)
        self.assertEquals(True, res)
        self.assertEquals(1, DBSession.query(tables.CorrEvent).count())
        self.assertEquals(3, DBSession.query(tables.Event).count())
Ejemplo n.º 6
0
class TestCorrevents5(unittest.TestCase):

    @deferred(timeout=60)
    def setUp(self):
        """Initialise la BDD au début de chaque test."""
        super(TestCorrevents5, self).setUp()
        helpers.setup_db()
        helpers.populate_statename()
        self.forwarder = helpers.RuleDispatcherStub()
        self.context_factory = helpers.ContextStubFactory()
        self.corrbuilder = CorrEventBuilder(Mock(), DummyDatabaseWrapper(True))
        self.corrbuilder.context_factory = self.context_factory
        self.make_deps()
        self.ts = int(time.time()) - 10
        return defer.succeed(None)

    @deferred(timeout=60)
    def tearDown(self):
        """Nettoie la BDD à la fin de chaque test."""
        super(TestCorrevents5, self).tearDown()
        helpers.teardown_db()
        self.context_factory.reset()
        return defer.succeed(None)


    def make_deps(self):
        """
        Création de 2 hôtes "Host 1" et "Host 2".
        """
        self.hosts = {}
        for i in xrange(1, 4 + 1):
            self.hosts[i] = functions.add_host(u'Host %d' % i)
            print "Added %s with ID #%d" % (
                self.hosts[i].name,
                self.hosts[i].idhost)
        print ""


    @defer.inlineCallbacks
    def handle_alert(self, host, new_state, preds=None, succs=None):
        """
        Simule l'arrivée d'une alerte concernant un hôte
        avec l'état donné en argument.
        """
        new_state = unicode(new_state)

        if preds is None:
            preds = []
        if succs is None:
            succs = []

        self.ts += 1
        info_dictionary = {
            'id': self.ts,
            #'timestamp': self.ts,
            'host': host.name,
            'service': u'',
            'state': new_state,
            'message': new_state,
        }
        info_dictionary['timestamp'] = datetime.fromtimestamp(self.ts)

        ctx = self.context_factory(self.ts)


        # Création Event.
        event = DBSession.query(tables.Event).filter(
            tables.Event.idsupitem == host.idhost).first()
        if event is None:
            event = tables.Event(idsupitem=host.idhost)

        event.current_state = tables.StateName.statename_to_value(
                                info_dictionary['state'])
        event.message = unicode(info_dictionary['message'])
        event.timestamp = info_dictionary['timestamp']
        DBSession.add(event)
        DBSession.flush()

        open_aggr = DBSession.query(
                tables.CorrEvent.idcorrevent
            ).filter(tables.CorrEvent.idcause == event.idevent
            ).scalar()
        # open_aggr vaut None si aucun événement corrélé
        # n'existe pour le moment pour l'élément.
        # Le contexte doit contenir 0 à la place pour ce cas.
        open_aggr = open_aggr or 0

        # On passe par une DeferredList pour garantir l'exécution
        # de tous les Deferred comme étant un seul bloc logique.
        yield defer.DeferredList([
            ctx.set('hostname', host.name),
            ctx.set('servicename', ''),
            ctx.set('statename', new_state),
            ctx.set('raw_event_id', event.idevent),
            ctx.set('idsupitem', host.idhost),
            ctx.set('payload', None),
            ctx.set('timestamp', info_dictionary['timestamp']),
            ctx.set('predecessors_aggregates', preds),
            ctx.set('successors_aggregates', succs),
            ctx.setShared('open_aggr:%s' % host.idhost, open_aggr),
        ])

        res = yield self.corrbuilder.make_correvent(info_dictionary)

        idcorrevent = DBSession.query(
                tables.CorrEvent.idcorrevent
            ).filter(tables.CorrEvent.idcause == event.idevent
            ).scalar()
        # Le expunge_all() évite que SQLAlchemy ne garde en cache
        # la valeur du .events des CorrEvents.
        DBSession.expunge_all()
        defer.returnValue( (res, idcorrevent) )


    @deferred(timeout=60)
    @defer.inlineCallbacks
    def test_reaggregate(self):
        """Réagrégation des événements corrélés (ordre alternatif)."""
        # Host 2 dépend de Host 1
        dep_group = functions.add_dependency_group(
                        self.hosts[2], None, u'topology', u'|')
        functions.add_dependency(dep_group, self.hosts[1], 1)

        # 1. Un 1er agrégat doit avoir été créé.
        LOGGER.debug('Step 1')
        res, idcorrevent1 = yield self.handle_alert(
            self.hosts[2], 'UNREACHABLE')
        # Aucune erreur n'a été levée.
        self.assertNotEquals(res, None)

        # 2. Un nouvel agrégat doit avoir été créé et l'agrégat
        #    précédent doit avoir été fusionné dans celui-ci.
        LOGGER.debug('Step 2')
        res, idcorrevent2 = yield self.handle_alert(
            self.hosts[1], 'DOWN', succs=[idcorrevent1])
        # Aucune erreur n'a été levée.
        self.assertNotEquals(res, None)

        # On a 2 événements bruts et 1 agrégat en base.
        LOGGER.debug("Checking events")
        self.assertEquals(2, DBSession.query(tables.Event).count())
        LOGGER.debug("Checking correvents")
        db_correvents = DBSession.query(tables.CorrEvent).all()
        self.assertEquals(1, len(db_correvents))

        # 3. L'agrégat de l'étape 2 doit avoir été désagrégé.
        LOGGER.debug('Step 3')
        res, idcorrevent3 = yield self.handle_alert(
            self.hosts[1], 'UP')
        # Aucune erreur n'a été levée.
        self.assertNotEquals(res, None)

        # On a 2 événements bruts et 2 agrégats en base.
        LOGGER.debug("Checking events")
        self.assertEquals(2, DBSession.query(tables.Event).count())
        LOGGER.debug("Checking correvents")
        db_correvents = DBSession.query(tables.CorrEvent).all()
        self.assertEquals(2, len(db_correvents))
        db_correvents.sort(key=lambda x: x.cause.supitem.name)
        # Le premier à l'état "UP" et porte sur "Host 1".
        self.assertEquals(self.hosts[1].idhost,
                          db_correvents[0].cause.idsupitem)
        self.assertEquals(
            u'UP',
            tables.StateName.value_to_statename(
                db_correvents[0].cause.current_state)
        )
        # Le 2nd est "UNREACHABLE" et porte sur "Host 2".
        self.assertEquals(self.hosts[2].idhost,
                          db_correvents[1].cause.idsupitem)
        self.assertEquals(
            u'UNREACHABLE',
            tables.StateName.value_to_statename(
                db_correvents[1].cause.current_state)
        )

        # 4. Les événements doivent avoir été réagrégés.
        LOGGER.debug('Step 4')
        # 3 = id du nouveau correvent après désagrégation
        res, idcorrevent4 = yield self.handle_alert(
            self.hosts[1], 'DOWN', succs=[3])
        # Aucune erreur n'a été levée.
        self.assertNotEquals(res, None)
        self.assertEquals(idcorrevent4, idcorrevent2)

        # On a 2 événements bruts et 1 agrégat en base.
        LOGGER.debug("Checking events")
        self.assertEquals(2, DBSession.query(tables.Event).count())
        LOGGER.debug("Checking correvents")
        db_correvents = DBSession.query(tables.CorrEvent).all()
        self.assertEquals(1, len(db_correvents))
        # Il a l'état "DOWN", porte sur "Host 1"
        # et contient les 2 événements bruts.
        self.assertEquals(self.hosts[1].idhost,
                          db_correvents[0].cause.idsupitem)
        self.assertEquals(
            u'DOWN',
            tables.StateName.value_to_statename(
                db_correvents[0].cause.current_state)
        )
        self.assertEquals(
            [u'Host 1', u'Host 2'],
            sorted([ev.supitem.name for ev in db_correvents[0].events])
        )

    @deferred(timeout=60)
    @defer.inlineCallbacks
    def test_reaggregate2(self):
        """Réagrégation des événements corrélés."""
        # Host 2 dépend de Host 1
        dep_group = functions.add_dependency_group(
                        self.hosts[2], None, u'topology', u'|')
        functions.add_dependency(dep_group, self.hosts[1], 1)

        # 1. Un 1er agrégat doit avoir été créé.
        LOGGER.debug('Step 1')
        res, idcorrevent1 = yield self.handle_alert(
            self.hosts[1], 'DOWN')
        # Aucune erreur n'a été levée.
        self.assertNotEquals(res, None)

        # 2. L'événement brut doit avoir été fusionné
        #    dans l'agrégat précédent.
        LOGGER.debug('Step 2')
        res, idcorrevent2 = yield self.handle_alert(
            self.hosts[2], 'UNREACHABLE', preds=[idcorrevent1])
        # Aucune erreur, mais correvent agrégé.
        self.assertEquals(res, None)
        self.assertEquals(idcorrevent2, None)

        # On a 2 événements bruts et 1 agrégat en base.
        LOGGER.debug("Checking events")
        self.assertEquals(2, DBSession.query(tables.Event).count())
        LOGGER.debug("Checking correvents")
        db_correvents = DBSession.query(tables.CorrEvent).all()
        self.assertEquals(1, len(db_correvents))

        # 3. L'agrégat de l'étape 2 doit avoir été désagrégé.
        LOGGER.debug('Step 3')
        res, idcorrevent3 = yield self.handle_alert(
            self.hosts[1], 'UP')
        # Aucune erreur n'a été levée.
        self.assertNotEquals(res, None)

        # On a 2 événements bruts et 2 agrégats en base.
        LOGGER.debug("Checking events")
        self.assertEquals(2, DBSession.query(tables.Event).count())
        LOGGER.debug("Checking correvents")
        db_correvents = DBSession.query(tables.CorrEvent).all()
        self.assertEquals(2, len(db_correvents))
        db_correvents.sort(key=lambda x: x.cause.supitem.name)
        # Le premier à l'état "UP" et porte sur "Host 1".
        self.assertEquals(self.hosts[1].idhost,
                          db_correvents[0].cause.idsupitem)
        self.assertEquals(
            u'UP',
            tables.StateName.value_to_statename(
                db_correvents[0].cause.current_state)
        )
        # Le 2nd est "UNREACHABLE" et porte sur "Host 2".
        self.assertEquals(self.hosts[2].idhost,
                          db_correvents[1].cause.idsupitem)
        self.assertEquals(
            u'UNREACHABLE',
            tables.StateName.value_to_statename(
                db_correvents[1].cause.current_state)
        )

        # 4. Les événements doivent avoir été réagrégés.
        LOGGER.debug('Step 4')
        # 2 = id du nouveau correvent après désagrégation
        res, idcorrevent4 = yield self.handle_alert(
            self.hosts[1], 'DOWN', succs=[2])
        # Aucune erreur n'a été levée.
        self.assertNotEquals(res, None)
        self.assertEquals(idcorrevent4, idcorrevent1)

        # On a 2 événements bruts et 1 agrégat en base.
        LOGGER.debug("Checking events")
        self.assertEquals(2, DBSession.query(tables.Event).count())
        LOGGER.debug("Checking correvents")
        db_correvents = DBSession.query(tables.CorrEvent).all()
        self.assertEquals(1, len(db_correvents))
        # Il a l'état "DOWN", porte sur "Host 1"
        # et contient les 2 événements bruts.
        self.assertEquals(self.hosts[1].idhost,
                          db_correvents[0].cause.idsupitem)
        self.assertEquals(
            u'DOWN',
            tables.StateName.value_to_statename(
                db_correvents[0].cause.current_state)
        )
        self.assertEquals(
            [u'Host 1', u'Host 2'],
            sorted([ev.supitem.name for ev in db_correvents[0].events])
        )

    @deferred(timeout=60)
    @defer.inlineCallbacks
    def test_aggregate_3_levels(self):
        """
        Agrégation sur 3 niveaux.
        """
        # Ajout des dépendances topologiques :
        # - Host 2 dépend de Host 1
        # - Host 3 dépend de Host 2
        dep_group = functions.add_dependency_group(
                        self.hosts[2], None, u'topology', u'|')
        functions.add_dependency(dep_group, self.hosts[1], 1)
        dep_group = functions.add_dependency_group(
                        self.hosts[3], None, u'topology', u'|')
        functions.add_dependency(dep_group, self.hosts[2], 1)
        functions.add_dependency(dep_group, self.hosts[1], 2)

        # Simule la chute de "Host 3" puis "Host 1", puis "Host 2".
        res, idcorrevent3 = yield self.handle_alert(self.hosts[3], 'UNREACHABLE')
        self.assertNotEquals(res, None)
        res, idcorrevent1 = yield self.handle_alert(self.hosts[1], 'DOWN')
        self.assertNotEquals(res, None)
        res, idcorrevent2 = yield self.handle_alert(
            self.hosts[2],
            'UNREACHABLE',
            preds=[idcorrevent1],
            succs=[idcorrevent3],
        )
        self.assertEquals(res, None) # Pas de nouvel agrégat créé.

        # On s'attend à trouver 3 événements bruts et 1 agrégat.
        self.assertEquals(3, DBSession.query(tables.Event).count())
        self.assertEquals(1, DBSession.query(tables.CorrEvent).count())

        events = DBSession.query(tables.Event).all()
        events.sort(key=lambda x: x.supitem.name)
        self.assertEquals(u'Host 1', events[0].supitem.name)
        self.assertEquals(u'Host 2', events[1].supitem.name)
        self.assertEquals(u'Host 3', events[2].supitem.name)
Ejemplo n.º 7
0
class TestAggregates(unittest.TestCase):

    @deferred(timeout=60)
    def setUp(self):
        """Initialisation avant chaque test."""
        super(TestAggregates, self).setUp()
        helpers.setup_db()
        helpers.populate_statename()
        self.forwarder = helpers.RuleDispatcherStub()
        self.context_factory = helpers.ContextStubFactory()
        self.corrbuilder = CorrEventBuilder(Mock(), DummyDatabaseWrapper(True))
        self.corrbuilder.context_factory = self.context_factory
        return defer.succeed(None)

    @deferred(timeout=60)
    def tearDown(self):
        """Finalisation après chaque test."""
        super(TestAggregates, self).tearDown()
        helpers.teardown_db()
        self.context_factory.reset()
        return defer.succeed(None)


    @deferred(timeout=60)
    @defer.inlineCallbacks
    def test_aggregation_scenario(self):
        """
        Scénario d'agrégation des événements corrélés (#910).
        """

        # Création de 4 hôtes.
        hosts = []
        for i in xrange(1, 5):
            hosts.append(tables.Host(
                name = u'Host éçà %d' % i,
                snmpcommunity = u'com11',
                hosttpl = u'tpl11',
                address = u'192.168.0.%d' % i,
                snmpport = 11,
                weight = 42,
            ))
            DBSession.add(hosts[i - 1])
        DBSession.flush()

        # Host1 et Host3 dépendent de Host2.
        # Host2 dépend de Host4.
        dg1 = tables.DependencyGroup(operator=u'|', role=u'topology',
                                     dependent=hosts[0])
        dg2 = tables.DependencyGroup(operator=u'|', role=u'topology',
                                     dependent=hosts[2])
        dg3 = tables.DependencyGroup(operator=u'|', role=u'topology',
                                     dependent=hosts[1])
        DBSession.add(dg1)
        DBSession.add(dg2)
        DBSession.add(dg3)

        DBSession.add(tables.Dependency(
            group=dg1,
            supitem=hosts[1],
            distance=1,
        ))
        DBSession.add(tables.Dependency(
            group=dg2,
            supitem=hosts[1],
            distance=1,
        ))
        DBSession.add(tables.Dependency(
            group=dg3,
            supitem=hosts[3],
            distance=1,
        ))
        DBSession.add(tables.Dependency(
            group=dg1,
            supitem=hosts[3],
            distance=2,
        ))
        DBSession.add(tables.Dependency(
            group=dg2,
            supitem=hosts[3],
            distance=2,
        ))
        DBSession.flush()

        correvents = []

        for i in xrange(1, 5):
            info_dictionary = {
                'id': i,
                'host': u'Host éçà %d' % i,
                'service': None,
                'state': u'DOWN',
                'timestamp': datetime.now(),
                'message': u'Host %d' % i,
            }

            event = tables.Event(
                idsupitem=hosts[i - 1].idhost,
                timestamp=info_dictionary['timestamp'],
                current_state=tables.StateName.statename_to_value(
                    info_dictionary['state']
                ),
                message=info_dictionary['message'],
            )
            DBSession.add(event)
            DBSession.flush()

            ctx = self.context_factory(i)
            defs = [
                ctx.set('hostname', hosts[i - 1].name),
                ctx.set('servicename', None),
                ctx.set('statename', 'DOWN'),
                ctx.set('raw_event_id', event.idevent),
                ctx.set('idsupitem', hosts[i - 1].idhost),
                ctx.set('payload', None),
                ctx.set('timestamp', info_dictionary['timestamp']),
                # Pas strictement requis, mais permet d'avoir toujours le même
                # résultat lors du test, quelle que soit la configuration.
                ctx.set('priority', 42),
            ]

            # Valeurs dans le contexte spécifiques à chaque message.
            if i == 1:
                is_update = False
                idcorrevent = 1
            elif i == 2:
                is_update = False
                idcorrevent = 2
                defs.append(ctx.set('successors_aggregates',
                        [correvents[0].idcorrevent]))
            elif i == 3:
                is_update = False
                idcorrevent = 2
                defs.append(ctx.set('predecessors_aggregates',
                        [correvents[1].idcorrevent]))
            else:
                is_update = False
                idcorrevent = i
                defs.append(ctx.set('successors_aggregates',
                        [correvents[1].idcorrevent]))

            # Prépare le contexte et appelle la fonction
            # de création/mise à jour de l'agrégat.
            yield defer.DeferredList(defs)
            yield self.corrbuilder.make_correvent(info_dictionary)
            DBSession.flush()

            correvent = DBSession.query(
                    tables.CorrEvent
                ).filter(tables.CorrEvent.idcause == event.idevent
                ).first()
            correvents.append(correvent)

            print self.corrbuilder.publisher.sendMessage.call_args_list

        # Il doit y avoir 1 seul agrégat, dont la cause est Host2
        # et qui contient 4 événements correspondant aux 4 hôtes.
        correvent = DBSession.query(tables.CorrEvent).one()
        self.assertEquals(hosts[3].idhost, correvent.cause.idsupitem)
        self.assertEquals(4, len(correvent.events))
Ejemplo n.º 8
0
class TestCorrevents4(unittest.TestCase):

    @deferred(timeout=60)
    def setUp(self):
        """Initialise la BDD au début de chaque test."""
        super(TestCorrevents4, self).setUp()
        helpers.setup_db()
        helpers.populate_statename()
        self.forwarder = helpers.RuleDispatcherStub()
        self.context_factory = helpers.ContextStubFactory()
        self.corrbuilder = CorrEventBuilder(Mock(), DummyDatabaseWrapper(True))
        self.corrbuilder.context_factory = self.context_factory
        self.make_deps()
        self.ts = int(time.time()) - 10
        return defer.succeed(None)

    @deferred(timeout=60)
    def tearDown(self):
        """Nettoie la BDD à la fin de chaque test."""
        super(TestCorrevents4, self).tearDown()
        helpers.teardown_db()
        self.context_factory.reset()
        return defer.succeed(None)


    def make_deps(self):
        """
        Création de 4 hôtes "Host 1" jusqu'à "Host 4".
        """
        self.hosts = {}
        for i in xrange(1, 4 + 1):
            self.hosts[i] = functions.add_host(u'Host %d' % i)
            print "Added %s with ID #%d" % (
                self.hosts[i].name,
                self.hosts[i].idhost)
        print ""


    @defer.inlineCallbacks
    def handle_alert(self, host, new_state, preds=None, succs=None):
        """
        Simule l'arrivée d'une alerte concernant un hôte
        avec l'état donné en argument.
        """
        new_state = unicode(new_state)

        if preds is None:
            preds = []
        if succs is None:
            succs = []

        self.ts += 1
        info_dictionary = {
            'id': self.ts,
            #'timestamp': self.ts,
            'host': host.name,
            'service': u'',
            'state': new_state,
            'message': new_state,
        }
        info_dictionary['timestamp'] = datetime.fromtimestamp(self.ts)

        ctx = self.context_factory(self.ts)


        # Création Event.
        event = DBSession.query(tables.Event).filter(
            tables.Event.idsupitem == host.idhost).first()
        if event is None:
            event = tables.Event(idsupitem=host.idhost)

        event.current_state = tables.StateName.statename_to_value(
                                info_dictionary['state'])
        event.message = unicode(info_dictionary['message'])
        event.timestamp = info_dictionary['timestamp']
        DBSession.add(event)
        DBSession.flush()

        open_aggr = DBSession.query(
                tables.CorrEvent.idcorrevent
            ).filter(tables.CorrEvent.idcause == event.idevent
            ).scalar()
        # open_aggr vaut None si aucun événement corrélé
        # n'exite pour le moment pour l'élément.
        # Le contexte doit contenir 0 à la place pour ce cas.
        open_aggr = open_aggr or 0

        # On passe par une DeferredList pour garantir l'exécution
        # de tous les Deferred comme étant un seul bloc logique.
        yield defer.DeferredList([
            ctx.set('hostname', host.name),
            ctx.set('servicename', ''),
            ctx.set('statename', new_state),
            ctx.set('raw_event_id', event.idevent),
            ctx.set('idsupitem', host.idhost),
            ctx.set('payload', None),
            ctx.set('timestamp', info_dictionary['timestamp']),
            ctx.set('predecessors_aggregates', preds),
            ctx.set('successors_aggregates', succs),
            ctx.setShared('open_aggr:%s' % host.idhost, open_aggr),
        ])

        res = yield self.corrbuilder.make_correvent(info_dictionary)
        DBSession.flush()

        idcorrevent = DBSession.query(
                tables.CorrEvent.idcorrevent
            ).filter(tables.CorrEvent.idcause == event.idevent
            ).scalar()
        # Le expunge_all() évite que SQLAlchemy ne garde en cache
        # la valeur du .events des CorrEvents.
        DBSession.expunge_all()
        defer.returnValue( (res, idcorrevent) )


    @deferred(timeout=60)
    @defer.inlineCallbacks
    def test_desaggregate(self):
        """Désagrégation des événements corrélés (#467)."""
        # Ajout des dépendances topologiques :
        # - Host 2 dépend de Host 1
        # - Host 4 dépend de Host 1
        # - Host 3 dépend de Host 4
        dep_group = functions.add_dependency_group(
                        self.hosts[2], None, u'topology', u'|')
        functions.add_dependency(dep_group, self.hosts[1], 1)

        dep_group = functions.add_dependency_group(
                        self.hosts[4], None, u'topology', u'|')
        functions.add_dependency(dep_group, self.hosts[1], 1)

        dep_group = functions.add_dependency_group(
                        self.hosts[3], None, u'topology', u'|')
        functions.add_dependency(dep_group, self.hosts[4], 1)
        functions.add_dependency(dep_group, self.hosts[1], 2)

        # 1. Un 1er agrégat doit avoir été créé.
        res, idcorrevent1 = yield self.handle_alert(
            self.hosts[2], 'UNREACHABLE')
        print "Finished step 1\n"
        # Aucune erreur n'a été levée.
        self.assertNotEquals(res, None)
        self.assertNotEquals(idcorrevent1, None)
        # Un agrégat a été créé sur cet hôte...
        db_correvent = DBSession.query(tables.CorrEvent).get(idcorrevent1)
        self.assertEquals(self.hosts[2].idhost, db_correvent.cause.idsupitem)
        # ... dans l'état indiqué.
        self.assertEquals(
            u'UNREACHABLE',
            tables.StateName.value_to_statename(
                db_correvent.cause.current_state)
        )
        # ... contenant uniquement un événement (cause hôte 2).
        self.assertEquals(
            [u'Host 2'],
            [ev.supitem.name for ev in db_correvent.events]
        )

        # 2. Un nouvel agrégat doit avoir été créé et l'agrégat
        #    précédent doit avoir été fusionné dans celui-ci.
        res, idcorrevent2 = yield self.handle_alert(
            self.hosts[1], 'DOWN', succs=[idcorrevent1])
        print "Finished step 2\n"
        # Aucune erreur n'a été levée.
        self.assertNotEquals(res, None)
        self.assertNotEquals(idcorrevent2, None)
        # Il ne doit rester qu'un seul agrégat (le 1er a été fusionné).
        db_correvent = DBSession.query(tables.CorrEvent).one()
        self.assertEquals(db_correvent.idcorrevent, idcorrevent2)
        # ... dont la cause est l'hôte 1.
        self.assertEquals(self.hosts[1].idhost, db_correvent.cause.idsupitem)
        # ... dans l'état indiqué.
        self.assertEquals(
            u'DOWN',
            tables.StateName.value_to_statename(
                db_correvent.cause.current_state)
        )
        # ... ayant 2 événements bruts rattachés (cause hôte 1 + hôte 2).
        self.assertEquals(
            [u'Host 1', u'Host 2'],
            sorted([ev.supitem.name for ev in db_correvent.events])
        )

        # 3. Pas de nouvel agrégat, mais un nouvel événement brut (hôte 4)
        #    ajouté à l'agrégat de l'étape 2.
        res, idcorrevent3 = yield self.handle_alert(
            self.hosts[4], 'UNREACHABLE', preds=[idcorrevent2])
        print "Finished step 3\n"
        # Aucune erreur n'a été levée.
        self.assertEquals(res, None)
        self.assertEquals(idcorrevent3, None) # ajouté dans l'agrégat 2.
        # Toujours un seul agrégat.
        db_correvent = DBSession.query(tables.CorrEvent).one()
        self.assertEquals(db_correvent.idcorrevent, idcorrevent2)
        # ... dont la cause est l'hôte 1.
        self.assertEquals(self.hosts[1].idhost, db_correvent.cause.idsupitem)
        # ... dans l'état indiqué.
        self.assertEquals(
            u'DOWN',
            tables.StateName.value_to_statename(
                db_correvent.cause.current_state)
        )
        # ... ayant 3 événements bruts.
        self.assertEquals(
            [u'Host 1', u'Host 2', u'Host 4'],
            sorted([ev.supitem.name for ev in db_correvent.events])
        )

        # 4. Pas de nouvel agrégat, mais un nouvel événement brut (hôte 3)
        #    ajouté à l'agrégat de l'étape 2.
        res, idcorrevent4 = yield self.handle_alert(
            self.hosts[3], 'UNREACHABLE', preds=[idcorrevent2])
        print "Finished step 4\n"
        # Aucune erreur n'a été levée.
        self.assertEquals(res, None)
        self.assertEquals(idcorrevent4, None) # ajouté dans l'agrégat 2.
        # On a 4 événements bruts en base.
        self.assertEquals(4, DBSession.query(tables.Event).count())
        # On a toujours un seul agrégat.
        db_correvent = DBSession.query(tables.CorrEvent).one()
        self.assertEquals(db_correvent.idcorrevent, idcorrevent2)
        # ... dont la cause est l'hôte 1.
        self.assertEquals(self.hosts[1].idhost, db_correvent.cause.idsupitem)
        # ... dans l'état indiqué.
        self.assertEquals(
            u'DOWN',
            tables.StateName.value_to_statename(
                db_correvent.cause.current_state)
        )
        # ... ayant 3 événements bruts.
        self.assertEquals(
            [u'Host 1', u'Host 2', u'Host 3', u'Host 4'],
            sorted([ev.supitem.name for ev in db_correvent.events])
        )

        # 5. L'agrégat de l'étape 2 doit avoir été désagrégé
        #    en 3 agrégats, l'un signalant que l'hôte 1 est UP,
        #    un autre indiquant que l'hôte 2 est UNREACHABLE,
        #    le dernier donnant les hôtes 4 et 3 UNREACHABLE.
        res, idcorrevent5 = yield self.handle_alert(self.hosts[1], 'UP')
        print "Finished step 5\n"
        # Aucune erreur n'a été levée.
        self.assertNotEquals(res, None)
        # Désagrégé à partir de l'agrégat 2.
        self.assertEquals(idcorrevent5, idcorrevent2)
        # On a 4 événements bruts et 3 agrégats en base.
        print "events"
        self.assertEquals(4, DBSession.query(tables.Event).count())
        db_correvents = DBSession.query(tables.CorrEvent).all()
        print "correvents"
        self.assertEquals(3, len(db_correvents))
        db_correvents.sort(key=lambda x: x.cause.supitem.name)
        # L'un porte sur l'hôte 1 qui doit être dans l'état "UP"
        # et ne contient qu'un seul événement brut sur host 1.
        self.assertEquals(self.hosts[1].idhost,
                          db_correvents[0].cause.idsupitem)
        self.assertEquals(
            u'UP',
            tables.StateName.value_to_statename(
                db_correvents[0].cause.current_state)
        )
        self.assertEquals(
            [u'Host 1'],
            sorted([ev.supitem.name for ev in db_correvents[0].events])
        )
        # Le second porte sur l'hôte 2, qui se trouve toujours dans
        # l'état "UNREACHABLE" et n'a qu'un seul événement brut (host 2).
        self.assertEquals(self.hosts[2].idhost,
                         db_correvents[1].cause.idsupitem)
        self.assertEquals(
            u'UNREACHABLE',
            tables.StateName.value_to_statename(
                db_correvents[1].cause.current_state)
        )
        self.assertEquals(
            [u'Host 2'],
            sorted([ev.supitem.name for ev in db_correvents[1].events])
        )
        # Le dernier des agrégats porte sur l'hôte 4
        # qui se trouve dans l'état UNREACHABLE et
        # contient 2 événements bruts (host 4 et host 3).
        self.assertEquals(self.hosts[4].idhost,
                          db_correvents[2].cause.idsupitem)
        self.assertEquals(
            u'UNREACHABLE',
            tables.StateName.value_to_statename(
                db_correvents[2].cause.current_state)
        )
        self.assertEquals(
            [u'Host 3', u'Host 4'],
            sorted([ev.supitem.name for ev in db_correvents[2].events])
        )


    @deferred(timeout=60)
    @defer.inlineCallbacks
    def test_pseudo_triangle(self):
        """
        Désagrégation avec événements en pseudo-triangle.

        Lorsque 2 hôtes tombent et qu'un 3ème hôte dépendant des 2 premiers
        au sens de la topologie devient indisponible, l'événement brut sur
        ce 3ème hôte doit être agrégé dans les agrégats des 2 premiers.

        Lorsque le 1er hôte remonte, un agrégat séparé doit être créé pour
        sa dépendance. L'agrégat n'est pas affecté lorsque le 2nd hôte
        redevient opérationnel. On finit donc avec 3 agrégats actifs.
        """
        # Ajout des dépendances topologiques :
        # - Host 3 dépend de Host 1 et Host 2 (triangle).
        dep_group = functions.add_dependency_group(
                        self.hosts[3], None, u'topology', u'|')
        functions.add_dependency(dep_group, self.hosts[1], 1)
        functions.add_dependency(dep_group, self.hosts[2], 2)

        # Simule la chute des hôtes "Host 1" et "Host 2",
        # puis l'indisponibilité de l'hôte "Host 3"
        # qui dépend des 2 autres topologiquement.
        res, idcorrevent1 = yield self.handle_alert(self.hosts[1], 'DOWN')
        self.assertNotEquals(res, None)
        res, idcorrevent2 = yield self.handle_alert(self.hosts[2], 'DOWN')
        self.assertNotEquals(res, None)
        res, idcorrevent3 = yield self.handle_alert(
            self.hosts[3],
            'UNREACHABLE',
            preds=[idcorrevent1, idcorrevent2],
        )
        self.assertEquals(res, None) # Pas de nouvel agrégat créé.
        print "Finished step 1\n"
        self.assertNotEquals(idcorrevent1, None)
        self.assertNotEquals(idcorrevent2, None)
        self.assertEquals(idcorrevent3, None)
        # On s'attend à trouver 3 événements bruts et 2 agrégats.
        self.assertEquals(3, DBSession.query(tables.Event).count())
        self.assertEquals(2, DBSession.query(tables.CorrEvent).count())
        # L'événement brut sur "Host 3" a été agrégé dans les 2 autres.
        event3 = DBSession.query(tables.Event).filter(
            tables.Event.idsupitem == self.hosts[3].idhost).one()
        # ... donc il appartient à l'agrégat de l'hôte 1.
        correvent = DBSession.query(tables.CorrEvent).get(idcorrevent1)
        self.assertTrue(event3 in correvent.events)
        # ... ainsi qu'à celui de l'hôte 2.
        correvent = DBSession.query(tables.CorrEvent).get(idcorrevent2)
        self.assertTrue(event3 in correvent.events)

        # Simule la remontée de "Host 1" :
        # l'événement brut concernant "Host 3" doit être retiré
        # des agrégats de l'hôte 1 et de l'hôte 2.
        # Un nouvel agrégat doit avoir été créé pour l'accueillir.
        res, _idcorrevent = yield self.handle_alert(self.hosts[1], 'UP')
        print "Finished step 2\n"
        self.assertNotEquals(res, None)
        # On s'attend à trouver 3 événements bruts et 3 agrégats.
        self.assertEquals(3, DBSession.query(tables.Event).count())
        self.assertEquals(3, DBSession.query(tables.CorrEvent).count())
        # L'événement brut sur "Host 3" n'est plus l'agrégat de l'hôte 1.
        correvent = DBSession.query(tables.CorrEvent).get(idcorrevent1)
        self.assertFalse(event3 in correvent.events)
        # ... ni dans celui de l'hôte 2.
        correvent = DBSession.query(tables.CorrEvent).get(idcorrevent2)
        self.assertFalse(event3 in correvent.events)
        # ... en revanche, il dispose de son propre agrégat.
        correvent = DBSession.query(
                tables.CorrEvent
            ).join(
                (tables.Event, tables.Event.idevent ==
                    tables.CorrEvent.idcause),
            ).filter(tables.Event.idsupitem == self.hosts[3].idhost
            ).one()

        # "Host 2" remonte : rien ne change.
        res, _idcorrevent = yield self.handle_alert(self.hosts[2], 'UP')
        print "Finished step 3\n"
        self.assertNotEquals(res, None)
        # On s'attend à trouver 3 événements bruts et 3 agrégats.
        self.assertEquals(3, DBSession.query(tables.Event).count())
        self.assertEquals(3, DBSession.query(tables.CorrEvent).count())
        # L'événement brut sur "Host 3" n'est plus l'agrégat de l'hôte 1.
        correvent = DBSession.query(tables.CorrEvent).get(idcorrevent1)
        self.assertFalse(event3 in correvent.events)
        # ... ni dans celui de l'hôte 2.
        correvent = DBSession.query(tables.CorrEvent).get(idcorrevent2)
        self.assertFalse(event3 in correvent.events)
        # ... en revanche, il dispose de son propre agrégat.
        correvent = DBSession.query(
                tables.CorrEvent
            ).join(
                (tables.Event, tables.Event.idevent ==
                    tables.CorrEvent.idcause),
            ).filter(tables.Event.idsupitem == self.hosts[3].idhost
            ).one()

    @deferred(timeout=60)
    @defer.inlineCallbacks
    def test_desaggregate2(self):
        """Désagrégation d'une vraie alerte d'un agrégat OK/UP (#1027)."""
        # Ajout des dépendances topologiques :
        # - Host 3 dépend de Host 1.
        dep_group = functions.add_dependency_group(
                        self.hosts[3], None, u'topology', u'|')
        functions.add_dependency(dep_group, self.hosts[1], 1)

        # Host 1 est dans l'état UP (pour cela, on génère déjà une alerte
        # DOWN qu'on fait ensuite repasser UP).
        res, idcorrevent1 = yield self.handle_alert(self.hosts[1], 'DOWN')
        self.assertNotEquals(res, None)
        res, idcorrevent1 = yield self.handle_alert(self.hosts[1], 'UP')
        self.assertNotEquals(res, None)
        # Host 3 est DOWN.
        event = functions.add_event(self.hosts[3], 'DOWN', 'DOWN')
        functions.add_host_state(self.hosts[3], 'DOWN')
        # Par erreur, l'alerte sur Host 3 s'est retrouvée masquée
        # par celle sur Host 1 (#1027).
        correvent = DBSession.query(tables.CorrEvent).filter(
            tables.CorrEvent.idcorrevent == idcorrevent1).one()
        correvent.events.append(event)
        DBSession.flush()

        # On reçoit une notification sur Host 3 qui confirme le problème.
        # L'événement doit être retiré de l'agrégat d'Host 1 et placé
        # dans un nouvel agrégat.
        res, idcorrevent2 = yield self.handle_alert(self.hosts[3], 'DOWN')
        self.assertNotEquals(res, None)
        self.assertNotEquals(idcorrevent2, idcorrevent1)

        # On vérifie que les événements sont bien dans les bons agrégats.
        correvent = DBSession.query(tables.CorrEvent).filter(
            tables.CorrEvent.idcorrevent == idcorrevent1).one()
        self.assertEquals(len(correvent.events), 1)
        correvent2 = DBSession.query(tables.CorrEvent).filter(
            tables.CorrEvent.idcorrevent == idcorrevent2).one()
        self.assertEquals(
            [event.idevent],
            [e.idevent for e in correvent2.events]
        )