/*******************************************************************************
* 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/
*******************************************************************************/
/*
* OCA CONTRIBUTION ACKNOWLEDGEMENT - NOT PART OF LEGAL BOILERPLATE
* DO NOT DUPLICATE THIS COMMENT BLOCK WHEN CREATING NEW FILES!
*
* This file was contributed to the OpenNMS(R) project under the
* terms of the OpenNMS Contributor Agreement (OCA). For details on
* the OCA, see http://www.opennms.org/index.php/Contributor_Agreement
*
* Contributed under the terms of the OCA by:
*
* Bobby Krupczak <rdk@krupczak.org>
* THE KRUPCZAK ORGANIZATION, LLC
* http://www.krupczak.org/
*/
/**
* OpenNMS XMP Collector class allows for the querying of XMP-enabled
* devices by the OpenNMS network management software suite. XMP is
* the <b>X</b>ML <b>M</b>management <b>P</b>rotocol; One XmpCollector
* object is instantiated per OpenNMS instance and its responsible for
* querying all XMP-enabled systems. Consequently, we need to be
* careful about leaving XMP sessions open for too long.
* @author <a href="mailto:rdk@krupczak.org">Bobby Krupczak</a>
* @version $Id: XmpCollector.java 38 2008-07-24 13:39:32Z rdk $
*/
package org.opennms.netmgt.protocols.xmp.collector;
import java.io.File;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.Date;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.krupczak.Xmp.SocketOpts;
import org.krupczak.Xmp.Xmp;
import org.krupczak.Xmp.XmpMessage;
import org.krupczak.Xmp.XmpSession;
import org.krupczak.Xmp.XmpVar;
import org.opennms.core.utils.ParameterMap;
import org.opennms.core.utils.ThreadCategory;
import org.opennms.netmgt.collectd.CollectionAgent;
import org.opennms.netmgt.collectd.ServiceCollector;
import org.opennms.netmgt.config.collector.AttributeGroup;
import org.opennms.netmgt.config.collector.AttributeGroupType;
import org.opennms.netmgt.config.collector.CollectionSet;
import org.opennms.netmgt.config.xmpConfig.XmpConfig;
import org.opennms.netmgt.config.xmpDataCollection.Group;
import org.opennms.netmgt.config.xmpDataCollection.MibObj;
import org.opennms.netmgt.config.xmpDataCollection.XmpCollection;
import org.opennms.netmgt.model.RrdRepository;
import org.opennms.netmgt.model.events.EventProxy;
import org.opennms.netmgt.protocols.xmp.config.XmpAgentConfig;
import org.opennms.netmgt.protocols.xmp.config.XmpConfigFactory;
import org.opennms.netmgt.protocols.xmp.config.XmpPeerFactory;
public class XmpCollector implements ServiceCollector {
/* class variables and methods *********************** */
static final String SERVICE_NAME = "XMP";
/* instance variables ******************************** */
int xmpPort;
int timeout; /* millseconds */
int retries;
Set<CollectionAgent> setOfNodes;
SocketOpts sockopts;
String authenUser;
/* constructors ************************************* */
/**
* <p>Constructor for XmpCollector.</p>
*/
public XmpCollector()
{
log().debug("XmpCollector created");
// initialize collections and containers for storing
// list of systems to query
setOfNodes = new HashSet<CollectionAgent>();
// defaults
xmpPort = Xmp.XMP_PORT;
sockopts = new SocketOpts();
authenUser = new String("xmpUser");
timeout = 3000; /* millseconds */
return;
}
/* private methods *********************************** */
private ThreadCategory log() {
return ThreadCategory.getInstance(getClass());
}
// handle scalar query and put in a single collection resource
// devoted to scalars; check sysUptime if its present
// and indicate if data should be persisted
private boolean handleScalarQuery(String groupName,
XmpCollectionSet collectionSet,
long oldUptime,
XmpSession session,
XmpCollectionResource scalarResource,
XmpVar[] queryVars)
{
XmpMessage reply;
AttributeGroupType agt;
AttributeGroup ag;
long newUptime;
int i;
XmpVar[] vars;
XmpCollectionAttribute aVar;
XmpCollectionAttributeType attribType;
//log().debug("sending scalar query");
reply = session.queryVars(queryVars);
if (reply == null) {
log().warn("collect: query to "+
collectionSet.getCollectionAgent()+" failed, "+
Xmp.errorStatusToString(session.getErrorStatus()));
return false;
}
agt = new AttributeGroupType(groupName,"ignore");
ag = new AttributeGroup(scalarResource,agt);
// for each variable in reply, store it in collectionSet
// hack alert: somewhere in some query, we asked for
// sysUptime; find it save value for later
// vars[i] should match up with mibObjects[i] !!!
vars = reply.getMIBVars();
newUptime = 0;
for (i=0; i<vars.length; i++) {
if (vars[i].getMibName().equals("core") &&
vars[i].getObjName().equals("sysUpTime")) {
newUptime = vars[i].getValueLong();
}
// put in collectionSet via this attribute group
attribType = new XmpCollectionAttributeType(vars[i],agt);
aVar = new XmpCollectionAttribute(scalarResource,
attribType,
vars[i].getObjName(),
vars[i]);
ag.addAttribute(aVar);
}
if (newUptime > oldUptime) {
collectionSet.ignorePersistFalse();
}
if (newUptime > 0) {
// save the agent's sysUpTime in the CollectionAgent
collectionSet.getCollectionAgent().setSavedSysUpTime(newUptime);
}
scalarResource.addAttributeGroup(ag);
return true;
} /* handleScalarQuery */
// handle a tabular query, save each row in its own
// collection resource
private boolean handleTableQuery(String groupName,
String resourceType,
XmpCollectionSet collectionSet,
String[] tableInfo,
XmpSession session,
XmpVar[] queryVars)
{
int numColumns,numRows;
XmpMessage reply;
int i,j;
XmpVar[] vars;
String targetInstance;
numColumns = queryVars.length;
// make sure we have an instance or * for all rows; preserve
// passed in value as targetInstance so we know if we are
// are going to use targetInstance for saving results or
// use returned instance for saving values
// if resourceType is present, we use it as a subDir in
// our RRD dir
targetInstance = tableInfo[2];
if ((tableInfo[2] == null) || (tableInfo[2].length() == 0)) {
tableInfo[2] = new String("*");
targetInstance = null;
}
log().debug("sending table query "+tableInfo[0]+","+
tableInfo[1]+","+tableInfo[2]+" target: "+targetInstance);
reply = session.queryTableVars(tableInfo,0,queryVars);
if (reply == null) {
log().warn("collect: query to "+
collectionSet.getCollectionAgent()+" failed, "+
Xmp.errorStatusToString(session.getErrorStatus()));
return false;
}
vars = reply.getMIBVars();
// we have to go through the reply and find out how
// many rows we have
// for each row: create a CollectionResource of
// appropriate type, instance, etc.
// create AttributeGroup to put
// the values in
numRows = vars.length / numColumns;
log().info("query returned valid table data for "+groupName+" numRows="+
numRows+" numColumns="+numColumns);
for (i=0; i<numRows; i++) {
XmpCollectionResource rowResource;
AttributeGroup ag;
AttributeGroupType agt;
String rowInstance;
// determine instance for this row
// we use either the rowInstance or targetInstance for
// naming the instance for saving RRD file; if user
// wanted all rows (blank instance), then we will use
// the returned instance; if user specified an instance
// we use that instance for specifying the RRD file
// and collection resource
rowInstance = vars[i*numColumns].getKey();
// instead of using '*' for the nodeTypeName, use the
// table name so that the proper rrd file is spec'd
if (targetInstance != null)
rowResource = new XmpCollectionResource(collectionSet.getCollectionAgent(),resourceType, tableInfo[1],targetInstance);
else
rowResource = new XmpCollectionResource(collectionSet.getCollectionAgent(),resourceType, tableInfo[1],rowInstance);
agt = new AttributeGroupType(groupName,"all");
ag = new AttributeGroup(rowResource,agt);
log().debug("queryTable instance="+rowInstance);
for (j=0; j<numColumns; j++) {
XmpCollectionAttributeType attribType = new XmpCollectionAttributeType(vars[i*numColumns+j],agt);
XmpCollectionAttribute aVar =
new XmpCollectionAttribute(rowResource,
attribType,
vars[i*numColumns+j].getObjName(),
vars[i*numColumns+j]);
ag.addAttribute(aVar);
} /* for each column */
rowResource.addAttributeGroup(ag);
collectionSet.addResource(rowResource);
log().info("query table data adding row resource "+rowResource);
} /* for each row returned */
return true;
} /* handleTableQuery() */
/* public methods ************************************ */
/**
* {@inheritDoc}
*
* initialize our XmpCollector with global parameters *
*/
public void initialize(Map<String, String> parameters)
{
// parameters come from collectd-configuration.xml
// and they are the service parameters specified in xml
// with keyname and value
// parameter key=collection value=default
// initialize our data collection factory
log().debug("initialize(params) called");
try {
XmpCollectionFactory.init();
} catch (Throwable e) {
log().error("initialize: XmpCollectionFactory failed to initialize");
throw new UndeclaredThrowableException(e);
}
try {
XmpPeerFactory.init();
} catch (Throwable e) {
log().error("initialize: XmpPeerFactory failed to initialize");
throw new UndeclaredThrowableException(e);
}
// initialize authenUser, port, timeout, other parameters
// want a xmp-config.xml for port, authenUser, timeout, etc.
try {
XmpConfigFactory.init();
} catch (Throwable e) {
log().error("initialize: config factory failed to initialize");
throw new UndeclaredThrowableException(e);
}
// initialize an RRD repository with various parameters
// /opt/opennms/share/rrd/snmp/
File f = new File(XmpCollectionFactory.getInstance().getRrdPath());
if (!f.isDirectory()) {
if (!f.mkdirs()) {
throw new RuntimeException("Unable to create RRD file " + "repository. Path doesn't already exist and could not make directory: " +
XmpCollectionFactory.getInstance().getRrdPath());
}
}
// get our top-level object for our protocol config file,
// xmp-config.xml, already parsed and ready to examine
XmpConfig protoConfig = XmpConfigFactory.getInstance().getXmpConfig();
if (protoConfig.hasPort())
xmpPort = protoConfig.getPort();
if (protoConfig.hasTimeout())
timeout = protoConfig.getTimeout();
// authenUser is optional; if it is present, it will
// be non-null
if (protoConfig.getAuthenUser() != null)
authenUser = protoConfig.getAuthenUser();
log().debug("initialize: authenUser '"+authenUser+"' port "+xmpPort);
log().debug("initialize: keystore found? "+sockopts.getKeystoreFound());
return;
} /* initialize() */
/**
* {@inheritDoc}
*
* initialize the querying of a particular agent/interface with
* parameters specific to this agent/interface *
*/
public void initialize(CollectionAgent agent, Map<String, Object> parameters)
{
log().debug("initialize agent/params called for "+agent);
// add an agent to our set to query
setOfNodes.add(agent);
// we are using whichever CollectionAgent instantiation
// is passed into us.
// parameters include SERVICE/service-name
// superset of parameters passed in main initialize
// ignore for now; other parameters like collection name
return;
}
/**
* Release/stop all querying of agents/interfaces and release
* state associated with them *
*/
public void release()
{
log().info("release()");
// orphan existing set thus making them available
// for garbage collection
setOfNodes = new HashSet<CollectionAgent>();
return;
}
/**
* {@inheritDoc}
*
* Release/stop querying a particular agent *
*/
public void release(CollectionAgent agent)
{
log().info("release agent called for "+agent);
// remove agent from set; ignore return value
setOfNodes.remove(agent);
return;
}
/**
* who am I and what am I ? *
*
* @return a {@link java.lang.String} object.
*/
public String serviceName() { return SERVICE_NAME; }
/**
* {@inheritDoc}
*
* Collect data, via XMP, from a particular agent EventProxy is
* used to send opennms events into the system in case a
* collection fails or if a system is back working again after a
* failure (suceed event). But otherwise, no events sent if
* collection succeeds. Collect is called once per agent per
* collection cycle. Parameters are a map of String Key/String
* Value passed in. Keys come from collectd config
*/
public CollectionSet collect(CollectionAgent agent, EventProxy eproxy,
Map<String, Object> parameters)
{
XmpCollectionSet collectionSet;
XmpSession session;
long oldUptime;
int i;
XmpCollection collection;
XmpCollectionResource scalarResource;
log().debug("collect agent "+agent);
oldUptime = 0;
// First go to the peer factory
XmpAgentConfig peerConfig = XmpPeerFactory.getInstance().getAgentConfig(agent.getInetAddress());
authenUser = peerConfig.getAuthenUser();
timeout = (int)peerConfig.getTimeout();
retries = peerConfig.getRetry();
xmpPort = peerConfig.getPort();
if (parameters.get("authenUser") != null)
authenUser = ParameterMap.getKeyedString(parameters, "authenUser", null);
if (parameters.get("timeout") != null) {
timeout = ParameterMap.getKeyedInteger(parameters, "timeout", 3000);
}
if (parameters.get("retry") != null) {
retries = ParameterMap.getKeyedInteger(parameters, "retries", 0);
}
parameters.get("collection");
if (parameters.get("port") != null) {
xmpPort = Integer.valueOf((String)parameters.get("port"));
}
//log().debug("collect got parameters for "+agent);
String collectionName = ParameterMap.getKeyedString(parameters, "collection", null);
//log().debug("XMP collection name "+collectionName);
// collectionName tells us what set of data to get
// this would/will come from xmp-datacollection.xml
if (collectionName == null) {
// log this!
log().warn("collect found no collectionName for "+agent);
return null;
}
//log().debug("collect got collectionName for "+agent);
log().debug("XmpCollector: collect "+collectionName+" from "+agent);
// get/create our collections set
collectionSet = new XmpCollectionSet(agent);
collectionSet.setCollectionTimestamp(new Date());
collectionSet.setStatusFailed(); // default
collectionSet.ignorePersistTrue(); // default not to persist
// default collection resource for putting scalars in
scalarResource = new XmpCollectionResource(agent,null,"node",null);
collectionSet.addResource(scalarResource);
// get the collection, again, from the data config file factory
// because it could have changed; its not necessarily re-parsed,
// but we are getting another copy of it for each agent
// that we are queried each time we are invoked
collection = XmpCollectionFactory.getInstance().getXmpCollection(collectionName);
if (collection == null) {
log().warn("collect found no matching collection for "+agent);
return collectionSet;
}
oldUptime = agent.getSavedSysUpTime();
// open/get a session with the target agent
log().debug("collect: attempting to open XMP session with "+agent.getInetAddress()+":"+xmpPort+","+authenUser);
// Set the SO_TIMEOUT, why don't we...
sockopts.setConnectTimeout(timeout);
session = new XmpSession(sockopts, agent.getInetAddress(), xmpPort,authenUser);
if (session.isClosed()) {
log().warn("collect unable to open XMP session with "+agent);
return collectionSet;
}
log().debug("collect: successfully opened XMP session with"+agent);
// for each group within the collection (from data config)
// query agent
for (Group group : collection.getGroups().getGroup()) {
// get name of group and MIB objects in group
String groupName = group.getName();
MibObj[] mibObjects = group.getMibObj();
XmpVar[] vars = new XmpVar[mibObjects.length];
log().debug("collecting XMP group "+groupName+" with "+mibObjects.length+" mib objects");
// prepare the query vars
for (i=0 ; i< mibObjects.length; i++) {
vars[i] = new XmpVar(mibObjects[i].getMib(),
mibObjects[i].getVar(),
mibObjects[i].getInstance(),
"",
Xmp.SYNTAX_NULLSYNTAX);
} /* for each MIB object in a particular group */
if ((mibObjects[0].getTable() != null) &&
(mibObjects[0].getTable().length() != 0)) {
String[] tableInfo = new String[3];
tableInfo[0] = mibObjects[0].getMib();
tableInfo[1] = mibObjects[0].getTable();
tableInfo[2] = mibObjects[0].getInstance();
// tabular query
if (handleTableQuery(group.getName(),
group.getResourceType(),
collectionSet,
tableInfo,
session,
vars) == false) {
session.closeSession();
return collectionSet;
}
}
else {
// scalar query
if (handleScalarQuery(group.getName(),
collectionSet,
oldUptime,
session,
scalarResource,
vars) == false) {
session.closeSession();
return collectionSet;
}
}
} /* for each Group in collection Group list */
// done talking to this agent; close session
session.closeSession();
// Did agent restart since last query? If so, set
// ignorePersist to true; our scalar
// query will have handled this by searching returned
// MIB objects for sysUpTime
// WARNING, EACH COLLECTION SHOULD HAVE A SCALAR QUERY THAT
// INCLUDES Core.sysUpTime
collectionSet.setStatus(ServiceCollector.COLLECTION_SUCCEEDED);
log().debug("XMP collect finished for "+collectionName+", uptime for "+agent+" is "+agent.getSavedSysUpTime());
return collectionSet;
}
/** {@inheritDoc} */
public RrdRepository getRrdRepository(String collectionName)
{
log().debug("XMP getRrdRepository called for "+collectionName);
// return the Rrd that I initialized but
// I don't have to put data in it; initialize
// it with the defaults, as example, that SNMP uses
// in datacollection-config.xml
return XmpCollectionFactory.getInstance().getRrdRepository(collectionName);
}
} /* class XmpCollector */