/*******************************************************************************
* 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.threshd;
/**
* @author mjamison
*
* TODO To change the template for this generated type comment go to
* Window - Preferences - Java - Code Style - Code Templates
*/
import java.io.File;
import java.lang.reflect.UndeclaredThrowableException;
import java.net.InetAddress;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.opennms.core.utils.DBUtils;
import org.opennms.core.utils.InetAddressUtils;
import org.opennms.core.utils.ParameterMap;
import org.opennms.core.utils.ThreadCategory;
import org.opennms.netmgt.config.DataSourceFactory;
import org.opennms.netmgt.config.ThresholdingConfigFactory;
import org.opennms.netmgt.config.threshd.Basethresholddef;
import org.opennms.netmgt.dao.support.RrdFileConstants;
import org.opennms.netmgt.model.events.EventProxy;
import org.opennms.netmgt.model.events.EventProxyException;
import org.opennms.netmgt.poller.NetworkInterface;
import org.opennms.netmgt.rrd.RrdException;
import org.opennms.netmgt.rrd.RrdUtils;
import org.opennms.netmgt.utils.IfLabel;
import org.opennms.netmgt.xml.event.Event;
import org.opennms.netmgt.xml.event.Events;
import org.opennms.netmgt.xml.event.Log;
import org.opennms.netmgt.xml.event.Parm;
import org.opennms.netmgt.xml.event.Value;
/**
* <P>
* The JMXThresholder class ...
* </P>
*
* @author <A HREF="mailto:mike@opennms.org">Mike Jamison </A>
* @author <A HREF="http://www.opennms.org/">OpenNMS </A>
* @deprecated No longer used - see ThresholdingVisitor
*/
public abstract class JMXThresholder implements ServiceThresholder {
/**
* SQL statement to retrieve interface's 'ipinterface' table information.
*/
private static final String SQL_GET_NODEID = "SELECT nodeid FROM ipinterface WHERE ipAddr=? AND ismanaged!='D'";
/**
* Default thresholding interval (in milliseconds).
*
*/
private static final int DEFAULT_INTERVAL = 300000; // 300s or 5m
/**
* Default age before which a data point is considered "out of date"
*/
private static final int DEFAULT_RANGE = 0; // 300s or 5m
/**
* Interface attribute key used to store the interface's node id
*/
static final String NODE_ID_KEY = "org.opennms.netmgt.collectd.JMXThresholder.NodeId";
/**
* Interface attribute key used to store the interface's node id
*/
static final String RRD_REPOSITORY_KEY = "org.opennms.netmgt.collectd.JMXThresholder.RrdRepository";
/**
* Interface attribute key used to store a map of node level ThresholdEntity
* objects keyed by datasource name.
*/
static final String NODE_THRESHOLD_MAP_KEY = "org.opennms.netmgt.collectd.JMXThresholder.NodeThresholdMap";
/**
* Interface attribute key used to store a map of interface level
* ThresholdEntity objects keyed by datasource name.
*/
static final String BASE_IF_THRESHOLD_MAP_KEY = "org.opennms.netmgt.collectd.JMXThresholder.IfThresholdMap";
/**
* We must maintain a map of interface level ThresholdEntity objects on a
* per interface basis in order to maintain separate exceeded counts and the
* like for each of a node's interfaces. This interface attribute key used
* to store a map of interface level ThresholdEntity object maps keyed by
* ifLabel. So it wil refer to a map of maps indexed by ifLabel.
*/
static final String ALL_IF_THRESHOLD_MAP_KEY = "org.opennms.netmgt.collectd.JMXThresholder.AllIfThresholdMap";
private String serviceName = null;
private boolean useFriendlyName = false;
/**
* <p>Setter for the field <code>serviceName</code>.</p>
*
* @param name a {@link java.lang.String} object.
*/
public void setServiceName(String name) {
serviceName = name;
}
/**
* <P>
* Returns the name of the service that the plug-in collects ("SNMP").
* </P>
*
* @return The service that the plug-in collects.
*/
public String serviceName() {
return serviceName.toUpperCase();
}
/**
* {@inheritDoc}
*
* <P>
* Initialize the service thresholder.
* </P>
* @exception RuntimeException
* Thrown if an unrecoverable error occurs that prevents the
* plug-in from functioning.
*/
public void initialize(Map<?,?> parameters) {
}
/**
* <p>reinitialize</p>
*/
public void reinitialize() {
//Nothing to do
}
/**
* Responsible for freeing up any resources held by the thresholder.
*/
public void release() {
// Nothing to release...
}
/**
* {@inheritDoc}
*
* Responsible for performing all necessary initialization for the specified
* interface in preparation for thresholding.
*/
public void initialize(ThresholdNetworkInterface iface, Map<?,?> parameters) {
// Get interface address from NetworkInterface
if (iface.getType() != NetworkInterface.TYPE_INET) {
throw new RuntimeException("Unsupported interface type, only TYPE_INET currently supported");
}
InetAddress ipAddr = (InetAddress) iface.getAddress();
// Retrieve the name of the thresholding group associated
// with this interface.
String groupName = ParameterMap.getKeyedString(parameters, "thresholding-group", serviceName);
// Get the threshold group's RRD repository path
String repository = null;
try {
repository = ThresholdingConfigFactory.getInstance().getRrdRepository(groupName);
} catch (IllegalArgumentException e) {
throw new RuntimeException("Thresholding group '" + groupName + "' does not exist.");
}
// Add RRD repository as an attribute of the interface for retrieval
// by the check() method.
iface.setAttribute(RRD_REPOSITORY_KEY, repository);
// Retrieve the collection of Threshold objects associated with
// the defined thresholding group and build two maps, one consisting
// of node level ThresholdEntity objects and another consisting of
// interface level ThresholdEntity objects both keyed by datasource
// name.
//
// Each ThresholdEntity can wrap one high Threshold and one low
// Threshold castor-generated object for a single datasource.
// If more than one high or more than one low threshold is defined
// for a single datasource a warning messages is generated. Only
// the first threshold in such a scenario will be used for thresholding.
//
Map<String, ThresholdEntity> nodeMap = new HashMap<String, ThresholdEntity>();
Map<String, ThresholdEntity> baseIfMap = new HashMap<String, ThresholdEntity>();
try {
for (Basethresholddef thresh : ThresholdingConfigFactory.getInstance().getThresholds(groupName)) {
// See if map entry already exists for this datasource
// If not, create a new one.
boolean newEntity = false;
ThresholdEntity thresholdEntity = null;
try {
BaseThresholdDefConfigWrapper wrapper=BaseThresholdDefConfigWrapper.getConfigWrapper(thresh);
if (wrapper.getDsType().equals("node")) {
thresholdEntity = nodeMap.get(wrapper.getDatasourceExpression());
} else if (wrapper.getDsType().equals("if")) {
thresholdEntity = baseIfMap.get(wrapper.getDatasourceExpression());
}
// Found entry?
if (thresholdEntity == null) {
// Nope, create a new one
newEntity = true;
thresholdEntity = new ThresholdEntity();
}
try {
thresholdEntity.addThreshold(wrapper);
} catch (IllegalStateException e) {
log().warn("Encountered duplicate " + thresh.getType() + " for datasource " + wrapper.getDatasourceExpression(), e);
}
// Add new entity to the map
if (newEntity) {
if (thresh.getDsType().equals("node")) {
nodeMap.put(wrapper.getDatasourceExpression(), thresholdEntity);
} else if (thresh.getDsType().equals("if")) {
baseIfMap.put(wrapper.getDatasourceExpression(), thresholdEntity);
}
}
} catch (ThresholdExpressionException e) {
log().warn("Could not parse threshold expression: "+e.getMessage(), e);
}
}
} catch (IllegalArgumentException e) {
throw new RuntimeException("Thresholding group '" + groupName + "' does not exist.");
}
// Add node and interface thresholding maps as attributes of the
// interface for retrieval by the check() method.
iface.setAttribute(NODE_THRESHOLD_MAP_KEY, nodeMap);
iface.setAttribute(BASE_IF_THRESHOLD_MAP_KEY, baseIfMap);
// Now create an empty map which will hold interface level
// ThresholdEntity objects for each of the node's interfaces.
// This map will be keyed by the interface's iflabel and will
// contain as a value a map of ThresholdEntity objects keyed
// by datasource name.
iface.setAttribute(ALL_IF_THRESHOLD_MAP_KEY, new HashMap<String,Map<String,ThresholdEntity>>());
final DBUtils d = new DBUtils(getClass());
// Get database connection in order to retrieve the nodeid and
// ifIndex from the database for this interface.
Connection dbConn = null;
try {
dbConn = DataSourceFactory.getInstance().getConnection();
d.watch(dbConn);
} catch (SQLException e) {
log().error("initialize: Failed getting connection to the database: " + e, e);
throw new UndeclaredThrowableException(e);
}
int nodeId = -1;
// All database calls wrapped in try/finally block so we make
// certain that the connection will be closed when we are
// finished.
try {
// Prepare & execute the SQL statement to get the 'nodeid',
// 'ifIndex' and 'isSnmpPrimary' fields from the ipInterface table.
PreparedStatement stmt = null;
final String hostAddress = InetAddressUtils.str(ipAddr);
try {
stmt = dbConn.prepareStatement(SQL_GET_NODEID);
d.watch(stmt);
stmt.setString(1, hostAddress); // interface address
ResultSet rs = stmt.executeQuery();
d.watch(rs);
if (rs.next()) {
nodeId = rs.getInt(1);
if (rs.wasNull()) {
nodeId = -1;
}
}
} catch (SQLException e) {
if (log().isDebugEnabled()) {
log().debug("initialize: SQL exception!!: " + e, e);
}
throw new RuntimeException("SQL exception while attempting to retrieve node id for interface " + hostAddress + ": " + e, e);
}
// RuntimeException is thrown if any of the following are true:
// - node id is invalid
// - primaryIfIndex is invalid
// - Interface is not the primary SNMP interface for the node
if (nodeId == -1) {
throw new RuntimeException("Unable to retrieve node id for interface " + hostAddress);
}
} finally {
d.cleanUp();
}
// Add nodeId as an attribute of the interface for retrieval
// by the check() method.
iface.setAttribute(NODE_ID_KEY, new Integer(nodeId));
// Debug
final String hostAddress = InetAddressUtils.str(ipAddr);
if (log().isDebugEnabled()) {
log().debug("initialize: dumping node thresholds defined for " + hostAddress + "/" + groupName + ":");
Iterator<ThresholdEntity> iter = nodeMap.values().iterator();
while (iter.hasNext()) {
log().debug(iter.next().toString());
}
log().debug("initialize: dumping interface thresholds defined for " + hostAddress + "/" + groupName + ":");
iter = baseIfMap.values().iterator();
while (iter.hasNext()) {
log().debug(iter.next().toString());
}
}
if (log().isDebugEnabled()) {
log().debug("initialize: initialization completed for " + hostAddress);
}
return;
}
/**
* {@inheritDoc}
*
* Responsible for releasing any resources associated with the specified
* interface.
*/
public void release(ThresholdNetworkInterface iface) {
// Nothing to release...
}
/**
* {@inheritDoc}
*
* Perform threshold checking.
*/
public int check(ThresholdNetworkInterface iface, EventProxy eproxy, Map<?,?> parameters) {
ThreadCategory log = log();
String dsDir = serviceName;
String port = ParameterMap.getKeyedString( parameters, "port", null);
String friendlyName = ParameterMap.getKeyedString( parameters, "friendly-name", port);
int range = ParameterMap.getKeyedInteger( parameters, "range", DEFAULT_RANGE);
if (useFriendlyName) {
dsDir = friendlyName;
}
InetAddress primary = (InetAddress) iface.getAddress();
// Get configuration parameters
String groupName = ParameterMap.getKeyedString(parameters, "thresholding-group", serviceName);
int interval = ParameterMap.getKeyedInteger(parameters, "interval", DEFAULT_INTERVAL);
final String hostAddress = InetAddressUtils.str(primary);
if (log.isDebugEnabled()) {
log.debug("check: service= " + serviceName.toUpperCase() + " address= " + hostAddress + " thresholding-group=" + groupName + " interval=" + interval + "mS range = " + range + " mS");
}
// RRD Repository attribute
String repository = iface.getAttribute(RRD_REPOSITORY_KEY);
if (log.isDebugEnabled()) {
log.debug("check: rrd repository=" + repository);
}
// Nodeid attribute
Integer nodeId = iface.getAttribute(NODE_ID_KEY);
// node and interface ThresholdEntity map attributes
Map<Object,ThresholdEntity> nodeMap = iface.getAttribute(NODE_THRESHOLD_MAP_KEY);
Map<String,ThresholdEntity> baseIfMap = iface.getAttribute(BASE_IF_THRESHOLD_MAP_KEY);
Map<String,Map<String,ThresholdEntity>> allIfMap = iface.getAttribute(ALL_IF_THRESHOLD_MAP_KEY);
// -----------------------------------------------------------
//
// Perform node-level threshold checking
//
// -----------------------------------------------------------
// Get File object representing the node directory
File nodeDirectory = new File(repository + File.separator + nodeId.toString() + File.separator + dsDir);
//if (!RrdFileConstants.isValidRRDNodeDir(nodeDirectory)) {
// log.error("Node directory for " + nodeDirectory + " does not exist or is not a valid RRD node directory.");
// log.error("Threshold checking failed for primary " + serviceName + " interface " + InetAddressUtils.str(primary));
//}
// Create empty Events object to hold any threshold
// events generated during the thresholding check...
Events events = new Events();
// Date stamp for all outgoing events
Date dateStamp = new Date();
try {
checkNodeDir(nodeDirectory, nodeId, primary, range, interval, dateStamp, nodeMap, events);
} catch (IllegalArgumentException e) {
log.info("check: Threshold checking failed for primary " + serviceName + " interface " + hostAddress + ": " + e, e);
return THRESHOLDING_FAILED;
}
// -----------------------------------------------------------
//
// Perform interface-level threshold checking
//
// -----------------------------------------------------------
// Iterate over node directory contents and call
// checkInterfaceDirectory() for any/all RRD interface
// directories.
File[] files = nodeDirectory.listFiles(RrdFileConstants.INTERFACE_DIRECTORY_FILTER);
if (files != null) {
for (int i = 0; i < files.length; i++) {
try {
// Found interface directory...
checkIfDir(files[i], nodeId, primary, interval, range, dateStamp, baseIfMap, allIfMap, events);
} catch (IllegalArgumentException e) {
log.info("check: Threshold checking failed for primary " + serviceName + " interface " + hostAddress + ": " + e, e);
return THRESHOLDING_FAILED;
}
}
}
// Send created events
if (events.getEventCount() > 0) {
try {
Log eventLog = new Log();
eventLog.setEvents(events);
eproxy.send(eventLog);
} catch (EventProxyException e) {
log.warn("check: Failed sending threshold events via event proxy: " + e, e);
return THRESHOLDING_FAILED;
}
}
// return the status of the threshold check
return THRESHOLDING_SUCCEEDED;
}
private Map<String, Double> getThresholdValues(File directory, int range, int interval, Collection<String> requiredDatasources) {
ThreadCategory log = log();
Map<String, Double> values=new HashMap<String,Double>();
for(String ds: requiredDatasources) {
File dsFile=new File(directory,ds+RrdUtils.getExtension());
Double thisValue=null;
if(dsFile.exists()) {
try {
if (range != 0) {
if (log.isDebugEnabled()) {
log.debug("checking values within " + range + " mS of last possible PDP");
}
thisValue = RrdUtils.fetchLastValueInRange(dsFile.getAbsolutePath(), ds, interval, range);
} else {
if (log.isDebugEnabled()) {
log.debug("checking value of last possible PDP only");
}
thisValue = RrdUtils.fetchLastValue(dsFile.getAbsolutePath(), ds, interval);
}
} catch (NumberFormatException e) {
log.warn("Unable to convert retrieved value for datasource '" + ds + "' to a double, skipping evaluation.");
} catch (RrdException e) {
log.info("An error occurred retriving the last value for datasource '" + ds + "': " + e, e);
}
}
if (thisValue == null || thisValue.isNaN()) {
return null;
}
values.put(ds,thisValue);
}
return values;
}
/**
* Performs threshold checking on an SNMP RRD node directory.
*
* @param directory
* RRD repository directory
* @param nodeId
* Node identifier
* @param primary
* Primary SNMP interface address
* @param interval
* Configured thresholding interval
* @param range
* Age before which PDP is considered out of date
* @param date
* Source for timestamp to be used for all generated events
* @param thresholdMap
* Map of node level ThresholdEntity objects keyed by datasource
* name.
* @param events
* Castor events object containing any events to be generated as
* a result of threshold checking.
*
* @throws IllegalArgumentException
* if path parameter is not a directory.
*/
private void checkNodeDir(File directory, Integer nodeId, InetAddress primary, int interval, int range, Date date, Map<Object,ThresholdEntity> thresholdMap, Events events) throws IllegalArgumentException {
ThreadCategory log = log();
// Sanity Check
if (directory == null || nodeId == null || primary == null || date == null || thresholdMap == null || events == null) {
throw new IllegalArgumentException("Null parameters not permitted.");
}
if (log.isDebugEnabled()) {
log.debug("checkNodeDir: threshold checking node dir: " + directory.getAbsolutePath());
}
for(Object threshKey :thresholdMap.keySet()) {
ThresholdEntity threshold = thresholdMap.get(threshKey);
Collection<String> requiredDatasources=threshold.getRequiredDatasources();
Map<String, Double> values=getThresholdValues(directory, range, interval, requiredDatasources);
if(values==null) {
continue; //Not all values were available
}
List<Event> eventList = threshold.evaluateAndCreateEvents(values, date);
if (eventList.size() == 0) {
// Nothing to do, so continue
continue;
}
completeEventListAndAddToEvents(events, eventList, nodeId, primary, null);
}
}
/**
* Performs threshold checking on an JMX RRD interface directory.
*
* @param directory
* RRD repository directory
* @param nodeId
* Node identifier
* @param primary
* Primary JMX interface address
* @param interval
* Configured thresholding interval
* @param range
* Age before which PDP is considered out of date
* @param date
* Source for timestamp to be used for all generated events
* @param baseIfThresholdMap
* Map of configured interface level ThresholdEntity objects
* keyed by datasource name.
* @param allIfThresholdMap
* Map of threshold maps indexed by ifLabel
* @param events
* Castor events object containing any events to be generated as
* a result of threshold checking.
*
* @throws IllegalArgumentException
* if path parameter is not a directory.
*/
private void checkIfDir(File directory, Integer nodeId, InetAddress primary, int interval, int range, Date date, Map<String,ThresholdEntity> baseIfThresholdMap, Map<String,Map<String,ThresholdEntity>> allIfThresholdMap, Events events) throws IllegalArgumentException {
// Sanity Check
if (directory == null || nodeId == null || primary == null || date == null || baseIfThresholdMap == null || allIfThresholdMap == null || events == null) {
throw new IllegalArgumentException("Null parameters not permitted.");
}
if (log().isDebugEnabled()) {
log().debug("checkIfDir: threshold checking interface dir: " + directory.getAbsolutePath());
}
String ifLabel = directory.getName();
if (log().isDebugEnabled()) {
log().debug("checkIfDir: ifLabel=" + ifLabel);
}
// This is an interface directory extract the
// interface label from the full path name of the file
/*
* String path = directory.getAbsolutePath(); String path = directory
* int fileSepIndex = path.lastIndexOf(File.separatorChar); if
* (fileSepIndex >= 0) ifLabel = path.substring(fileSepIndex+1,
* path.length()); else ifLabel = path;
*/
// Attempt to retrieve the threshold map for this interface
// using the ifLabel for the interface
Map<String, ThresholdEntity> thresholdMap = allIfThresholdMap.get(ifLabel);
if (thresholdMap == null) {
// Doesn't exist yet, go ahead and create it
// Must maintain a separate threshold map for
// each interface.
thresholdMap = new HashMap<String, ThresholdEntity>();
// Iterate over base interface threshold map and clone each
// ThresholdEntity object and add it to the threshold map.
// for this interface.
//
Iterator<ThresholdEntity> iter = baseIfThresholdMap.values().iterator();
while (iter.hasNext()) {
ThresholdEntity entity = iter.next();
thresholdMap.put(entity.getDataSourceExpression(), entity.clone());
}
// Add the new threshold map for this interface
// to the all interfaces map.
allIfThresholdMap.put(ifLabel, thresholdMap);
}
Map<String, String> ifDataMap = new HashMap<String, String>();
for(Object threshKey :thresholdMap.keySet()) {
ThresholdEntity threshold = thresholdMap.get(threshKey);
Collection<String> requiredDatasources=threshold.getRequiredDatasources();
Map<String, Double> values=getThresholdValues(directory, range, interval, requiredDatasources);
if(values==null) {
continue; //Not all values were available
}
List<Event> eventList = threshold.evaluateAndCreateEvents(values, date);
if (eventList.size() == 0) {
// Nothing to do, so continue
continue;
}
if (ifDataMap.size() == 0 && ifLabel != null) {
populateIfDataMap(nodeId, ifLabel, ifDataMap);
}
completeEventListAndAddToEvents(events, eventList, nodeId, primary, ifDataMap);
}
}
private void completeEventListAndAddToEvents(Events events, List<Event> eventList, Integer nodeId, InetAddress primary, Map<String, String> ifDataMap) {
for (Event event : eventList) {
event.setNodeid(nodeId.longValue());
event.setService(serviceName());
// Set event interface
if (ifDataMap == null || ifDataMap.get("ipaddr") == null) {
// Node level datasource
if (primary != null) {
event.setInterfaceAddress(primary);
}
} else {
/*
* Interface-level datasource
*
* NOTE: Non-IP interfaces will have an
* address of "0.0.0.0".
*/
String ifAddr = ifDataMap.get("ipaddr");
event.setInterfaceAddress(InetAddressUtils.addr(ifAddr));
}
// Add appropriate parms
final List<Parm> eventParms = event.getParmCollection();
Parm eventParm;
Value parmValue;
if (ifDataMap != null && ifDataMap.get("iflabel") != null) {
eventParm = new Parm();
eventParm.setParmName("ifLabel");
parmValue = new Value();
parmValue.setContent(ifDataMap.get("iflabel"));
eventParm.setValue(parmValue);
eventParms.add(eventParm);
}
events.addEvent(event);
}
}
private final ThreadCategory log() {
return ThreadCategory.getInstance(getClass());
}
private void populateIfDataMap(Integer nodeId, String ifLabel, Map<String, String> ifDataMap) {
ifDataMap.putAll(IfLabel.getInterfaceInfoFromIfLabel(nodeId.intValue(), ifLabel));
// Add ifLabel value to the map for potential when creating events
ifDataMap.put("iflabel", ifLabel);
}
/**
* <p>Setter for the field <code>useFriendlyName</code>.</p>
*
* @param useFriendlyName a boolean.
*/
public void setUseFriendlyName(boolean useFriendlyName) {
this.useFriendlyName = useFriendlyName;
}
}