Java tutorial
/* * 'SNMPMeasurementPlugin.java' NOTE: This copyright does *not* cover user * programs that use HQ program services by normal system calls through the * application program interfaces provided as part of the Hyperic Plug-in * Development Kit or the Hyperic Client Development Kit - this is merely * considered normal use of the program, and does *not* fall under the heading * of "derived work". Copyright (C) [2004, 2005, 2006, 2007, 2008, 2009], * Hyperic, Inc. This file is part of HQ. HQ is free software; you can * redistribute it and/or modify it under the terms version 2 of the GNU General * Public License as published by the Free Software Foundation. This program 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 this * program; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA. */ package org.hyperic.hq.product; import java.util.ArrayList; import java.util.List; import java.util.Properties; import java.util.HashMap; import java.util.Map; import java.util.Iterator; import org.apache.commons.logging.Log; import org.hyperic.snmp.MIBLookupException; import org.hyperic.snmp.MIBTree; import org.hyperic.snmp.SNMPClient; import org.hyperic.snmp.SNMPException; import org.hyperic.snmp.SNMPSession; import org.hyperic.snmp.SNMPValue; import org.hyperic.util.StringUtil; import org.hyperic.util.timer.StopWatch; public class SNMPMeasurementPlugin extends MeasurementPlugin { public static final String DOMAIN = "snmp"; public static final String PROP_INDEX_NAME = "snmpIndexName"; public static final String PROP_INDEX_VALUE = "snmpIndexValue"; private static final String PROP_OID = "snmpOID"; private static final String PROP_VARTYPE = "snmpVarType"; private static final int VARTYPE_SINGLE = 0; private static final int VARTYPE_NEXT = 1; private static final int VARTYPE_COLUMN = 2; private static final int VARTYPE_INDEX = 3; private static final int VARTYPE_OID = 4; private static long ixTimestamp = 0; private static long ixExpire = (60 * 1000) * 60; // 1 hour private static HashMap VARTYPES = new HashMap(); private SNMPClient client = new SNMPClient(); private static Map ixCache = new HashMap(); private static Object ixLock = new Object(); static { VARTYPES.put("single", new Integer(VARTYPE_SINGLE)); VARTYPES.put("column", new Integer(VARTYPE_COLUMN)); VARTYPES.put("index", new Integer(VARTYPE_INDEX)); VARTYPES.put("oid", new Integer(VARTYPE_OID)); VARTYPES.put("next", new Integer(VARTYPE_NEXT)); } private Log log; /* * @return The MIB names that should be loaded for this plugin. */ protected String[] getMIBs() { String prop = getPluginProperty("MIBS"); if (prop == null) { return new String[0]; } else { List mibs = StringUtil.explode(prop, ","); return (String[]) mibs.toArray(new String[0]); } } private static int convertVarType(Properties props) { String var = props.getProperty(PROP_VARTYPE); if (var == null) { if (props.getProperty(PROP_INDEX_NAME) != null) { var = "index"; } else if (props.getProperty(PROP_OID) != null) { var = "oid"; } else { var = "single"; // Default } } Integer type = (Integer) VARTYPES.get(var); if (type == null) { String msg = "Unsupported " + PROP_VARTYPE + ": '" + var + "'"; throw new IllegalArgumentException(msg); } return type.intValue(); } /* * @see org.hyperic.hq.product.GenericPlugin#init */ public void init(PluginManager manager) throws PluginException { super.init(manager); this.log = getLog(); String prop = "snmp.indexCacheExpire"; String expire = manager.getProperty(prop); if (expire != null) { ixExpire = Integer.parseInt(expire) * 1000; } final String pdkDir = ProductPluginManager.getPdkDir(); if (pdkDir == null) { return; // Don't load MIBs in the server... } MIBTree.setMibDir(pdkDir + "/mibs"); try { if (this.client.init(manager.getProperties())) { if (this.data != null) // 'null' in the case of proxies { String jar = this.data.getFile(); String[] mibs = getMIBs(); if (jar.endsWith(".xml")) { // MIB files on local disk... this.client.addMIBs(mibs); } else { // MIB files embedded within the jar... this.client.addMIBs(jar, mibs); } } } } catch (SNMPException e) { throw new PluginException(e.getMessage(), e); } } private double getDoubleValue(SNMPValue snmpValue) throws PluginException { final String invalidType = "SNMP query returned a string which could not be handled: "; // If toLong or toFloat throw an exception only if // SNMPException.E_VARIABLE_IS_NOT_NUMERIC // we swallow those exceptions because we've already checked the type switch (snmpValue.getType()) { case SNMPValue.TYPE_LONG: case SNMPValue.TYPE_LONG_CONVERTABLE: try { return (double) snmpValue.toLong(); } catch (SNMPException e) { } case SNMPValue.TYPE_STRING: String value = snmpValue.toString(); // e.g. iplanet iwsInstanceLoad1MinuteAverage // on anything but solaris... if ("".equals(value)) { this.log.debug("string value is empty, returning -1"); return -1; } // In general, we should not be dealing with strings at all. // However, iPlanet for example stores cpu usage as a string. // In which case we can convert to double... try { double val = Double.parseDouble(value); this.log.debug("converted using Double.parseDouble"); return val; } catch (NumberFormatException e) { } // snmpValue.toLong() when value is TYPE_STRING // converts to a date stamp, which is our last attempt... try { double val = (double) snmpValue.toLong(); this.log.debug("converted using snmpValue.toLong"); return val; } catch (SNMPException e) { } default: throw new PluginException(invalidType + snmpValue.toString()); } } private static boolean listIsEmpty(List list) { if (list == null) { return true; } return list.isEmpty(); } // Unless we change snmplib to throw a more informative message // SNMPSession_v1.getNextValue throws SNMPException with the // message "SNMPException #101" // only seems to happen if it cannot talk to the snmp agent. // Otherwise getColumn and getNextValue just return a null List... private MetricUnreachableException snmpConnectException(Metric metric, SNMPException e) { Properties props = metric.getObjectProperties(); String cfg = props.getProperty(SNMPClient.PROP_IP, SNMPClient.DEFAULT_IP) + ":" + props.getProperty(SNMPClient.PROP_PORT, SNMPClient.DEFAULT_PORT_STRING) + " " + props.getProperty(SNMPClient.PROP_VERSION, SNMPClient.VALID_VERSIONS[1]) + "," + props.getProperty(SNMPClient.PROP_COMMUNITY, SNMPClient.DEFAULT_COMMUNITY); String msg = "Unable to connect to SNMP Agent (" + cfg + ")"; return new MetricUnreachableException(msg, e); } // e.g. ifOperStatus == 0 == AVAIL_DOWN... private boolean isAvail(Metric metric) { return "true".equals(metric.getObjectProperty("Avail")); } /* * @see org.hyperic.hq.product.MeasurementPlugin#getValue */ public MetricValue getValue(Metric metric) throws MetricUnreachableException, MetricNotFoundException, PluginException { boolean isDebug = this.log.isDebugEnabled(); SNMPSession session = getSession(metric); if (session == null) { throw new PluginException("SNMPSession was null!"); } Properties props = metric.getProperties(); double value = 0; String varName = metric.getAttributeName(); if ((varName == null) || (varName.length() == 0) || varName.equals("%oid%")) { // Special case for optional netservices.SNMP.OID Value metric... return MetricValue.NONE; } String varOID = getPluginProperty(varName); if (varOID != null) { if (isDebug) { log.debug(getName() + " defined " + varName + " to " + varOID); } varName = varOID; } int varType = convertVarType(props); int size; List columnOfValues; StopWatch timer = null; if (isDebug) { timer = new StopWatch(); } if ((varType == VARTYPE_SINGLE) || (varType == VARTYPE_NEXT)) { SNMPValue snmpValue; try { if (varType == VARTYPE_SINGLE) { snmpValue = session.getSingleValue(varName); } else { snmpValue = session.getNextValue(varName); } } catch (MIBLookupException e) { throw new MetricInvalidException(e.getMessage()); } catch (SNMPException e) { if (isAvail(metric)) { return new MetricValue(Metric.AVAIL_DOWN); } else { throw snmpConnectException(metric, e); } } finally { if (timer != null) { this.log.debug("getValue took: " + timer); } } value = getDoubleValue(snmpValue); } else { try { switch (varType) { case VARTYPE_INDEX: columnOfValues = session.getBulk(varName); if (listIsEmpty(columnOfValues)) { String msg = "Column data not found: " + varName; throw new MetricNotFoundException(msg); } size = columnOfValues.size(); int index = getIndex(props, session); if (index >= columnOfValues.size()) { String ix = props.getProperty(PROP_INDEX_NAME); String val = props.getProperty(PROP_INDEX_VALUE); String msg = "No value found for SNMP index: " + ix + "." + val; throw new MetricNotFoundException(msg); } value = getDoubleValue((SNMPValue) columnOfValues.get(index)); break; case VARTYPE_OID: int idx = -1; if (props.getProperty(PROP_INDEX_NAME) != null) { // Lookup index if given and include in the oid match... idx = getIndex(props, session); } String oid = props.getProperty(PROP_OID); boolean found = false; SNMPValue snmpValue = session.getTableValue(varName, idx + 1, oid); if (snmpValue != null) { value = getDoubleValue(snmpValue); found = true; } if (!found) { String msg = "OID not found: " + oid; throw new MetricNotFoundException(msg); } break; case VARTYPE_COLUMN: columnOfValues = session.getBulk(varName); if (listIsEmpty(columnOfValues)) { String msg = "Column data not found: " + varName; throw new MetricNotFoundException(msg); } size = columnOfValues.size(); // Should support OID matching here too... for (int i = 0; i < size; i++) { value += getDoubleValue((SNMPValue) columnOfValues.get(i)); } default: throw new MetricNotFoundException("Invalid vartype"); } } catch (SNMPException e) { if (isAvail(metric)) { return new MetricValue(Metric.AVAIL_DOWN); } else { throw snmpConnectException(metric, e); } } finally { if (timer != null) { this.log.debug("getValue took: " + timer + " (type=" + varType + ")"); } } } if (isAvail(metric)) { if (value <= 0) { value = Metric.AVAIL_DOWN; } else { value = Metric.AVAIL_UP; } } return new MetricValue(value); } private int getIndex(Properties props, SNMPSession session) throws MetricUnreachableException, MetricNotFoundException { String indexName = props.getProperty(PROP_INDEX_NAME); String indexValue = props.getProperty(PROP_INDEX_VALUE); if (indexName == null) { throw new MetricInvalidException("missing indexName"); } if (indexValue == null) { throw new MetricInvalidException("missing indexValue"); } synchronized (ixLock) { return getIndex(indexName, indexValue, session); } } private int getIndex(String indexName, String indexValue, SNMPSession session) throws MetricUnreachableException, MetricNotFoundException { long timeNow = System.currentTimeMillis(); Integer ix; boolean expired = false; if ((timeNow - ixTimestamp) > ixExpire) { if (ixTimestamp == 0) { this.log.debug("initializing index cache"); } else { this.log.debug("clearing index cache"); } ixCache.clear(); ixTimestamp = timeNow; expired = true; } else { if ((ix = (Integer) ixCache.get(indexValue)) != null) { return ix.intValue(); } } // For multiple indices we iterate through indexNames and // combine the values which we later attempt to match against // indexValue... List indexNames = StringUtil.explode(indexName, "->"); ArrayList data = new ArrayList(); // This can be optimized, esp. if indexNames.size() == 1... for (int i = 0; i < indexNames.size(); i++) { String name = (String) indexNames.get(i); List values = null; try { values = session.getBulk(name); for (int j = 0; j < values.size(); j++) { String value = values.get(j).toString(); StringBuffer buf = null; if (data.size() - 1 >= j) { buf = (StringBuffer) data.get(j); buf.append("->").append(value); } else { buf = new StringBuffer(value); data.add(buf); } } } catch (SNMPException e) { throw new MetricInvalidException(e); } } // We go in reverse in the case of apache having // two servername->80 entries, the first being the default (unused) // second being the vhost on 80 which is actually handling the requests // -- We could/should? enforce uniqueness here... for (int i = data.size() - 1; i >= 0; i--) { StringBuffer buf = (StringBuffer) data.get(i); String cur = buf.toString(); // Since we fetched all the data might as well build // up the index cache for future reference... Integer index = new Integer(i); ixCache.put(cur, index); // Only seen w/ microsoft snmp server // where interface name has a trailing null byte... ixCache.put(cur.trim(), index); } if (this.log.isDebugEnabled()) { if (expired) { this.log.debug("built index cache:"); for (Iterator it = ixCache.entrySet().iterator(); it.hasNext();) { Map.Entry ent = (Map.Entry) it.next(); this.log.debug(" " + ent.getKey() + "=>" + ent.getValue()); } } else { this.log.debug("forced to rebuild index cache looking for: " + indexValue); } } if ((ix = (Integer) ixCache.get(indexValue)) != null) { return ix.intValue(); } String possibleValues = ", possible values="; if (listIsEmpty(data)) { possibleValues += "[NONE FOUND]"; } else { possibleValues += data.toString(); } throw new MetricNotFoundException( "could not find value '" + indexValue + "' in column '" + indexName + "'" + possibleValues); } private SNMPSession getSession(Metric metric) throws PluginException { Properties props = metric.getObjectProperties(); if (props.get(SNMPClient.PROP_IP) == null) { // Backcompat: poor decision to make ip address the domain name props.put(SNMPClient.PROP_IP, metric.getDomainName()); } try { return this.client.getSession(props); } catch (SNMPException e) { throw new PluginException(e.getMessage(), e); } } }