/******************************************************************************* * 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); } }