001: /*
002: * JBoss, Home of Professional Open Source.
003: * Copyright 2006, Red Hat Middleware LLC, and individual contributors
004: * as indicated by the @author tags. See the copyright.txt file in the
005: * distribution for a full listing of individual contributors.
006: *
007: * This is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU Lesser General Public License as
009: * published by the Free Software Foundation; either version 2.1 of
010: * the License, or (at your option) any later version.
011: *
012: * This software is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this software; if not, write to the Free
019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021: */
022: package org.jboss.jmx.adaptor.snmp.agent;
023:
024: import java.io.InputStream;
025: import java.net.InetAddress;
026: import java.util.Iterator;
027: import java.util.List;
028: import java.util.SortedMap;
029: import java.util.SortedSet;
030: import java.util.TreeMap;
031: import java.util.TreeSet;
032:
033: import javax.management.Attribute;
034: import javax.management.MBeanServer;
035: import javax.management.ObjectName;
036:
037: import org.jboss.jmx.adaptor.snmp.config.attribute.AttributeMappings;
038: import org.jboss.jmx.adaptor.snmp.config.attribute.ManagedBean;
039: import org.jboss.jmx.adaptor.snmp.config.attribute.MappedAttribute;
040: import org.jboss.logging.Logger;
041: import org.jboss.xb.binding.ObjectModelFactory;
042: import org.jboss.xb.binding.Unmarshaller;
043: import org.jboss.xb.binding.UnmarshallerFactory;
044: import org.opennms.protocols.snmp.SnmpAgentSession;
045: import org.opennms.protocols.snmp.SnmpInt32;
046: import org.opennms.protocols.snmp.SnmpNull;
047: import org.opennms.protocols.snmp.SnmpObjectId;
048: import org.opennms.protocols.snmp.SnmpOctetString;
049: import org.opennms.protocols.snmp.SnmpPduPacket;
050: import org.opennms.protocols.snmp.SnmpPduRequest;
051: import org.opennms.protocols.snmp.SnmpSyntax;
052: import org.opennms.protocols.snmp.SnmpUInt32;
053: import org.opennms.protocols.snmp.SnmpVarBind;
054:
055: /**
056: * Implement RequestHandler with mapping of snmp get/set requests
057: * to JMX mbean attribute gets/sets
058: *
059: * @author <a href="mailto:hwr@pilhuhn.de>">Heiko W. Rupp</a>
060: * @author <a href="mailto:dimitris@jboss.org">Dimitris Andreadis</a>
061: * @version $Revision: 57210 $
062: */
063: public class RequestHandlerImpl extends RequestHandlerSupport implements
064: Reconfigurable {
065: // Protected Data ------------------------------------------------
066:
067: private static final String NO_ENTRY_FOUND_FOR_OID = "No entry found for oid ";
068: private static final String SKIP_ENTRY = " - skipping entry";
069:
070: /** Bindings from oid to mbean */
071: protected SortedMap bindings = new TreeMap();
072:
073: private SortedSet oidKeys = null;
074:
075: /** Has this RequestHandler instance been initialized? */
076: private boolean initialized = false;
077:
078: // Constructors --------------------------------------------------
079:
080: /**
081: * Default CTOR
082: */
083: public RequestHandlerImpl() {
084: bindings = new TreeMap();
085: oidKeys = new TreeSet();
086: }
087:
088: // RequestHandler Implementation ---------------------------------
089:
090: /**
091: * Initialize
092: *
093: * @param resourceName A file containing get/set mappings
094: * @param server Our MBean-Server
095: * @param log The logger we use
096: * @param uptime The uptime of the snmp-agent subsystem.
097: */
098: public void initialize(String resourceName, MBeanServer server,
099: Logger log, Clock uptime) throws Exception {
100: log.debug("initialize() with res=" + resourceName);
101: super .initialize(resourceName, server, log, uptime);
102: if (resourceName != null)
103: initializeBindings();
104: else
105: log
106: .warn("No RequestHandlerResName configured, disabling snmp-get");
107:
108: initialized = true;
109: }
110:
111: // Reconfigurable Implementation ---------------------------------
112: /**
113: * Reconfigures the RequestHandler
114: */
115: public void reconfigure(String resName) throws Exception {
116: if (resName == null || resName.equals(""))
117: throw new IllegalArgumentException(
118: "Null or empty resName, cannot reconfigure");
119:
120: if (initialized == false)
121: throw new IllegalStateException(
122: "Cannot reconfigure, not initialized yet");
123:
124: this .resourceName = resName;
125:
126: // Wipe out old entries
127: bindings.clear();
128:
129: // Fetch them again
130: initializeBindings();
131: }
132:
133: // SnmpAgentHandler Implementation -------------------------------
134:
135: /**
136: * <P>
137: * This method is defined to handle SNMP Get requests that are received by
138: * the session. The request has already been validated by the system. This
139: * routine will build a response and pass it back to the caller.
140: * </P>
141: *
142: * @param pdu
143: * The SNMP pdu
144: * @param getNext
145: * The agent is requesting the lexically NEXT item after each
146: * item in the pdu.
147: *
148: * @return SnmpPduRequest filled in with the proper response, or null if
149: * cannot process NOTE: this might be changed to throw an exception.
150: */
151: public SnmpPduRequest snmpReceivedGet(SnmpPduPacket pdu,
152: boolean getNext) {
153: try {
154: SnmpPduRequest response = null;
155: int pduLength = pdu.getLength();
156: final boolean trace = log.isTraceEnabled();
157:
158: if (trace)
159: log.trace("requestId=" + pdu.getRequestId()
160: + ", pduLength=" + pduLength + ", getNext="
161: + getNext);
162:
163: SnmpVarBind[] vblist = new SnmpVarBind[pduLength];
164: int errorStatus = SnmpPduPacket.ErrNoError;
165: int errorIndex = 0;
166:
167: // Process for each varibind in the request
168: for (int i = 0; i < pduLength; i++) {
169: boolean good = true;
170: SnmpVarBind vb = pdu.getVarBindAt(i);
171: SnmpObjectId oid = vb.getName();
172: if (getNext) {
173: /*
174: * We call getNextOid() to find out what is the next valid OID
175: * instance in the supported MIB (sub-)tree. Assign that OID to the
176: * VB List and then proceed same as that of get request. If the
177: * passed oid is already the last, we flag it.
178: */
179: ComparableSnmpObjectId coid = new ComparableSnmpObjectId(
180: oid);
181: oid = getNextOid(coid, true);
182: if (oid == null) {
183: good = false;
184: } else {
185: pdu.setVarBindAt(i, new SnmpVarBind(oid));
186: }
187: }
188: if (oid != null)
189: vblist[i] = new SnmpVarBind(oid);
190: else
191: vblist[i] = new SnmpVarBind(vb.getName()); // oid passed in
192:
193: if (trace)
194: log.trace("oid=" + oid);
195:
196: SnmpSyntax result = null;
197: if (good && bindings != null)
198: result = getValueFor(oid);
199:
200: if (trace)
201: log.trace("got result of " + result);
202:
203: if (result == null || !good) {
204: errorStatus = SnmpPduPacket.ErrNoSuchName;
205: errorIndex = i + 1;
206: log.debug("Error Occured "
207: + vb.getName().toString());
208: } else {
209: vblist[i].setValue(result);
210: log.debug("Varbind[" + i + "] := "
211: + vblist[i].getName().toString());
212: log
213: .debug(" --> "
214: + vblist[i].getValue().toString());
215: }
216: } // for ...
217: response = new SnmpPduRequest(SnmpPduPacket.RESPONSE,
218: vblist);
219: response.setErrorStatus(errorStatus);
220: response.setErrorIndex(errorIndex);
221: return response;
222: } catch (Exception e) {
223: // TODO Auto-generated catch block
224: e.printStackTrace();
225: return null;
226: }
227: }
228:
229: /**
230: * <P>
231: * This method is defined to handle SNMP Set requests that are received by
232: * the session. The request has already been validated by the system. This
233: * routine will build a response and pass it back to the caller.
234: * </P>
235: *
236: * @param pdu
237: * The SNMP pdu
238: *
239: * @return SnmpPduRequest filled in with the proper response, or null if
240: * cannot process NOTE: this might be changed to throw an exception.
241: */
242: public SnmpPduRequest snmpReceivedSet(SnmpPduPacket pdu) {
243: final boolean trace = log.isTraceEnabled();
244: SnmpPduRequest response = null;
245: int errorStatus = SnmpPduPacket.ErrNoError;
246: int errorIndex = 0;
247: int k = pdu.getLength();
248: SnmpVarBind[] vblist = new SnmpVarBind[k];
249:
250: for (int i = 0; i < k; i++) {
251: SnmpVarBind vb = pdu.getVarBindAt(i);
252: vblist[i] = new SnmpVarBind(vb);
253: SnmpObjectId oid = vb.getName();
254: SnmpSyntax newVal = vb.getValue();
255: if (trace)
256: log.trace("set: received oid " + oid.toString()
257: + " with value " + newVal.toString());
258: SnmpSyntax result = null;
259: try {
260: result = setValueFor(oid, newVal);
261: } catch (ReadOnlyException e) {
262: errorStatus = SnmpPduPacket.ErrReadOnly;
263: errorIndex = i + 1;
264: }
265:
266: if (result != null) {
267: errorStatus = SnmpPduPacket.ErrReadOnly;
268: errorIndex = i + 1;
269: log.debug("Error occured " + vb.getName().toString());
270: }
271:
272: if (trace) {
273: log.trace("Varbind[" + i + "] := "
274: + vb.getName().toString());
275: log.trace(" --> " + vb.getValue().toString());
276: }
277: }
278: response = new SnmpPduRequest(SnmpPduPacket.RESPONSE, vblist);
279: response.setErrorStatus(errorStatus);
280: response.setErrorIndex(errorIndex);
281:
282: return response;
283: }
284:
285: /**
286: * <P>
287: * This method is defined to handle SNMP requests that are received by the
288: * session. The parameters allow the handler to determine the host, port,
289: * and community string of the received PDU
290: * </P>
291: *
292: * @param session
293: * The SNMP session
294: * @param manager
295: * The remote sender
296: * @param port
297: * The remote senders port
298: * @param community
299: * The community string
300: * @param pdu
301: * The SNMP pdu
302: *
303: */
304: public void snmpReceivedPdu(SnmpAgentSession session,
305: InetAddress manager, int port, SnmpOctetString community,
306: SnmpPduPacket pdu) {
307: log.error("Message from manager " + manager.toString()
308: + " on port " + port);
309: int cmd = pdu.getCommand();
310: log.error("Unsupported PDU command......... " + cmd);
311: }
312:
313: /**
314: * <P>
315: * This method is invoked if an error occurs in the session. The error code
316: * that represents the failure will be passed in the second parameter,
317: * 'error'. The error codes can be found in the class SnmpAgentSession
318: * class.
319: * </P>
320: *
321: * <P>
322: * If a particular PDU is part of the error condition it will be passed in
323: * the third parameter, 'pdu'. The pdu will be of the type SnmpPduRequest or
324: * SnmpPduTrap object. The handler should use the "instanceof" operator to
325: * determine which type the object is. Also, the object may be null if the
326: * error condition is not associated with a particular PDU.
327: * </P>
328: *
329: * @param session
330: * The SNMP Session
331: * @param error
332: * The error condition value.
333: * @param ref
334: * The PDU reference, or potentially null. It may also be an
335: * exception.
336: */
337: public void SnmpAgentSessionError(SnmpAgentSession session,
338: int error, Object ref) {
339: log.error("An error occured in the trap session");
340: log.error("Session error code = " + error);
341: if (ref != null) {
342: log.error("Session error reference: " + ref.toString());
343: }
344:
345: if (error == SnmpAgentSession.ERROR_EXCEPTION) {
346: synchronized (session) {
347: session.notify(); // close the session
348: }
349: }
350: }
351:
352: // Private -------------------------------------------------------
353:
354: /**
355: * Initialize the bindings from the file given in resourceName
356: */
357: private void initializeBindings() throws Exception {
358: log.debug("Reading resource: '" + resourceName + "'");
359:
360: ObjectModelFactory omf = new AttributeMappingsBinding();
361: InputStream is = null;
362: AttributeMappings mappings = null;
363: try {
364: // locate resource
365: is = getClass().getResourceAsStream(resourceName);
366:
367: // create unmarshaller
368: Unmarshaller unmarshaller = UnmarshallerFactory
369: .newInstance().newUnmarshaller();
370:
371: // let JBossXB do it's magic using the AttributeMappingsBinding
372: mappings = (AttributeMappings) unmarshaller.unmarshal(is,
373: omf, null);
374: } catch (Exception e) {
375: log.error("Accessing resource '" + resourceName + "'");
376: throw e;
377: } finally {
378: if (is != null) {
379: // close the XML stream
380: is.close();
381: }
382: }
383: if (mappings == null) {
384: log.warn("No bindings found in " + resourceName);
385: return;
386: }
387: log.debug("Found " + mappings.size() + " attribute mappings");
388: /**
389: * We have the MBeans now. Put them into the bindungs.
390: */
391:
392: Iterator it = mappings.iterator();
393: while (it.hasNext()) {
394: ManagedBean mmb = (ManagedBean) it.next();
395: String oidPrefix = mmb.getOidPrefix();
396: List attrs = mmb.getAttributes();
397: Iterator aIt = attrs.iterator();
398: while (aIt.hasNext()) {
399: MappedAttribute ma = (MappedAttribute) aIt.next();
400: String oid;
401: if (oidPrefix != null)
402: oid = oidPrefix + ma.getOid();
403: else
404: oid = ma.getOid();
405:
406: BindEntry be = new BindEntry(oid, mmb.getName(), ma
407: .getName());
408: be.isReadWrite = ma.isReadWrite();
409:
410: ComparableSnmpObjectId coid = new ComparableSnmpObjectId(
411: oid);
412:
413: if (log.isTraceEnabled())
414: log.trace("New bind entry " + be);
415: if (bindings.containsKey(coid)) {
416: log.info("Duplicate oid " + oid + SKIP_ENTRY);
417: continue;
418: }
419: if (mmb.getName() == null || mmb.getName().equals("")) {
420: log.info("Invalid mbean name for oid " + oid
421: + SKIP_ENTRY);
422: continue;
423: }
424: if (ma.getName() == null || ma.getName().equals("")) {
425: log.info("Invalid attribute name " + ma.getName()
426: + " for oid " + oid + SKIP_ENTRY);
427: continue;
428: }
429: bindings.put(coid, be);
430: oidKeys.add(coid);
431:
432: }
433: }
434: }
435:
436: /**
437: * Return the current value for the given oid
438: *
439: * @param oid
440: * The oid we want a value for
441: * @return SnmpNull if no value present
442: */
443: private SnmpSyntax getValueFor(final SnmpObjectId oid) {
444:
445: BindEntry be = findBindEntryForOid(oid);
446: SnmpSyntax ssy = null;
447: if (be != null) {
448: if (log.isTraceEnabled())
449: log.trace("Found entry " + be.toString() + " for oid "
450: + oid);
451:
452: try {
453: Object val = server.getAttribute(be.mbean, be.attr
454: .getName());
455:
456: if (val instanceof Long) {
457: Long uin = (Long) val;
458: ssy = new SnmpUInt32(uin);
459: } else if (val instanceof String) {
460: String in = (String) val;
461: ssy = new SnmpOctetString(in.getBytes());
462: } else if (val instanceof Integer) {
463: Integer in = (Integer) val;
464: ssy = new SnmpInt32(in);
465: } else if (val instanceof SnmpObjectId) {
466: ssy = (SnmpObjectId) val;
467: } else
468: log.info("Unknown type for " + be);
469: } catch (Exception e) {
470: log.warn("getValueFor (" + be.mbean.toString() + ", "
471: + be.attr.getName() + ": " + e.toString());
472: }
473: } else {
474: ssy = new SnmpNull();
475: log.info(NO_ENTRY_FOUND_FOR_OID + oid);
476: }
477: return ssy;
478: }
479:
480: /**
481: * Set a jmx attribute
482: * @param oid The oid to set. This is translated into a mbean / attribute pair
483: * @param newVal The new value to set
484: * @return null on success, non-null on failure
485: * @throws ReadOnlyException If the referred entry is read only.
486: */
487: private SnmpSyntax setValueFor(final SnmpObjectId oid,
488: final SnmpSyntax newVal) throws ReadOnlyException {
489: final boolean trace = log.isTraceEnabled();
490:
491: BindEntry be = findBindEntryForOid(oid);
492:
493: if (trace)
494: log.trace("setValueFor: found bind entry for " + oid);
495:
496: SnmpSyntax ssy = null;
497: if (be != null) {
498: if (trace)
499: log.trace("setValueFor: " + be.toString());
500:
501: if (be.isReadWrite == false) {
502: if (trace)
503: log.trace("setValueFor: this is marked read only");
504:
505: throw new ReadOnlyException(oid);
506: }
507: try {
508: Object val = null;
509: if (newVal instanceof SnmpOctetString) {
510: val = newVal.toString();
511: } else if (newVal instanceof SnmpInt32) {
512: val = new Integer(((SnmpInt32) newVal).getValue());
513: } else if (newVal instanceof SnmpUInt32) {
514: val = new Long(((SnmpUInt32) newVal).getValue());
515: }
516: // TODO do more mumbo jumbo for type casting / changing
517:
518: if (val != null) {
519: Attribute at = new Attribute(be.attr.getName(), val);
520: server.setAttribute(be.mbean, at);
521: if (trace)
522: log
523: .trace("setValueFor: set attribute in mbean-Server");
524: } else {
525: log
526: .debug("Did not find a suitable data type for newVal "
527: + newVal);
528: ssy = new SnmpNull();
529: }
530: // TODO
531: } catch (Exception e) {
532: log.debug("setValueFor: exception " + e.getMessage());
533: ssy = new SnmpNull();
534: }
535: } else {
536: ssy = new SnmpNull();
537: log.info(NO_ENTRY_FOUND_FOR_OID + oid);
538: }
539: return ssy;
540: }
541:
542: /**
543: * Lookup a BinEntry on the given oid. If the oid ends in .0,
544: * then the .0 will be stripped of before the search.
545: * @param oid The oid look up.
546: * @return a bind entry or null.
547: */
548: private BindEntry findBindEntryForOid(final SnmpObjectId oid) {
549:
550: ComparableSnmpObjectId coid = new ComparableSnmpObjectId(oid);
551:
552: if (coid.isLeaf()) {
553: coid = coid.removeLastPart();
554: }
555: BindEntry be = (BindEntry) bindings.get(coid);
556:
557: return be;
558: }
559:
560: /**
561: * Return the next oid that is larger than ours.
562: * @param oid the starting oid
563: * @param stayInSubtree if true, the next oid will not have a different prefix than the one of oid.
564: * @return the next oid or null if none found.
565: */
566: private ComparableSnmpObjectId getNextOid(
567: final ComparableSnmpObjectId oid, boolean stayInSubtree) {
568: ComparableSnmpObjectId coid = new ComparableSnmpObjectId(oid);
569:
570: if (coid.isLeaf())
571: coid = coid.removeLastPart();
572:
573: SortedSet ret;
574: ret = oidKeys.tailSet(oid); // get oids >= oid
575: Iterator it = ret.iterator();
576: ComparableSnmpObjectId roid = null;
577:
578: /*
579: * If there are elements in the tail set, then
580: * - get first one.
581: * - if first is input (which it is supposed to be according to the contract of
582: * SortedSet.tailSet() , then get next, which is the
583: * one we look for.
584: */
585: if (it.hasNext()) {
586: roid = (ComparableSnmpObjectId) it.next(); // oid
587: }
588:
589: if (roid == null) {
590: return null; // roid is null,
591: }
592:
593: if (roid.compareTo(coid) == 0) // input elment
594: {
595: // if there is a next element, then it is ours.
596: if (it.hasNext()) {
597: roid = (ComparableSnmpObjectId) it.next();
598: } else {
599: roid = null; // end of list
600: }
601: }
602:
603: /*
604: * Check if still in subtree if requested to stay within
605: */
606: if (stayInSubtree && roid != null) {
607: ComparableSnmpObjectId parent = coid.removeLastPart();
608: if (!parent.isRootOf(roid))
609: roid = null;
610: }
611:
612: return roid;
613: }
614:
615: // Inner Class ---------------------------------------------------
616:
617: /**
618: * An entry containing the mapping between oid and mbean/attribute
619: *
620: * @author <a href="mailto:pilhuhn@user.sf.net>">Heiko W. Rupp</a>
621: */
622: private class BindEntry implements Comparable {
623: private final ComparableSnmpObjectId oid;
624:
625: private ObjectName mbean;
626: private Attribute attr;
627: private String mName;
628: private String aName;
629: private boolean isReadWrite = false;
630:
631: /**
632: * Constructs a new BindEntry
633: *
634: * @param oid
635: * The SNMP-oid, this entry will use.
636: * @param mbName
637: * The name of an MBean with attribute to query
638: * @param attrName
639: * The name of the attribute to query
640: */
641: BindEntry(final String oidString, final String mbName,
642: final String attrName) {
643: this (new ComparableSnmpObjectId(oidString), mbName,
644: attrName);
645: }
646:
647: /**
648: * Constructs a new BindEntry.
649: * @param coid The SNMP-oid, this entry will use.
650: * @param mbName The name of an MBean with attribute to query
651: * @param attrName The name of the attribute to query
652: */
653: BindEntry(final ComparableSnmpObjectId coid,
654: final String mbName, final String attrName) {
655: oid = coid;
656: this .mName = mbName;
657: this .aName = attrName;
658: try {
659: mbean = new ObjectName(mbName);
660: attr = new Attribute(attrName, null);
661:
662: } catch (Exception e) {
663: log.warn(e.toString());
664: mName = "-unset-";
665: aName = "-unset-";
666: }
667: }
668:
669: /**
670: * A string representation of this BindEntry
671: */
672: public String toString() {
673: StringBuffer buf = new StringBuffer();
674: buf.append("[oid=");
675: buf.append(oid).append(", mbean=");
676: buf.append(mName).append(", attr=");
677: buf.append(aName).append(", rw=");
678: buf.append(isReadWrite).append("]");
679:
680: return buf.toString();
681: }
682:
683: public Attribute getAttr() {
684: return attr;
685: }
686:
687: public ObjectName getMbean() {
688: return mbean;
689: }
690:
691: public ComparableSnmpObjectId getOid() {
692: return oid;
693: }
694:
695: /**
696: * Compare two BindEntries. Ordering is defined at oid-level.
697: *
698: * @param other
699: * The BindEntry to compare to.
700: * @return 0 on equals, 1 if this is bigger than other
701: */
702: public int compareTo(Object other) {
703: if (other == null)
704: throw new NullPointerException("Can't compare to NULL");
705:
706: if (!(other instanceof BindEntry))
707: throw new ClassCastException(
708: "Parameter is no BindEntry");
709:
710: // trivial case
711: if (this .equals(other))
712: return 0;
713:
714: BindEntry obe = (BindEntry) other;
715: if (getOid().equals(obe.getOid()))
716: return 0;
717:
718: int res = oid.compare(obe.getOid());
719: return res;
720: }
721:
722: }
723:
724: }
|