/*******************************************************************************
* 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;
import java.io.File;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.opennms.core.utils.ThreadCategory;
import org.opennms.netmgt.rrd.RrdException;
import org.opennms.netmgt.rrd.RrdUtils;
import org.opennms.netmgt.threshd.ThresholdEvaluatorState.Status;
import org.opennms.netmgt.xml.event.Event;
import org.opennms.netmgt.xml.event.Parm;
import org.opennms.netmgt.xml.event.Value;
/**
* Wraps the castor created org.opennms.netmgt.config.threshd.Threshold class
* and provides the ability to track threshold exceeded occurrences.
*
* @author ranger
* @version $Id: $
*/
public final class ThresholdEntity implements Cloneable {
private static List<ThresholdEvaluator> s_thresholdEvaluators;
//Contains a list of evaluators for each used "instance". Is populated with the list for the "default" instance (the "null" key)
// in the Constructor. Note that this means we must use a null-key capable map like HashMap
private Map<String,List<ThresholdEvaluatorState>> m_thresholdEvaluatorStates = new HashMap<String,List<ThresholdEvaluatorState>>();
// the commands for these need to be listed in ThresholdController as well
static {
s_thresholdEvaluators = new LinkedList<ThresholdEvaluator>();
s_thresholdEvaluators.add(new ThresholdEvaluatorHighLow());
s_thresholdEvaluators.add(new ThresholdEvaluatorRelativeChange());
s_thresholdEvaluators.add(new ThresholdEvaluatorAbsoluteChange());
s_thresholdEvaluators.add(new ThresholdEvaluatorRearmingAbsoluteChange());
}
/**
* Constructor.
*/
public ThresholdEntity() {
//Put in a default list for the "null" key (the default evaluators)
m_thresholdEvaluatorStates.put(null, new LinkedList<ThresholdEvaluatorState>());
}
/**
* <p>getThresholdConfig</p>
*
* @return a {@link org.opennms.netmgt.threshd.BaseThresholdDefConfigWrapper} object.
*/
public BaseThresholdDefConfigWrapper getThresholdConfig() {
return m_thresholdEvaluatorStates.get(null).get(0).getThresholdConfig();
}
private boolean hasThresholds() {
return m_thresholdEvaluatorStates.get(null).size()!=0;
}
/**
* Get datasource name
*
* @return a {@link java.lang.String} object.
*/
public String getDataSourceExpression() {
if (hasThresholds()) {
return getThresholdConfig().getDatasourceExpression();
} else {
throw new IllegalStateException("No thresholds have been added.");
}
}
/**
* Get datasource type
*
* @return a {@link java.lang.String} object.
*/
public String getDatasourceType() {
if (hasThresholds()) {
return getThresholdConfig().getDsType();
} else {
throw new IllegalStateException("No thresholds have been added.");
}
}
/**
* Get datasource Label
*
* @return a {@link java.lang.String} object.
*/
public String getDatasourceLabel() {
if (hasThresholds()) {
return getThresholdConfig().getDsLabel();
} else {
return null;
}
}
/**
* Returns the names of the dataousrces required to evaluate this threshold entity
*
* @return Collection of the names of datasources
*/
public Collection<String> getRequiredDatasources() {
if (hasThresholds()) {
return getThresholdConfig().getRequiredDatasources();
} else {
throw new IllegalStateException("No thresholds have been added.");
}
}
/**
* Returns a copy of this ThresholdEntity object.
*
* NOTE: The m_lowThreshold and m_highThreshold member variables are not
* actually cloned...the returned ThresholdEntity object will simply contain
* references to the same castor Threshold objects as the original
* ThresholdEntity object.
*
* All state will be lost, particularly instances, so it's not a true clone by any stretch of the imagination
*
* @return a {@link org.opennms.netmgt.threshd.ThresholdEntity} object.
*/
public ThresholdEntity clone() {
ThresholdEntity clone = new ThresholdEntity();
for (ThresholdEvaluatorState thresholdItem : getThresholdEvaluatorStates(null)) {
clone.addThreshold(thresholdItem.getThresholdConfig());
}
return clone;
}
/**
* This method is responsible for returning a String object which represents
* the content of this ThresholdEntity. Primarily used for debugging
* purposes.
*
* @return String which represents the content of this ThresholdEntity
*/
public String toString() {
if (!hasThresholds()) {
return "";
}
StringBuffer buffer = new StringBuffer("{");
buffer.append("evaluator=").append(this.getThresholdConfig().getType());
buffer.append(", dsName=").append(this.getDataSourceExpression());
buffer.append(", dsType=").append(this.getDatasourceType());
buffer.append(", evaluators=[");
for (ThresholdEvaluatorState item : getThresholdEvaluatorStates(null)) {
buffer.append("{ds=").append(item.getThresholdConfig().getDatasourceExpression());
buffer.append(", value=").append(item.getThresholdConfig().getValue());
buffer.append(", rearm=").append(item.getThresholdConfig().getRearm());
buffer.append(", trigger=").append(item.getThresholdConfig().getTrigger());
buffer.append("}");
}
buffer.append("]}");
return buffer.toString();
}
/**
* Evaluates the threshold in light of the provided datasource value and
* create any events for thresholds.
*
* Semi-deprecated method; only used for old Thresholding code (threshd and friends)
* Implemented in terms of the other method with the same name and the extra param
*
* @param values
* map of values (by datasource name) to evaluate against the threshold (might be an expression)
* @param date
* Date to use in created events
* @return List of events
*/
public List<Event> evaluateAndCreateEvents(Map<String, Double> values, Date date) {
return evaluateAndCreateEvents(null, values, date);
}
/**
* Evaluates the threshold in light of the provided datasource value, for
* the named instance (or the generic instance if instance is null) and
* create any events for thresholds.
*
* @param values
* map of values (by datasource name) to evaluate against the threshold (might be an expression)
* @param date
* Date to use in created events
* @return List of events
* @param resource a {@link org.opennms.netmgt.threshd.CollectionResourceWrapper} object.
*/
public List<Event> evaluateAndCreateEvents(CollectionResourceWrapper resource, Map<String, Double> values, Date date) {
List<Event> events = new LinkedList<Event>();
double dsValue=0.0;
String instance = resource != null ? resource.getInstance() : null;
try {
if (getThresholdEvaluatorStates(instance).size() > 0) {
dsValue=getThresholdConfig().evaluate(values);
} else {
throw new IllegalStateException("No thresholds have been added.");
}
} catch (ThresholdExpressionException e) {
log().warn("Failed to evaluate: ", e);
return events; //No events to report
}
if (log().isDebugEnabled()) {
log().debug("evaluate: value= " + dsValue + " against threshold: " + this);
}
for (ThresholdEvaluatorState item : getThresholdEvaluatorStates(instance)) {
Status status = item.evaluate(dsValue);
Event event = item.getEventForState(status, date, dsValue, resource);
if (event != null) {
events.add(event);
}
}
return events;
}
private final ThreadCategory log() {
return ThreadCategory.getInstance(ThresholdEntity.class);
}
/**
* <p>fetchLastValue</p>
*
* @param latIface a {@link org.opennms.netmgt.threshd.LatencyInterface} object.
* @param latParms a {@link org.opennms.netmgt.threshd.LatencyParameters} object.
* @return a {@link java.lang.Double} object.
* @throws org.opennms.netmgt.threshd.ThresholdingException if any.
*/
public Double fetchLastValue(LatencyInterface latIface, LatencyParameters latParms) throws ThresholdingException {
//Assume that this only happens on a simple "Threshold", not an "Expression"
//If it is an Expression, then we don't yet know what to do - this will likely just fail with some sort of exception.
//perhaps we should figure out how to expand it (or at least use code elsewhere to do so sensibly)
String datasource=getDataSourceExpression();
// Use RRD strategy to "fetch" value of the datasource from the RRD file
Double dsValue = null;
try {
if (getDatasourceType().equals("if")) {
if (log().isDebugEnabled()) {
log().debug("Fetching last value from dataSource '" + datasource + "'");
}
File rrdFile = new File(latIface.getLatencyDir(), datasource+RrdUtils.getExtension());
if (!rrdFile.exists()) {
log().info("rrd file "+rrdFile+" does not exist");
return null;
}
if (!rrdFile.canRead()) {
log().error("Unable to read existing rrd file "+rrdFile);
return null;
}
if (latParms.getRange() == 0) {
dsValue = RrdUtils.fetchLastValue(rrdFile.getAbsolutePath(), datasource, latParms.getInterval());
} else {
dsValue = RrdUtils.fetchLastValueInRange(rrdFile.getAbsolutePath(), datasource, latParms.getInterval(), latParms.getRange());
}
} else {
throw new ThresholdingException("expr types not yet implemented", LatencyThresholder.THRESHOLDING_FAILED);
}
if (log().isDebugEnabled()) {
log().debug("Last value from dataSource '" + datasource + "' was "+dsValue);
}
} catch (NumberFormatException nfe) {
log().warn("Unable to convert retrieved value for datasource '" + datasource + "' to a double, skipping evaluation.");
} catch (RrdException e) {
log().error("An error occurred retriving the last value for datasource '" + datasource + "': " + e, e);
}
return dsValue;
}
/**
* <p>addThreshold</p>
*
* @param threshold a {@link org.opennms.netmgt.threshd.BaseThresholdDefConfigWrapper} object.
*/
public void addThreshold(BaseThresholdDefConfigWrapper threshold) {
ThresholdEvaluator evaluator = getEvaluatorForThreshold(threshold);
//Get the default list of evaluators (the null key)
List<ThresholdEvaluatorState> defaultList=m_thresholdEvaluatorStates.get(null);
for (ThresholdEvaluatorState item : defaultList) {
if (threshold.getType().equals(item.getThresholdConfig().getType())) {
throw new IllegalStateException(threshold.getType().toString() + " threshold already set.");
}
}
defaultList.add(evaluator.getThresholdEvaluatorState(threshold));
}
private ThresholdEvaluator getEvaluatorForThreshold(BaseThresholdDefConfigWrapper threshold) {
for (ThresholdEvaluator evaluator : getThresholdEvaluators()) {
if (evaluator.supportsType(threshold.getType())) {
return evaluator;
}
}
String message = "Threshold type '" + threshold.getType().toString() + "' for "+ threshold.getDatasourceExpression() + " is not supported";
log().warn(message);
throw new IllegalArgumentException(message);
}
/**
* Returns the evaluator states *for the given instance.
*
* @param instance The key to use to identify the instance to get states for. Can be null to get the default instance
* @return a {@link java.util.List} object.
*/
public List<ThresholdEvaluatorState> getThresholdEvaluatorStates(String instance) {
List<ThresholdEvaluatorState> result= m_thresholdEvaluatorStates.get(instance);
if(result==null) {
//There is no set of evaluators for this instance; create a list by copying the base ones
List<ThresholdEvaluatorState> defaultList=m_thresholdEvaluatorStates.get(null);
//Create the new list
result=new LinkedList<ThresholdEvaluatorState>();
for(ThresholdEvaluatorState state: defaultList) {
result.add(state.getCleanClone());
}
//Store the new list with the instance as the key
m_thresholdEvaluatorStates.put(instance == null ? null : instance.intern(), result);
}
return result;
}
/**
* Merges the configuration and update states using parameter entity as a reference.
*
* @param entity a {@link org.opennms.netmgt.threshd.ThresholdEntity} object.
*/
public void merge(ThresholdEntity entity) {
if (getThresholdConfig().identical(entity.getThresholdConfig()) == false) {
sendRearmForTriggeredStates();
getThresholdConfig().merge(entity.getThresholdConfig());
}
}
/**
* Delete this will check states and will send rearm for all triggered.
*/
public void delete() {
sendRearmForTriggeredStates();
}
private void sendRearmForTriggeredStates() {
for (String instance : m_thresholdEvaluatorStates.keySet()) {
for (ThresholdEvaluatorState state : m_thresholdEvaluatorStates.get(instance)) {
if (state.isTriggered()) {
Event e = state.getEventForState(Status.RE_ARMED, new Date(), Double.NaN, null);
Parm p = new Parm();
p.setParmName("reason");
Value v = new Value();
v.setContent("Configuration has been changed");
p.setValue(v);
e.addParm(p);
log().info("sendRearmForTriggeredStates: sending rearm for " + e);
ThresholdingEventProxyFactory.getFactory().getProxy().add(e);
state.clearState();
}
}
}
}
/**
* <p>getThresholdEvaluators</p>
*
* @return a {@link java.util.List} object.
*/
public static final List<ThresholdEvaluator> getThresholdEvaluators() {
return s_thresholdEvaluators;
}
}