/*******************************************************************************
* This file is part of OpenNMS(R).
*
* Copyright (C) 2006-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.discovery;
import java.io.IOException;
import java.net.InetAddress;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.locks.Lock;
import org.exolab.castor.xml.MarshalException;
import org.exolab.castor.xml.ValidationException;
import org.opennms.core.utils.DBUtils;
import org.opennms.core.utils.InetAddressUtils;
import org.opennms.netmgt.EventConstants;
import org.opennms.netmgt.config.DataSourceFactory;
import org.opennms.netmgt.config.DiscoveryConfigFactory;
import org.opennms.netmgt.daemon.AbstractServiceDaemon;
import org.opennms.netmgt.eventd.EventIpcManagerFactory;
import org.opennms.netmgt.icmp.Pinger;
import org.opennms.netmgt.model.discovery.IPPollAddress;
import org.opennms.netmgt.model.events.EventBuilder;
import org.opennms.netmgt.model.events.EventForwarder;
import org.opennms.netmgt.model.events.annotations.EventHandler;
import org.opennms.netmgt.model.events.annotations.EventListener;
import org.opennms.netmgt.xml.event.Event;
import org.opennms.netmgt.xml.event.Parm;
import org.springframework.util.Assert;
/**
* This class is the main interface to the OpenNMS discovery service. The class
* implements the <em>singleton</em> design pattern, in that there is only one
* instance in any given virtual machine. The service delays the reading of
* configuration information until the service is started.
*
* @author <a href="mailto:weave@oculan.com">Brian Weaver </a>
* @author <a href="http://www.opennms.org/">OpenNMS.org </a>
*/
@EventListener(name="OpenNMS.Discovery")
public class Discovery extends AbstractServiceDaemon {
/**
* The callback that sends newSuspect events upon successful ping response.
*/
private static final DiscoveryPingResponseCallback cb = new DiscoveryPingResponseCallback();
private static final int PING_IDLE = 0;
private static final int PING_RUNNING = 1;
private static final int PING_FINISHING = 2;
/**
* The SQL query used to get the list of managed IP addresses from the database
*/
private static final String ALL_IP_ADDRS_SQL = "SELECT DISTINCT ipAddr FROM ipInterface WHERE isManaged <> 'D'";
/**
* a set of devices to skip discovery on
*/
private Set<String> m_alreadyDiscovered = Collections.synchronizedSet(new HashSet<String>());
private DiscoveryConfigFactory m_discoveryFactory;
private Timer m_timer;
private int m_xstatus = PING_IDLE;
private volatile EventForwarder m_eventForwarder;
private Pinger m_pinger;
/**
* <p>setEventForwarder</p>
*
* @param eventForwarder a {@link org.opennms.netmgt.model.events.EventForwarder} object.
*/
public void setEventForwarder(EventForwarder eventForwarder) {
m_eventForwarder = eventForwarder;
}
/**
* <p>setPinger</p>
*
* @param pinger a {@link JniPinger} object.
*/
public void setPinger(Pinger pinger) {
m_pinger = pinger;
}
/**
* <p>getEventForwarder</p>
*
* @return a {@link org.opennms.netmgt.model.events.EventForwarder} object.
*/
public EventForwarder getEventForwarder() {
return m_eventForwarder;
}
/**
* <p>setDiscoveryFactory</p>
*
* @param discoveryFactory a {@link org.opennms.netmgt.config.DiscoveryConfigFactory} object.
*/
public void setDiscoveryFactory(DiscoveryConfigFactory discoveryFactory) {
m_discoveryFactory = discoveryFactory;
}
/**
* <p>getDiscoveryFactory</p>
*
* @return a {@link org.opennms.netmgt.config.DiscoveryConfigFactory} object.
*/
public DiscoveryConfigFactory getDiscoveryFactory() {
return m_discoveryFactory;
}
/**
* Constructs a new discovery instance.
*/
public Discovery() {
super("OpenNMS.Discovery");
}
/**
* <p>onInit</p>
*
* @throws java.lang.IllegalStateException if any.
*/
protected void onInit() throws IllegalStateException {
Assert.state(m_eventForwarder != null, "must set the eventForwarder property");
try {
initializeConfiguration();
EventIpcManagerFactory.init();
} catch (Throwable e) {
log().debug("onInit: initialization failed: "+e, e);
throw new IllegalStateException("Could not initialize discovery configuration.", e);
}
}
private void initializeConfiguration() throws MarshalException, ValidationException, IOException {
DiscoveryConfigFactory.reload();
setDiscoveryFactory(DiscoveryConfigFactory.getInstance());
}
private void doPings() {
infof("starting ping sweep");
try {
initializeConfiguration();
} catch (Throwable e) {
log().error("doPings: could not re-init configuration, continuing with in memory configuration."+e, e);
}
m_xstatus = PING_RUNNING;
getDiscoveryFactory().getReadLock().lock();
try {
for (IPPollAddress pollAddress : getDiscoveryFactory().getConfiguredAddresses()) {
if (m_xstatus == PING_FINISHING || m_timer == null) {
m_xstatus = PING_IDLE;
return;
}
ping(pollAddress);
try {
Thread.sleep(getDiscoveryFactory().getIntraPacketDelay());
} catch (InterruptedException e) {
infof("interrupting discovery sweep");
break;
}
}
} finally {
getDiscoveryFactory().getReadLock().unlock();
}
infof("finished discovery sweep");
m_xstatus = PING_IDLE;
}
private void ping(IPPollAddress pollAddress) {
InetAddress address = pollAddress.getAddress();
if (address != null) {
if (!isAlreadyDiscovered(address)) {
try {
m_pinger.ping(address, pollAddress.getTimeout(), pollAddress.getRetries(), (short) 1, cb);
} catch (Throwable e) {
debugf(e, "error pinging %s", address.getAddress());
}
}
}
}
private boolean isAlreadyDiscovered(InetAddress address) {
if (m_alreadyDiscovered.contains(InetAddressUtils.str(address))) {
return true;
}
return false;
}
private void startTimer() {
if (m_timer != null) {
debugf("startTimer() called, but a previous timer exists; making sure it's cleaned up");
m_xstatus = PING_FINISHING;
m_timer.cancel();
}
debugf("scheduling new discovery timer");
m_timer = new Timer("Discovery.Pinger", true);
TimerTask task = new TimerTask() {
@Override
public void run() {
doPings();
}
};
final Lock readLock = getDiscoveryFactory().getReadLock();
readLock.lock();
try {
m_timer.scheduleAtFixedRate(task, getDiscoveryFactory().getInitialSleepTime(), getDiscoveryFactory().getRestartSleepTime());
} finally {
readLock.unlock();
}
}
private void stopTimer() {
if (m_timer != null) {
debugf("stopping existing timer");
m_xstatus = PING_FINISHING;
m_timer.cancel();
m_timer = null;
} else {
debugf("stopTimer() called, but there is no existing timer");
}
}
/**
* <p>onStart</p>
*/
protected void onStart() {
syncAlreadyDiscovered();
startTimer();
}
/**
* <p>onStop</p>
*/
protected void onStop() {
stopTimer();
}
/**
* <p>onPause</p>
*/
protected void onPause() {
stopTimer();
}
/**
* <p>onResume</p>
*/
protected void onResume() {
startTimer();
}
/**
* <p>syncAlreadyDiscovered</p>
*/
protected void syncAlreadyDiscovered() {
/**
* Make a new list with which we'll replace the existing one, that way
* if something goes wrong with the DB we won't lose whatever was already
* in there
*/
Set<String> newAlreadyDiscovered = Collections.synchronizedSet(new HashSet<String>());
Connection conn = null;
final DBUtils d = new DBUtils(getClass());
try {
conn = DataSourceFactory.getInstance().getConnection();
d.watch(conn);
PreparedStatement stmt = conn.prepareStatement(ALL_IP_ADDRS_SQL);
d.watch(stmt);
ResultSet rs = stmt.executeQuery();
d.watch(rs);
if (rs != null) {
while (rs.next()) {
newAlreadyDiscovered.add(rs.getString(1));
}
} else {
log().warn("Got null ResultSet from query for all IP addresses");
}
m_alreadyDiscovered = newAlreadyDiscovered;
} catch (SQLException sqle) {
log().warn("Caught SQLException while trying to query for all IP addresses: " + sqle.getMessage());
} finally {
d.cleanUp();
}
log().info("syncAlreadyDiscovered initialized list of managed IP addresses with " + m_alreadyDiscovered.size() + " members");
}
/**
* <p>handleDiscoveryConfigurationChanged</p>
*
* @param event a {@link org.opennms.netmgt.xml.event.Event} object.
*/
@EventHandler(uei=EventConstants.DISCOVERYCONFIG_CHANGED_EVENT_UEI)
public void handleDiscoveryConfigurationChanged(Event event) {
log().info("handleDiscoveryConfigurationChanged: handling message that a change to configuration happened...");
reloadAndReStart();
}
private void reloadAndReStart() {
EventBuilder ebldr = null;
try {
initializeConfiguration();
ebldr = new EventBuilder(EventConstants.RELOAD_DAEMON_CONFIG_SUCCESSFUL_UEI, getName());
ebldr.addParam(EventConstants.PARM_DAEMON_NAME, "Discovery");
this.stop();
this.start();
} catch (MarshalException e) {
fatalf(e, "Unable to initialize the discovery configuration factory");
ebldr = new EventBuilder(EventConstants.RELOAD_DAEMON_CONFIG_FAILED_UEI, getName());
ebldr.addParam(EventConstants.PARM_DAEMON_NAME, "Discovery");
ebldr.addParam(EventConstants.PARM_REASON, e.getLocalizedMessage().substring(0, 128));
} catch (ValidationException e) {
fatalf(e, "Unable to initialize the discovery configuration factory");
ebldr = new EventBuilder(EventConstants.RELOAD_DAEMON_CONFIG_FAILED_UEI, getName());
ebldr.addParam(EventConstants.PARM_DAEMON_NAME, "Discovery");
ebldr.addParam(EventConstants.PARM_REASON, e.getLocalizedMessage().substring(0, 128));
} catch (IOException e) {
fatalf(e, "Unable to initialize the discovery configuration factory");
ebldr = new EventBuilder(EventConstants.RELOAD_DAEMON_CONFIG_FAILED_UEI, getName());
ebldr.addParam(EventConstants.PARM_DAEMON_NAME, "Discovery");
ebldr.addParam(EventConstants.PARM_REASON, e.getLocalizedMessage().substring(0, 128));
}
m_eventForwarder.sendNow(ebldr.getEvent());
}
/**
* <p>reloadDaemonConfig</p>
*
* @param e a {@link org.opennms.netmgt.xml.event.Event} object.
*/
@EventHandler(uei=EventConstants.RELOAD_DAEMON_CONFIG_UEI)
public void reloadDaemonConfig(Event e) {
log().info("reloadDaemonConfig: processing reload daemon event...");
if (isReloadConfigEventTarget(e)) {
reloadAndReStart();
}
log().info("reloadDaemonConfig: reload daemon event processed.");
}
private boolean isReloadConfigEventTarget(Event event) {
boolean isTarget = false;
final List<Parm> parmCollection = event.getParmCollection();
for (final Parm parm : parmCollection) {
if (EventConstants.PARM_DAEMON_NAME.equals(parm.getParmName()) && "Discovery".equalsIgnoreCase(parm.getValue().getContent())) {
isTarget = true;
break;
}
}
log().debug("isReloadConfigEventTarget: discovery was target of reload event: "+isTarget);
return isTarget;
}
/**
* <p>handleInterfaceDeleted</p>
*
* @param event a {@link org.opennms.netmgt.xml.event.Event} object.
*/
@EventHandler(uei=EventConstants.INTERFACE_DELETED_EVENT_UEI)
public void handleInterfaceDeleted(final Event event) {
if(event.getInterface() != null) {
// remove from known nodes
final String iface = event.getInterface();
m_alreadyDiscovered.remove(iface);
debugf("Removed %s from known node list", iface);
}
}
/**
* <p>handleDiscoveryResume</p>
*
* @param event a {@link org.opennms.netmgt.xml.event.Event} object.
*/
@EventHandler(uei=EventConstants.DISC_RESUME_EVENT_UEI)
public void handleDiscoveryResume(Event event) {
try {
resume();
} catch (IllegalStateException ex) {
}
}
/**
* <p>handleDiscoveryPause</p>
*
* @param event a {@link org.opennms.netmgt.xml.event.Event} object.
*/
@EventHandler(uei=EventConstants.DISC_PAUSE_EVENT_UEI)
public void handleDiscoveryPause(Event event) {
try {
pause();
} catch (IllegalStateException ex) {
}
}
/**
* <p>handleNodeGainedInterface</p>
*
* @param event a {@link org.opennms.netmgt.xml.event.Event} object.
*/
@EventHandler(uei=EventConstants.NODE_GAINED_INTERFACE_EVENT_UEI)
public void handleNodeGainedInterface(Event event) {
// add to known nodes
final String iface = event.getInterface();
m_alreadyDiscovered.add(iface);
debugf("Added %s as discovered", iface);
}
}