/******************************************************************************* * This file is part of OpenNMS(R). * * Copyright (C) 2010-2011 The OpenNMS Group, Inc. * OpenNMS(R) is Copyright (C) 1999-2011 The OpenNMS Group, Inc. * * OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc. * * OpenNMS(R) is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * * OpenNMS(R) is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenNMS(R). If not, see: * http://www.gnu.org/licenses/ * * For more information contact: * OpenNMS(R) Licensing <license@opennms.org> * http://www.opennms.org/ * http://www.opennms.com/ *******************************************************************************/ package org.opennms.netmgt.alarmd; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.CountDownLatch; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.opennms.core.soa.ServiceRegistry; import org.opennms.core.test.OpenNMSJUnit4ClassRunner; import org.opennms.core.utils.BeanUtils; import org.opennms.netmgt.alarmd.api.NorthboundAlarm; import org.opennms.netmgt.alarmd.api.Northbounder; import org.opennms.netmgt.alarmd.api.NorthbounderException; import org.opennms.netmgt.dao.NodeDao; import org.opennms.netmgt.dao.db.JUnitConfigurationEnvironment; import org.opennms.netmgt.dao.db.JUnitTemporaryDatabase; import org.opennms.netmgt.dao.db.TemporaryDatabaseAware; import org.opennms.netmgt.mock.MockDatabase; import org.opennms.netmgt.mock.MockEventIpcManager; import org.opennms.netmgt.mock.MockEventUtil; import org.opennms.netmgt.mock.MockNetwork; import org.opennms.netmgt.mock.MockNode; import org.opennms.netmgt.model.OnmsNode; import org.opennms.netmgt.model.OnmsSeverity; import org.opennms.netmgt.model.events.EventBuilder; import org.opennms.netmgt.xml.event.AlarmData; import org.opennms.netmgt.xml.event.Event; import org.opennms.netmgt.xml.event.UpdateField; import org.opennms.test.ThrowableAnticipator; import org.opennms.test.mock.MockUtil; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.ResultSetExtractor; import org.springframework.jdbc.core.RowCallbackHandler; import org.springframework.test.context.ContextConfiguration; import org.springframework.util.StringUtils; @RunWith(OpenNMSJUnit4ClassRunner.class) @ContextConfiguration(locations={ "classpath:/META-INF/opennms/applicationContext-soa.xml", "classpath:/META-INF/opennms/applicationContext-dao.xml", "classpath*:/META-INF/opennms/component-dao.xml", "classpath:/META-INF/opennms/applicationContext-daemon.xml", "classpath:/META-INF/opennms/mockEventIpcManager.xml", "classpath:/META-INF/opennms/applicationContext-alarmd.xml", "classpath:/META-INF/opennms/applicationContext-minimal-conf.xml" }) @JUnitConfigurationEnvironment @JUnitTemporaryDatabase(dirtiesContext=false,tempDbClass=MockDatabase.class) public class AlarmdTest implements TemporaryDatabaseAware<MockDatabase>, InitializingBean { public class MockNorthbounder implements Northbounder { private boolean m_startCalled = false; private List<NorthboundAlarm> m_alarms = new ArrayList<NorthboundAlarm>(); @Override public void start() throws NorthbounderException { m_startCalled = true; } @Override public void onAlarm(final NorthboundAlarm alarm) throws NorthbounderException { m_alarms.add(alarm); } @Override public void stop() throws NorthbounderException { } public boolean isInitialized() { return m_startCalled; } public List<NorthboundAlarm> getAlarms() { return m_alarms; } } private MockNetwork m_mockNetwork = new MockNetwork(); @Autowired private Alarmd m_alarmd; @Autowired private NodeDao m_nodeDao; @Autowired private JdbcTemplate m_jdbcTemplate; @Autowired private MockEventIpcManager m_eventdIpcMgr; @Autowired private ServiceRegistry m_registry; private MockDatabase m_database; private MockNorthbounder m_northbounder; @Override public void setTemporaryDatabase(final MockDatabase database) { m_database = database; } @Override public void afterPropertiesSet() throws Exception { BeanUtils.assertAutowiring(this); } @Before public void setUp() throws Exception { m_mockNetwork.createStandardNetwork(); m_eventdIpcMgr.setEventWriter(m_database); // Insert some empty nodes to avoid foreign-key violations on subsequent events/alarms final OnmsNode node = new OnmsNode(); node.setId(1); node.setLabel("node1"); m_nodeDao.save(node); m_northbounder = new MockNorthbounder(); m_registry.register(m_northbounder, Northbounder.class); } @After public void tearDown() throws Exception { m_alarmd.destroy(); } @Test @JUnitTemporaryDatabase(tempDbClass=MockDatabase.class) // Relies on specific IDs so we need a fresh database public void testPersistAlarm() throws Exception { final MockNode node = m_mockNetwork.getNode(1); //there should be no alarms in the alarms table assertEquals(0, m_jdbcTemplate.queryForInt("select count(*) from alarms")); //this should be the first occurrence of this alarm //there should be 1 alarm now sendNodeDownEvent("%nodeid%", node); Thread.sleep(1000); assertEquals(1, m_jdbcTemplate.queryForInt("select count(*) from alarms")); //this should be the second occurrence and shouldn't create another row //there should still be only 1 alarm sendNodeDownEvent("%nodeid%", node); Thread.sleep(1000); assertEquals(1, m_jdbcTemplate.queryForInt("select count(*) from alarms")); //this should be a new alarm because of the new key //there should now be 2 alarms sendNodeDownEvent("DontReduceThis", node); Thread.sleep(1000); assertEquals(2, m_jdbcTemplate.queryForInt("select count(*) from alarms")); MockUtil.println("Going for the print of the counter column"); m_jdbcTemplate.query("select reductionKey, sum(counter) from alarms group by reductionKey", new RowCallbackHandler() { public void processRow(ResultSet rs) throws SQLException { MockUtil.println("count for reductionKey: " + rs.getString(1) + " is: " + rs.getObject(2)); } }); } @Test @JUnitTemporaryDatabase(tempDbClass=MockDatabase.class) public void testPersistManyAlarmsAtOnce() throws InterruptedException { int numberOfAlarmsToReduce = 10; //there should be no alarms in the alarms table assertEquals(0, m_jdbcTemplate.queryForInt("select count(*) from alarms")); final String reductionKey = "countThese"; final MockNode node = m_mockNetwork.getNode(1); final long millis = System.currentTimeMillis()+2500; final CountDownLatch signal = new CountDownLatch(numberOfAlarmsToReduce); for (int i=1; i<= numberOfAlarmsToReduce; i++) { MockUtil.println("Creating Runnable: "+i+" of "+numberOfAlarmsToReduce+" events to reduce."); class EventRunner implements Runnable { public void run() { try { while (System.currentTimeMillis() < millis) { try { Thread.sleep(10); } catch (InterruptedException e) { MockUtil.println(e.getMessage()); } } sendNodeDownEvent(reductionKey, node); } catch (Throwable t) { t.printStackTrace(); } finally { signal.countDown(); } } } Runnable r = new EventRunner(); Thread p = new Thread(r); p.start(); } signal.await(); //this should be the first occurrence of this alarm //there should be 1 alarm now int rowCount = m_jdbcTemplate.queryForInt("select count(*) from alarms"); Integer counterColumn = m_jdbcTemplate.queryForInt("select counter from alarms where reductionKey = ?", new Object[] { reductionKey }); MockUtil.println("rowcCount is: "+rowCount+", expected 1."); MockUtil.println("counterColumn is: "+counterColumn+", expected "+numberOfAlarmsToReduce); assertEquals(1, rowCount); if (numberOfAlarmsToReduce != counterColumn) { final List<Integer> reducedEvents = new ArrayList<Integer>(); m_jdbcTemplate.query("select eventid from events where alarmID is not null", new RowCallbackHandler() { public void processRow(ResultSet rs) throws SQLException { reducedEvents.add(rs.getInt(1)); } }); Collections.sort(reducedEvents); final List<Integer> nonReducedEvents = new ArrayList<Integer>(); m_jdbcTemplate.query("select eventid from events where alarmID is null", new RowCallbackHandler() { public void processRow(ResultSet rs) throws SQLException { nonReducedEvents.add(rs.getInt(1)); } }); Collections.sort(nonReducedEvents); fail("number of alarms to reduce (" + numberOfAlarmsToReduce + ") were not reduced into a single alarm (instead the counter column reads " + counterColumn + "); " + "events that were reduced: " + StringUtils.collectionToCommaDelimitedString(reducedEvents) + "; events that were not reduced: " + StringUtils.collectionToCommaDelimitedString(nonReducedEvents)); } Integer alarmId = m_jdbcTemplate.queryForInt("select alarmId from alarms where reductionKey = ?", new Object[] { reductionKey }); rowCount = m_jdbcTemplate.queryForInt("select count(*) from events where alarmid = ?", new Object[] { alarmId }); MockUtil.println(String.valueOf(rowCount) + " of events with alarmid: "+alarmId); // assertEquals(numberOfAlarmsToReduce, rowCount); rowCount = m_jdbcTemplate.queryForInt("select count(*) from events where alarmid is null"); MockUtil.println(String.valueOf(rowCount) + " of events with null alarmid"); assertEquals(0, rowCount); } @Test public void testNullEvent() throws Exception { ThrowableAnticipator ta = new ThrowableAnticipator(); ta.anticipate(new IllegalArgumentException("event argument must not be null")); try { m_alarmd.getPersister().persist(null); } catch (Throwable t) { ta.throwableReceived(t); } ta.verifyAnticipated(); } @Test @JUnitTemporaryDatabase(tempDbClass=MockDatabase.class) // Relies on specific IDs so we need a fresh database public void testNorthbounder() throws Exception { assertTrue(m_northbounder.isInitialized()); assertTrue(m_northbounder.getAlarms().isEmpty()); final EventBuilder bldr = new EventBuilder("testNoLogmsg", "AlarmdTest"); bldr.setAlarmData(new AlarmData()); bldr.setLogMessage("This is a test."); final Event event = bldr.getEvent(); event.setDbid(17); MockNode node = m_mockNetwork.getNode(1); sendNodeDownEvent("%nodeid%", node); final List<NorthboundAlarm> alarms = m_northbounder.getAlarms(); assertTrue(alarms.size() > 0); } @Test public void testNoLogmsg() throws Exception { EventBuilder bldr = new EventBuilder("testNoLogmsg", "AlarmdTest"); bldr.setAlarmData(new AlarmData()); ThrowableAnticipator ta = new ThrowableAnticipator(); ta.anticipate(new IllegalArgumentException("Incoming event has an illegal dbid (0), aborting")); try { m_alarmd.getPersister().persist(bldr.getEvent()); } catch (Throwable t) { ta.throwableReceived(t); } ta.verifyAnticipated(); } @Test public void testNoAlarmData() throws Exception { EventBuilder bldr = new EventBuilder("testNoAlarmData", "AlarmdTest"); bldr.setLogMessage(null); m_alarmd.getPersister().persist(bldr.getEvent()); } @Test public void testNoDbid() throws Exception { EventBuilder bldr = new EventBuilder("testNoDbid", "AlarmdTest"); bldr.setLogMessage(null); bldr.setAlarmData(new AlarmData()); ThrowableAnticipator ta = new ThrowableAnticipator(); ta.anticipate(new IllegalArgumentException("Incoming event has an illegal dbid (0), aborting")); try { m_alarmd.getPersister().persist(bldr.getEvent()); } catch (Throwable t) { ta.throwableReceived(t); } ta.verifyAnticipated(); } @Test @JUnitTemporaryDatabase(tempDbClass=MockDatabase.class) public void changeFields() throws InterruptedException, SQLException { assertEquals(0, m_jdbcTemplate.queryForInt("select count(*) from alarms")); String reductionKey = "testUpdateField"; int alarmCount = m_jdbcTemplate.queryForInt("select count(*) from alarms"); assertEquals(0, alarmCount); MockNode node1 = m_mockNetwork.getNode(1); //Verify we have the default alarm sendNodeDownEvent(reductionKey, node1); int severity = m_jdbcTemplate.queryForInt("select severity from alarms a where a.reductionKey = ?", reductionKey); assertEquals(OnmsSeverity.MAJOR, OnmsSeverity.get(severity)); //Store the original logmsg from the original alarm (we are about to test changing it with subsequent alarm reduction) String defaultLogMsg = m_jdbcTemplate.query("select logmsg from alarms", new ResultSetExtractor<String>() { @Override public String extractData(ResultSet results) throws SQLException, DataAccessException { results.next(); int row = results.getRow(); boolean isLast = results.isLast(); boolean isFirst = results.isFirst(); if (row != 1 && !isLast && !isFirst) { throw new SQLException("Row count is not = 1. There should only be one row returned from the query: \n"+ results.getStatement()); } return results.getString(1); } }); assertTrue("The logmsg column should not be null", defaultLogMsg != null); //Duplicate the alarm but change the severity and verify the change sendNodeDownEventWithUpdateFieldSeverity(reductionKey, node1, OnmsSeverity.CRITICAL); severity = m_jdbcTemplate.queryForInt("select severity from alarms"); assertEquals("Severity should now be Critical", OnmsSeverity.CRITICAL, OnmsSeverity.get(severity)); //Duplicate the alarm but don't force the change of severity sendNodeDownEvent(reductionKey, node1); severity = m_jdbcTemplate.queryForInt("select severity from alarms"); assertEquals("Severity should still be Critical", OnmsSeverity.CRITICAL, OnmsSeverity.get(severity)); //Duplicate the alarm and change the logmsg sendNodeDownEventChangeLogMsg(reductionKey, node1, "new logMsg"); String newLogMsg = m_jdbcTemplate.query("select logmsg from alarms", new ResultSetExtractor<String>() { @Override public String extractData(ResultSet results) throws SQLException, DataAccessException { results.next(); return results.getString(1); } }); assertEquals("new logMsg", newLogMsg); assertTrue(!newLogMsg.equals(defaultLogMsg)); //Duplicate the alarm but force logmsg to not change (lggmsg field is updated by default) sendNodeDownEventDontChangeLogMsg(reductionKey, node1, "newer logMsg"); newLogMsg = m_jdbcTemplate.query("select logmsg from alarms", new ResultSetExtractor<String>() { @Override public String extractData(ResultSet results) throws SQLException, DataAccessException { results.next(); return results.getString(1); } }); assertTrue("The logMsg should not have changed.", !"newer logMsg".equals(newLogMsg)); assertEquals("The logMsg should still be equal to the previous update.", "new logMsg", newLogMsg); //Duplicate the alarm with the default configuration and verify the logmsg has changed (as is the default behavior //for this field) sendNodeDownEvent(reductionKey, node1); newLogMsg = m_jdbcTemplate.query("select logmsg from alarms", new ResultSetExtractor<String>() { @Override public String extractData(ResultSet results) throws SQLException, DataAccessException { results.next(); return results.getString(1); } }); assertTrue("The logMsg should have changed.", !"new logMsg".equals(newLogMsg)); assertEquals("The logMsg should new be the default logMsg.", newLogMsg, defaultLogMsg); } //Supporting method for test private void sendNodeDownEventDontChangeLogMsg(String reductionKey, MockNode node, String logMsg) { EventBuilder event = MockEventUtil.createNodeDownEventBuilder("Test", node); if (reductionKey != null) { AlarmData data = new AlarmData(); data.setAlarmType(1); data.setReductionKey(reductionKey); List<UpdateField> fields = new ArrayList<UpdateField>(); UpdateField field = new UpdateField(); field.setFieldName("logMsg"); field.setUpdateOnReduction(Boolean.FALSE); fields.add(field); data.setUpdateField(fields); event.setAlarmData(data); } else { event.setAlarmData(null); } event.setLogDest("logndisplay"); event.setLogMessage(logMsg); m_eventdIpcMgr.sendNow(event.getEvent()); } private void sendNodeDownEventChangeLogMsg(String reductionKey, MockNode node, String logMsg) { EventBuilder event = MockEventUtil.createNodeDownEventBuilder("Test", node); if (reductionKey != null) { AlarmData data = new AlarmData(); data.setAlarmType(1); data.setReductionKey(reductionKey); List<UpdateField> fields = new ArrayList<UpdateField>(); UpdateField field = new UpdateField(); field.setFieldName("logMsg"); field.setUpdateOnReduction(Boolean.TRUE); fields.add(field); data.setUpdateField(fields); event.setAlarmData(data); } else { event.setAlarmData(null); } event.setLogDest("logndisplay"); event.setLogMessage(logMsg); m_eventdIpcMgr.sendNow(event.getEvent()); } private void sendNodeDownEventWithUpdateFieldSeverity(String reductionKey, MockNode node, OnmsSeverity severity) throws SQLException { EventBuilder event = MockEventUtil.createNodeDownEventBuilder("Test", node); if (reductionKey != null) { AlarmData data = new AlarmData(); data.setAlarmType(1); data.setReductionKey(reductionKey); List<UpdateField> fields = new ArrayList<UpdateField>(); UpdateField field = new UpdateField(); field.setFieldName("Severity"); field.setUpdateOnReduction(Boolean.TRUE); fields.add(field); data.setUpdateField(fields); event.setAlarmData(data); } else { event.setAlarmData(null); } event.setLogDest("logndisplay"); event.setLogMessage("testing"); event.setSeverity(severity.getLabel()); m_eventdIpcMgr.sendNow(event.getEvent()); } private void sendNodeDownEvent(String reductionKey, MockNode node) throws SQLException { EventBuilder event = MockEventUtil.createNodeDownEventBuilder("Test", node); if (reductionKey != null) { AlarmData data = new AlarmData(); data.setAlarmType(1); data.setReductionKey(reductionKey); event.setAlarmData(data); } else { event.setAlarmData(null); } event.setLogDest("logndisplay"); event.setLogMessage("testing"); m_eventdIpcMgr.sendNow(event.getEvent()); } }