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)
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)
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)
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)
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())
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)
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))
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] )