/*******************************************************************************
* This file is part of OpenNMS(R).
*
* Copyright (C) 2009-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.poller.monitors;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.bsf.BSFException;
import org.apache.bsf.BSFManager;
import org.apache.bsf.util.IOUtils;
import org.opennms.core.utils.LogUtils;
import org.opennms.core.utils.ParameterMap;
import org.opennms.netmgt.model.PollStatus;
import org.opennms.netmgt.poller.Distributable;
import org.opennms.netmgt.poller.DistributionContext;
import org.opennms.netmgt.poller.MonitoredService;
// This might actually be usable in the remote poller with some work
@Distributable(DistributionContext.DAEMON)
/**
* <P>
* This <code>ServiceMonitor</code> is designed to enable the evaluation
* or execution of user-supplied scripts via the Bean Scripting Framework
* (BSF). Scripts should indicate a status whose string value is one of:
*
* "OK" (service is available),
* "UNK" (service status unknown),
* "UNR" (service is unresponsive), or
* "NOK" (service is unavailable).
*
* These strings map into the status values defined in @PollStatus and are
* indicated differently depending on the run-type of the script in question
* (see below for details).
*
* Use cases:
*
* a) Evaluate an expression from a file in the filesystem. The result of the
* evaluation carries the status indication in this mode. As a special case
* for backward compatibility, a status code outside the set described above
* will be taken to convey that the service is unavailable, and the status code
* itself will be set as the reason code.
*
* If the scripting engine in use supports bean manipulation during an
* evaluation, then any entries put into the "times" bean will be returned
* for optional thresholding and/or persisting. If no entries exist in this
* bean upon the evaluation's return, then a single value describing the time
* window (in milliseconds) from just before the evaluation began until just
* after it returned will be substituted.
*
* This mode is the default if no "run-type" parameter is
* specified in the service definition or if this parameter's value is "eval".
*
* b) Execute a self-contained script from a file in the filesystem. The script
* must put an entry into the "results" HashMap bean with key "status" and a
* value from the above list of service status indications. If the script puts
* one or more entries into the "times" bean, then these key-value pairs will
* be returned for optional thresholding and/or persisting. If no entry exists
* in this bean with a key of "response-time", the overall response time will
* be substituted as the time from just before the script's execution to just
* after its completion.
*
* This mode is used if the service's definition contains a "run-type" parameter
* with a value of "exec".
*
* The following beans are declared in the script's execution context:
*
* map: A @Map<String,Object> allowing direct access to the list of parameters
* configured for the service at hand
* ip_addr: A @String representing the IPv4 or IPv6 address of the interface
* on which the polled service resides
* node_id: An int containing the unique identifying number from the OpenNMS
* configuration database of the node on whose interface the
* monitored service resides
* node_label: A @String containing the textual node label of the node on whose
* interface the monitored service resides
* svc_name: A @String containing the textual name of the monitored service
* bsf_monitor: The singleton instance of the @BSFMonitor class, useful primarily
* for purposes of logging via its
* log(String sev, String fmt, Object... args) method. The severity
* must be one of TRACE, DEBUG, INFO, WARN, ERROR, FATAL. The format
* is a printf-style format string, and the args fill in the tokens.
* results: A @HashMap<String,String> that the script may use to pass its results
* back to the @BSFMonitor. A status indication should be set into the
* entry with key "status", and for status indications other than "OK"
* a reason code should be set into the entry with key "reason".
* times: A @LinkedHashMap<String,Number> that the script may use to pass one
* or more response times back to the @BSFMonitor.
* </P>
*
* @author <A HREF="mailto:jay@opennms.org">Jason Aras</A>
* @author <A HREF="mailto:jeffg@opennms.org">Jeff Gehlbach</A>
* @author <A HREF="http://www.opennms.org">OpenNMS</A>
*/
public class BSFMonitor extends AbstractServiceMonitor {
private static final String STATUS_UNKNOWN = "UNK";
private static final String STATUS_UNRESPONSIVE = "UNR";
private static final String STATUS_AVAILABLE = "OK";
private static final String STATUS_UNAVAILABLE = "NOK";
/** {@inheritDoc} */
public PollStatus poll(MonitoredService svc, Map<String,Object> map) {
BSFManager bsfManager = new BSFManager();
PollStatus pollStatus = PollStatus.unavailable();
String fileName = ParameterMap.getKeyedString(map,"file-name", null);
String lang = ParameterMap.getKeyedString(map, "lang-class", null);
String langEngine = ParameterMap.getKeyedString(map, "bsf-engine", null);
String langExtensions[] = ParameterMap.getKeyedString(map, "file-extensions", "").split(",");
String runType = ParameterMap.getKeyedString(map, "run-type", "eval");
File file = new File(fileName);
try {
if(lang==null)
lang = BSFManager.getLangFromFilename(fileName);
if(langEngine!=null && lang!=null && langExtensions.length > 0 ){
BSFManager.registerScriptingEngine(lang,langEngine,langExtensions);
}
if(file.exists() && file.canRead()){
String code = IOUtils.getStringFromReader(new InputStreamReader(new FileInputStream(file), "UTF-8"));
HashMap<String,String> results = new HashMap<String,String>();
LinkedHashMap<String,Number> times = new LinkedHashMap<String,Number>();
// Declare some beans that can be used inside the script
bsfManager.declareBean("map", map, Map.class);
bsfManager.declareBean("ip_addr",svc.getIpAddr(),String.class);
bsfManager.declareBean("node_id",svc.getNodeId(),int.class );
bsfManager.declareBean("node_label", svc.getNodeLabel(), String.class);
bsfManager.declareBean("svc_name", svc.getSvcName(), String.class);
bsfManager.declareBean("bsf_monitor", this, BSFMonitor.class);
bsfManager.declareBean("results", results, HashMap.class);
bsfManager.declareBean("times", times, LinkedHashMap.class);
for (final Entry<String, Object> entry : map.entrySet()) {
bsfManager.declareBean(entry.getKey(),entry.getValue(),String.class);
}
pollStatus = PollStatus.unknown("The script did not update the service status");
long startTime = System.currentTimeMillis();
if ("eval".equals(runType)) {
results.put("status", bsfManager.eval(lang, "BSFMonitor", 0, 0, code).toString());
} else if ("exec".equals(runType)) {
bsfManager.exec(lang, "BSFMonitor", 0, 0, code);
} else {
LogUtils.warnf(this, "Invalid run-type parameter value '%s' for service '%s'. Only 'eval' and 'exec' are supported.", runType, svc.getSvcName());
throw new RuntimeException("Invalid run-type '" + runType + "'");
}
long endTime = System.currentTimeMillis();
if (!times.containsKey("response-time")) {
times.put("response-time", endTime - startTime);
}
if (STATUS_UNKNOWN.equals(results.get("status"))) {
pollStatus = PollStatus.unknown(results.get("reason"));
} else if (STATUS_UNRESPONSIVE.equals(results.get("status"))) {
pollStatus = PollStatus.unresponsive(results.get("reason"));
} else if (STATUS_AVAILABLE.equals(results.get("status"))){
pollStatus = PollStatus.available();
} else if (STATUS_UNAVAILABLE.equals(results.get("status"))) {
pollStatus = PollStatus.unavailable(results.get("reason"));
} else {
// Fall through to the old default of treating any other non-OK
// code as meaning unavailable and also carrying the reason code
pollStatus = PollStatus.unavailable(results.get("status"));
}
LogUtils.debugf(this, "Setting %d times for service '%s'", times.size(), svc.getSvcName());
pollStatus.setProperties(times);
if ("exec".equals(runType) && !results.containsKey("status")) {
LogUtils.warnf(this, "The exec script '%s' for service '%s' never put a 'status' entry in the 'results' bean. Exec scripts should put this entry with a value of 'OK' for up.", fileName, svc.getSvcName());
}
} else {
LogUtils.warnf(this, "Cannot locate or read BSF script file '%s'. Marking service '%s' down.", fileName, svc.getSvcName());
pollStatus = PollStatus.unavailable("Cannot locate or read BSF script file: " + fileName);
}
} catch (BSFException e) {
LogUtils.warnf(this, e, "BSFMonitor poll for service '%s' failed with BSFException: %s", svc.getSvcName(), e.getMessage());
pollStatus = PollStatus.unavailable(e.getMessage());
} catch (FileNotFoundException e){
LogUtils.warnf(this, "Could not find BSF script file '%s'. Marking service '%s' down.", fileName, svc.getSvcName());
pollStatus = PollStatus.unavailable("Could not find BSF script file: " + fileName);
} catch (IOException e) {
pollStatus = PollStatus.unavailable(e.getMessage());
LogUtils.warnf(this, e, "BSFMonitor poll for service '%s' failed with IOException: %s", svc.getSvcName(), e.getMessage());
} catch (Throwable e) {
// Catch any RuntimeException throws
pollStatus = PollStatus.unavailable(e.getMessage());
LogUtils.warnf(this, e, "BSFMonitor poll for service '%s' failed with unexpected throwable: %s", svc.getSvcName(), e.getMessage());
} finally {
bsfManager.terminate();
}
return pollStatus;
}
public void log(String level, String format, Object... args) {
if ("TRACE".equals(level)) LogUtils.tracef(this, format, args);
if ("DEBUG".equals(level)) LogUtils.debugf(this, format, args);
if ("INFO".equals(level)) LogUtils.infof(this, format, args);
if ("WARN".equals(level)) LogUtils.warnf(this, format, args);
if ("ERROR".equals(level)) LogUtils.errorf(this, format, args);
if ("FATAL".equals(level)) LogUtils.errorf(this, format, args);
}
}