org.hyperic.hq.product.SNMPMeasurementPlugin.java Source code

Java tutorial

Introduction

Here is the source code for org.hyperic.hq.product.SNMPMeasurementPlugin.java

Source

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