/*
* JBoss, Home of Professional Open Source.
* Copyright 2008, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.jmx.adaptor.snmp.agent;
import java.io.InputStream;
import java.net.InetAddress;
import java.util.Iterator;
import java.util.List;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import javax.management.Attribute;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import org.jboss.jmx.adaptor.snmp.config.attribute.AttributeMappings;
import org.jboss.jmx.adaptor.snmp.config.attribute.ManagedBean;
import org.jboss.jmx.adaptor.snmp.config.attribute.MappedAttribute;
import org.jboss.logging.Logger;
import org.jboss.xb.binding.ObjectModelFactory;
import org.jboss.xb.binding.Unmarshaller;
import org.jboss.xb.binding.UnmarshallerFactory;
import org.opennms.protocols.snmp.SnmpAgentSession;
import org.opennms.protocols.snmp.SnmpInt32;
import org.opennms.protocols.snmp.SnmpNull;
import org.opennms.protocols.snmp.SnmpObjectId;
import org.opennms.protocols.snmp.SnmpOctetString;
import org.opennms.protocols.snmp.SnmpPduPacket;
import org.opennms.protocols.snmp.SnmpPduRequest;
import org.opennms.protocols.snmp.SnmpSyntax;
import org.opennms.protocols.snmp.SnmpUInt32;
import org.opennms.protocols.snmp.SnmpVarBind;
/**
* Implement RequestHandler with mapping of snmp get/set requests
* to JMX mbean attribute gets/sets
*
* @author <a href="mailto:hwr@pilhuhn.de>">Heiko W. Rupp</a>
* @author <a href="mailto:dimitris@jboss.org">Dimitris Andreadis</a>
* @version $Revision: 81038 $
*/
public class RequestHandlerImpl extends RequestHandlerSupport
implements Reconfigurable
{
// Protected Data ------------------------------------------------
private static final String NO_ENTRY_FOUND_FOR_OID = "No entry found for oid ";
private static final String SKIP_ENTRY = " - skipping entry";
/** Bindings from oid to mbean */
protected SortedMap bindings = new TreeMap();
private SortedSet oidKeys = null;
/** Has this RequestHandler instance been initialized? */
private boolean initialized = false;
// Constructors --------------------------------------------------
/**
* Default CTOR
*/
public RequestHandlerImpl()
{
bindings = new TreeMap();
oidKeys = new TreeSet();
}
// RequestHandler Implementation ---------------------------------
/**
* Initialize
*
* @param resourceName A file containing get/set mappings
* @param server Our MBean-Server
* @param log The logger we use
* @param uptime The uptime of the snmp-agent subsystem.
*/
public void initialize(String resourceName, MBeanServer server, Logger log, Clock uptime)
throws Exception
{
log.debug("initialize() with res=" + resourceName);
super.initialize(resourceName, server, log, uptime);
if (resourceName != null)
initializeBindings();
else
log.warn("No RequestHandlerResName configured, disabling snmp-get");
initialized = true;
}
// Reconfigurable Implementation ---------------------------------
/**
* Reconfigures the RequestHandler
*/
public void reconfigure(String resName) throws Exception
{
if (resName == null || resName.equals(""))
throw new IllegalArgumentException("Null or empty resName, cannot reconfigure");
if (initialized == false)
throw new IllegalStateException("Cannot reconfigure, not initialized yet");
this.resourceName = resName;
// Wipe out old entries
bindings.clear();
// Fetch them again
initializeBindings();
}
// SnmpAgentHandler Implementation -------------------------------
/**
* <P>
* This method is defined to handle SNMP Get requests that are received by
* the session. The request has already been validated by the system. This
* routine will build a response and pass it back to the caller.
* </P>
*
* @param pdu
* The SNMP pdu
* @param getNext
* The agent is requesting the lexically NEXT item after each
* item in the pdu.
*
* @return SnmpPduRequest filled in with the proper response, or null if
* cannot process NOTE: this might be changed to throw an exception.
*/
public SnmpPduRequest snmpReceivedGet(SnmpPduPacket pdu, boolean getNext)
{
try
{
SnmpPduRequest response = null;
int pduLength = pdu.getLength();
final boolean trace = log.isTraceEnabled();
if (trace)
log.trace("requestId=" + pdu.getRequestId() + ", pduLength="
+ pduLength + ", getNext=" + getNext);
SnmpVarBind[] vblist = new SnmpVarBind[pduLength];
int errorStatus = SnmpPduPacket.ErrNoError;
int errorIndex = 0;
// Process for each varibind in the request
for (int i = 0; i < pduLength; i++)
{
boolean good = true;
SnmpVarBind vb = pdu.getVarBindAt(i);
SnmpObjectId oid = vb.getName();
if (getNext)
{
/*
* We call getNextOid() to find out what is the next valid OID
* instance in the supported MIB (sub-)tree. Assign that OID to the
* VB List and then proceed same as that of get request. If the
* passed oid is already the last, we flag it.
*/
ComparableSnmpObjectId coid = new ComparableSnmpObjectId(oid);
oid = getNextOid(coid, true);
if (oid == null)
{
good = false;
}
else
{
pdu.setVarBindAt(i, new SnmpVarBind(oid));
}
}
if (oid!=null)
vblist[i] = new SnmpVarBind(oid);
else
vblist[i] = new SnmpVarBind(vb.getName()); // oid passed in
if (trace)
log.trace("oid=" + oid);
SnmpSyntax result = null;
if (good && bindings != null)
result = getValueFor(oid);
if (trace)
log.trace("got result of " + result);
if (result == null || !good)
{
errorStatus = SnmpPduPacket.ErrNoSuchName;
errorIndex = i + 1;
log.debug("Error Occured " + vb.getName().toString());
}
else
{
vblist[i].setValue(result);
log.debug("Varbind[" + i + "] := "
+ vblist[i].getName().toString());
log.debug(" --> " + vblist[i].getValue().toString());
}
} // for ...
response = new SnmpPduRequest(SnmpPduPacket.RESPONSE, vblist);
response.setErrorStatus(errorStatus);
response.setErrorIndex(errorIndex);
return response;
} catch (Exception e)
{
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
}
/**
* <P>
* This method is defined to handle SNMP Set requests that are received by
* the session. The request has already been validated by the system. This
* routine will build a response and pass it back to the caller.
* </P>
*
* @param pdu
* The SNMP pdu
*
* @return SnmpPduRequest filled in with the proper response, or null if
* cannot process NOTE: this might be changed to throw an exception.
*/
public SnmpPduRequest snmpReceivedSet(SnmpPduPacket pdu)
{
final boolean trace = log.isTraceEnabled();
SnmpPduRequest response = null;
int errorStatus = SnmpPduPacket.ErrNoError;
int errorIndex = 0;
int k = pdu.getLength();
SnmpVarBind[] vblist = new SnmpVarBind[k];
for (int i = 0; i < k; i++)
{
SnmpVarBind vb = pdu.getVarBindAt(i);
vblist[i] = new SnmpVarBind(vb);
SnmpObjectId oid = vb.getName();
SnmpSyntax newVal = vb.getValue();
if (trace)
log.trace("set: received oid " + oid.toString() + " with value " + newVal.toString());
SnmpSyntax result = null;
try
{
result = setValueFor(oid,newVal);
}
catch (ReadOnlyException e)
{
errorStatus = SnmpPduPacket.ErrReadOnly;
errorIndex = i + 1;
}
if (result != null)
{
errorStatus = SnmpPduPacket.ErrReadOnly;
errorIndex = i + 1;
log.debug("Error occured " + vb.getName().toString());
}
if (trace)
{
log.trace("Varbind[" + i + "] := " + vb.getName().toString());
log.trace(" --> " + vb.getValue().toString());
}
}
response = new SnmpPduRequest(SnmpPduPacket.RESPONSE, vblist);
response.setErrorStatus(errorStatus);
response.setErrorIndex(errorIndex);
return response;
}
/**
* <P>
* This method is defined to handle SNMP requests that are received by the
* session. The parameters allow the handler to determine the host, port,
* and community string of the received PDU
* </P>
*
* @param session
* The SNMP session
* @param manager
* The remote sender
* @param port
* The remote senders port
* @param community
* The community string
* @param pdu
* The SNMP pdu
*
*/
public void snmpReceivedPdu(SnmpAgentSession session, InetAddress manager,
int port, SnmpOctetString community, SnmpPduPacket pdu)
{
log.error("Message from manager " + manager.toString() + " on port " + port);
int cmd = pdu.getCommand();
log.error("Unsupported PDU command......... " + cmd);
}
/**
* <P>
* This method is invoked if an error occurs in the session. The error code
* that represents the failure will be passed in the second parameter,
* 'error'. The error codes can be found in the class SnmpAgentSession
* class.
* </P>
*
* <P>
* If a particular PDU is part of the error condition it will be passed in
* the third parameter, 'pdu'. The pdu will be of the type SnmpPduRequest or
* SnmpPduTrap object. The handler should use the "instanceof" operator to
* determine which type the object is. Also, the object may be null if the
* error condition is not associated with a particular PDU.
* </P>
*
* @param session
* The SNMP Session
* @param error
* The error condition value.
* @param ref
* The PDU reference, or potentially null. It may also be an
* exception.
*/
public void SnmpAgentSessionError(SnmpAgentSession session, int error, Object ref)
{
log.error("An error occured in the trap session");
log.error("Session error code = " + error);
if (ref != null)
{
log.error("Session error reference: " + ref.toString());
}
if (error == SnmpAgentSession.ERROR_EXCEPTION)
{
synchronized (session)
{
session.notify(); // close the session
}
}
}
// Private -------------------------------------------------------
/**
* Initialize the bindings from the file given in resourceName
*/
private void initializeBindings() throws Exception
{
log.debug("Reading resource: '" + resourceName + "'");
ObjectModelFactory omf = new AttributeMappingsBinding();
InputStream is = null;
AttributeMappings mappings = null;
try
{
// locate resource
is = getClass().getResourceAsStream(resourceName);
// create unmarshaller
Unmarshaller unmarshaller = UnmarshallerFactory.newInstance().newUnmarshaller();
// let JBossXB do it's magic using the AttributeMappingsBinding
mappings = (AttributeMappings)unmarshaller.unmarshal(is, omf, null);
}
catch (Exception e)
{
log.error("Accessing resource '" + resourceName + "'");
throw e;
}
finally
{
if (is != null)
{
// close the XML stream
is.close();
}
}
if (mappings == null)
{
log.warn("No bindings found in " + resourceName);
return;
}
log.debug("Found " + mappings.size() + " attribute mappings");
/**
* We have the MBeans now. Put them into the bindungs.
*/
Iterator it = mappings.iterator();
while (it.hasNext())
{
ManagedBean mmb = (ManagedBean)it.next();
String oidPrefix = mmb.getOidPrefix();
List attrs = mmb.getAttributes();
Iterator aIt = attrs.iterator();
while (aIt.hasNext())
{
MappedAttribute ma = (MappedAttribute)aIt.next();
String oid;
if (oidPrefix != null)
oid = oidPrefix + ma.getOid();
else
oid = ma.getOid();
BindEntry be = new BindEntry(oid, mmb.getName(), ma.getName());
be.isReadWrite = ma.isReadWrite();
ComparableSnmpObjectId coid = new ComparableSnmpObjectId(oid);
if (log.isTraceEnabled())
log.trace("New bind entry " + be);
if (bindings.containsKey(coid)) {
log.info("Duplicate oid " + oid + SKIP_ENTRY);
continue;
}
if (mmb.getName() == null || mmb.getName().equals(""))
{
log.info("Invalid mbean name for oid " + oid + SKIP_ENTRY);
continue;
}
if (ma.getName() == null || ma.getName().equals(""))
{
log.info("Invalid attribute name " + ma.getName() + " for oid " + oid + SKIP_ENTRY);
continue;
}
bindings.put(coid, be);
oidKeys.add(coid);
}
}
}
/**
* Return the current value for the given oid
*
* @param oid
* The oid we want a value for
* @return SnmpNull if no value present
*/
private SnmpSyntax getValueFor(final SnmpObjectId oid) {
BindEntry be = findBindEntryForOid(oid);
SnmpSyntax ssy = null;
if (be != null)
{
if (log.isTraceEnabled())
log.trace("Found entry " + be.toString() + " for oid " + oid);
try
{
Object val = server.getAttribute(be.mbean, be.attr.getName());
if (val instanceof Long)
{
Long uin = (Long) val;
ssy = new SnmpUInt32(uin);
}
else if (val instanceof String)
{
String in = (String) val;
ssy = new SnmpOctetString(in.getBytes());
}
else if (val instanceof Integer)
{
Integer in = (Integer) val;
ssy = new SnmpInt32(in);
}
else if (val instanceof SnmpObjectId)
{
ssy = (SnmpObjectId)val;
}
else
log.info("Unknown type for " + be);
}
catch (Exception e)
{
log.warn("getValueFor (" + be.mbean.toString() + ", "
+ be.attr.getName() + ": " + e.toString());
}
}
else
{
ssy = new SnmpNull();
log.info(NO_ENTRY_FOUND_FOR_OID + oid);
}
return ssy;
}
/**
* Set a jmx attribute
* @param oid The oid to set. This is translated into a mbean / attribute pair
* @param newVal The new value to set
* @return null on success, non-null on failure
* @throws ReadOnlyException If the referred entry is read only.
*/
private SnmpSyntax setValueFor(final SnmpObjectId oid, final SnmpSyntax newVal) throws ReadOnlyException
{
final boolean trace = log.isTraceEnabled();
BindEntry be = findBindEntryForOid(oid);
if (trace)
log.trace("setValueFor: found bind entry for " + oid);
SnmpSyntax ssy = null;
if (be != null)
{
if (trace)
log.trace("setValueFor: " + be.toString());
if (be.isReadWrite == false)
{
if (trace)
log.trace("setValueFor: this is marked read only");
throw new ReadOnlyException(oid);
}
try
{
Object val = null;
if (newVal instanceof SnmpOctetString)
{
val = newVal.toString();
}
else if (newVal instanceof SnmpInt32)
{
val = new Integer(((SnmpInt32)newVal).getValue());
}
else if (newVal instanceof SnmpUInt32)
{
val = new Long(((SnmpUInt32)newVal).getValue());
}
// TODO do more mumbo jumbo for type casting / changing
if (val != null)
{
Attribute at = new Attribute(be.attr.getName(), val);
server.setAttribute(be.mbean, at);
if (trace)
log.trace("setValueFor: set attribute in mbean-Server");
}
else
{
log.debug("Did not find a suitable data type for newVal " + newVal);
ssy = new SnmpNull();
}
// TODO
}
catch (Exception e )
{
log.debug("setValueFor: exception " + e.getMessage());
ssy = new SnmpNull();
}
}
else
{
ssy = new SnmpNull();
log.info(NO_ENTRY_FOUND_FOR_OID + oid);
}
return ssy;
}
/**
* Lookup a BinEntry on the given oid. If the oid ends in .0,
* then the .0 will be stripped of before the search.
* @param oid The oid look up.
* @return a bind entry or null.
*/
private BindEntry findBindEntryForOid(final SnmpObjectId oid) {
ComparableSnmpObjectId coid= new ComparableSnmpObjectId(oid);
if (coid.isLeaf())
{
coid = coid.removeLastPart();
}
BindEntry be = (BindEntry)bindings.get(coid);
return be;
}
/**
* Return the next oid that is larger than ours.
* @param oid the starting oid
* @param stayInSubtree if true, the next oid will not have a different prefix than the one of oid.
* @return the next oid or null if none found.
*/
private ComparableSnmpObjectId getNextOid(final ComparableSnmpObjectId oid, boolean stayInSubtree) {
ComparableSnmpObjectId coid = new ComparableSnmpObjectId(oid);
if (coid.isLeaf())
coid = coid.removeLastPart();
SortedSet ret;
ret= oidKeys.tailSet(oid); // get oids >= oid
Iterator it = ret.iterator();
ComparableSnmpObjectId roid=null;
/*
* If there are elements in the tail set, then
* - get first one.
* - if first is input (which it is supposed to be according to the contract of
* SortedSet.tailSet() , then get next, which is the
* one we look for.
*/
if (it.hasNext())
{
roid = (ComparableSnmpObjectId)it.next(); // oid
}
if (roid == null)
{
return null; // roid is null,
}
if (roid.compareTo(coid)==0) // input elment
{
// if there is a next element, then it is ours.
if (it.hasNext())
{
roid = (ComparableSnmpObjectId)it.next();
}
else
{
roid = null; // end of list
}
}
/*
* Check if still in subtree if requested to stay within
*/
if (stayInSubtree && roid != null)
{
ComparableSnmpObjectId parent = coid.removeLastPart();
if (!parent.isRootOf(roid))
roid = null;
}
return roid;
}
// Inner Class ---------------------------------------------------
/**
* An entry containing the mapping between oid and mbean/attribute
*
* @author <a href="mailto:pilhuhn@user.sf.net>">Heiko W. Rupp</a>
*/
private class BindEntry implements Comparable {
private final ComparableSnmpObjectId oid;
private ObjectName mbean;
private Attribute attr;
private String mName;
private String aName;
private boolean isReadWrite = false;
/**
* Constructs a new BindEntry
*
* @param oid
* The SNMP-oid, this entry will use.
* @param mbName
* The name of an MBean with attribute to query
* @param attrName
* The name of the attribute to query
*/
BindEntry(final String oidString, final String mbName, final String attrName) {
this(new ComparableSnmpObjectId(oidString),
mbName,
attrName);
}
/**
* Constructs a new BindEntry.
* @param coid The SNMP-oid, this entry will use.
* @param mbName The name of an MBean with attribute to query
* @param attrName The name of the attribute to query
*/
BindEntry(final ComparableSnmpObjectId coid, final String mbName, final String attrName) {
oid = coid;
this.mName = mbName;
this.aName = attrName;
try
{
mbean = new ObjectName(mbName);
attr = new Attribute(attrName, null);
}
catch (Exception e)
{
log.warn(e.toString());
mName = "-unset-";
aName = "-unset-";
}
}
/**
* A string representation of this BindEntry
*/
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append("[oid=");
buf.append(oid).append(", mbean=");
buf.append(mName).append(", attr=");
buf.append(aName).append(", rw=");
buf.append(isReadWrite).append("]");
return buf.toString();
}
public Attribute getAttr() {
return attr;
}
public ObjectName getMbean()
{
return mbean;
}
public ComparableSnmpObjectId getOid()
{
return oid;
}
/**
* Compare two BindEntries. Ordering is defined at oid-level.
*
* @param other
* The BindEntry to compare to.
* @return 0 on equals, 1 if this is bigger than other
*/
public int compareTo(Object other)
{
if (other == null)
throw new NullPointerException("Can't compare to NULL");
if (!(other instanceof BindEntry))
throw new ClassCastException("Parameter is no BindEntry");
// trivial case
if (this.equals(other))
return 0;
BindEntry obe = (BindEntry) other;
if (getOid().equals(obe.getOid()))
return 0;
int res =oid.compare(obe.getOid());
return res;
}
}
}