/******************************************************************************* * This file is part of OpenNMS(R). * * Copyright (C) 2010-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/ *******************************************************************************/ package org.opennms.netmgt.dao; import java.io.File; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.lang.StringUtils; import org.opennms.core.utils.ConfigFileConstants; import org.opennms.core.utils.ThreadCategory; import org.opennms.netmgt.config.DataCollectionConfigDao; import org.opennms.netmgt.config.MibObject; import org.opennms.netmgt.config.datacollection.DatacollectionConfig; import org.opennms.netmgt.config.datacollection.Group; import org.opennms.netmgt.config.datacollection.Groups; import org.opennms.netmgt.config.datacollection.MibObj; import org.opennms.netmgt.config.datacollection.ResourceType; import org.opennms.netmgt.config.datacollection.SnmpCollection; import org.opennms.netmgt.config.datacollection.SystemDef; import org.opennms.netmgt.config.datacollection.SystemDefChoice; import org.opennms.netmgt.config.datacollection.Systems; import org.opennms.netmgt.dao.castor.DataCollectionConfigParser; import org.opennms.netmgt.model.RrdRepository; import org.springframework.core.io.Resource; /** * DefaultDataCollectionConfigDao * * <p>This class is the main repository for SNMP data collection configuration * information used by the SNMP service monitor. When this class is loaded it * reads the SNNMP data collection configuration into memory.</p> * * @author <a href="mail:agalue@opennms.org">Alejandro Galue</a> */ public class DefaultDataCollectionConfigDao extends AbstractJaxbConfigDao<DatacollectionConfig, DatacollectionConfig> implements DataCollectionConfigDao { private String m_configDirectory; // have we validated the config since last reloading? private boolean m_validated = false; private RuntimeException m_validationException = null; public DefaultDataCollectionConfigDao() { super(DatacollectionConfig.class, "data-collection"); } @Override protected DatacollectionConfig loadConfig(final Resource resource) { m_validated = false; m_validationException = null; return super.loadConfig(resource); } @Override public DatacollectionConfig translateConfig(final DatacollectionConfig config) { final DataCollectionConfigParser parser = new DataCollectionConfigParser(getConfigDirectory()); // Updating Configured Collections for (final SnmpCollection collection : config.getSnmpCollectionCollection()) { parser.parseCollection(collection); } // Create a special collection to hold all resource types, because they should be defined only once. final SnmpCollection resourceTypeCollection = new SnmpCollection(); resourceTypeCollection.setName("__resource_type_collection"); for (final ResourceType rt : parser.getAllResourceTypes()) { resourceTypeCollection.addResourceType(rt); } resourceTypeCollection.setGroups(new Groups()); resourceTypeCollection.setSystems(new Systems()); config.getSnmpCollectionCollection().add(0, resourceTypeCollection); return config; } public void setConfigDirectory(String configDirectory) { this.m_configDirectory = configDirectory; } public String getConfigDirectory() { if (m_configDirectory == null) { final StringBuffer sb = new StringBuffer(ConfigFileConstants.getHome()); sb.append(File.separator); sb.append("etc"); sb.append(File.separator); sb.append("datacollection"); sb.append(File.separator); m_configDirectory = sb.toString(); } return m_configDirectory; } public String getSnmpStorageFlag(final String collectionName) { final SnmpCollection collection = getSnmpCollection(collectionName); return collection == null ? null : collection.getSnmpStorageFlag(); } public List<MibObject> getMibObjectList(final String cName, final String aSysoid, final String anAddress, final int ifType) { if (log().isDebugEnabled()) log().debug("getMibObjectList: collection: " + cName + " sysoid: " + aSysoid + " address: " + anAddress + " ifType: " + ifType); if (aSysoid == null) { if (log().isDebugEnabled()) log().debug("getMibObjectList: aSysoid parameter is NULL..."); return new ArrayList<MibObject>(); } // Retrieve the appropriate Collection object final SnmpCollection collection = getSnmpCollection(cName); if (collection == null) { return Collections.emptyList(); } // First build a list of SystemDef objects which "match" the passed // sysoid and IP address parameters. The SystemDef object must match // on both the sysoid AND the IP address. // // SYSOID MATCH // // A SystemDef object's sysoid value may be a complete system object // identifier or it may be a mask (a partial sysoid). // // If the sysoid is not a mask, the 'aSysoid' string must equal the // sysoid value exactly in order to match. // // If the sysoid is a mask, the 'aSysoid' string need only start with // the sysoid mask value in order to match // // For example, a sysoid mask of ".1.3.6.1.4.1.11." would match any // Hewlett-Packard product which had this sysoid prefix (which should // include all of them). // // IPADDRESS MATCH // // In order to match on IP Address one of the following must be true: // // The SystemDef's IP address list (ipList) must contain the 'anAddress' // parm (must be an exact match) // // OR // // The 'anAddress' parm must have the same prefix as one of the // SystemDef's IP address mask list (maskList) entries. // // NOTE: A SystemDef object which contains an empty IP list and // an empty Mask list matches ALL IP addresses (default is INCLUDE). final List<SystemDef> systemList = new ArrayList<SystemDef>(); for (final SystemDef system : collection.getSystems().getSystemDefCollection()) { // Match on sysoid? boolean bMatchSysoid = false; // Retrieve sysoid for this SystemDef and/ set the isMask boolean. boolean isMask = false; String currSysoid = null; SystemDefChoice sysChoice = system.getSystemDefChoice(); if (sysChoice.getSysoid() != null) { currSysoid = sysChoice.getSysoid(); } else if (sysChoice.getSysoidMask() != null) { currSysoid = sysChoice.getSysoidMask(); isMask = true; } if (currSysoid != null) { if (isMask) { // SystemDef's sysoid is a mask, 'aSysoid' need only // start with the sysoid mask in order to match if (aSysoid.startsWith(currSysoid)) { if (log().isDebugEnabled()) log().debug("getMibObjectList: includes sysoid " + aSysoid + " for system <name>: " + system.getName()); bMatchSysoid = true; } } else { // System's sysoid is not a mask, 'aSysoid' must // match the sysoid exactly. if (aSysoid.equals(currSysoid)) { if (log().isDebugEnabled()) log().debug("getMibObjectList: includes sysoid " + aSysoid + " for system <name>: " + system.getName()); bMatchSysoid = true; } } } // Match on ipAddress? boolean bMatchIPAddress = true; // default is INCLUDE if (bMatchSysoid == true) { if (anAddress != null) { List<String> addrList = null; List<String> maskList = null; if (system.getIpList() != null) { addrList = system.getIpList().getIpAddrCollection(); maskList = system.getIpList().getIpAddrMaskCollection(); } // If either Address list or Mask list exist then 'anAddress' // must be included by one of them if (addrList != null && addrList.size() > 0 || maskList != null && maskList.size() > 0) { bMatchIPAddress = false; } // First see if address is in list of specific addresses if (addrList != null && addrList.size() > 0) { if (addrList.contains(anAddress)) { if (log().isDebugEnabled()) log().debug("getMibObjectList: addrList exists and does include IP address " + anAddress + " for system <name>: " + system.getName()); bMatchIPAddress = true; } } // If still no match, see if address matches any of the masks if (bMatchIPAddress == false) { if (maskList != null && maskList.size() > 0) { for (final String currMask : maskList) { if (anAddress.indexOf(currMask) == 0) { if (log().isDebugEnabled()) log().debug("getMibObjectList: anAddress '" + anAddress + "' matches mask '" + currMask + "'"); bMatchIPAddress = true; break; } } } } } } if (bMatchSysoid && bMatchIPAddress) { if (log().isDebugEnabled()) log().debug("getMibObjectList: MATCH!! adding system '" + system.getName() + "'"); systemList.add(system); } } // Next build list of Mib objects to collect from the list of matching SystemDefs final List<MibObject> mibObjectList = new ArrayList<MibObject>(); for (final SystemDef system : systemList) { // Next process each of the SystemDef's groups for (final String grpName : system.getCollect().getIncludeGroupCollection()) { processGroupName(cName, grpName, ifType, mibObjectList); } } return mibObjectList; } public Map<String, ResourceType> getConfiguredResourceTypes() { final Map<String,ResourceType> map = new HashMap<String,ResourceType>(); final Collection<SnmpCollection> snmpCollections = getContainer().getObject().getSnmpCollectionCollection(); for (final SnmpCollection collection : snmpCollections) { for (final ResourceType resourceType : collection.getResourceTypeCollection()) { map.put(resourceType.getName(), resourceType); } } // FIXME: I guarantee there's a cleaner way to do this, but I didn't want to refactor everything // that calls this just to optimize out validation. if (!m_validated) { try { validateResourceTypes(map.keySet()); } catch (final RuntimeException e) { m_validationException = e; throw e; } } else { if (m_validationException != null) { throw m_validationException; } } return Collections.unmodifiableMap(map); } public RrdRepository getRrdRepository(final String collectionName) { final RrdRepository repo = new RrdRepository(); repo.setRrdBaseDir(new File(getRrdPath())); repo.setRraList(getRRAList(collectionName)); repo.setStep(getStep(collectionName)); repo.setHeartBeat((2 * getStep(collectionName))); return repo; } public int getStep(final String collectionName) { final SnmpCollection collection = getSnmpCollection(collectionName); return collection == null ? -1 : collection.getRrd().getStep(); } public List<String> getRRAList(final String collectionName) { final SnmpCollection collection = getSnmpCollection(collectionName); return collection == null ? null : collection.getRrd().getRraCollection(); } public String getRrdPath() { final String rrdPath = getContainer().getObject().getRrdRepository(); if (rrdPath == null) { throw new RuntimeException("Configuration error, failed to retrieve path to RRD repository."); } /* * TODO: make a path utils class that has the below in it strip the * File.separator char off of the end of the path. */ if (rrdPath.endsWith(File.separator)) { return rrdPath.substring(0, (rrdPath.length() - File.separator.length())); } return rrdPath; } /* Private Methods */ private SnmpCollection getSnmpCollection(final String collectionName) { for (final SnmpCollection collection : getContainer().getObject().getSnmpCollection()) { if (collection.getName().equals(collectionName)) return collection; } return null; } /** * Private utility method used by the getMibObjectList() method. This method * takes a group name and a list of MibObject objects as arguments and adds * all of the MibObjects associated with the group to the object list. If * the passed group consists of any additional sub-groups, then this method * will be called recursively for each sub-group until the entire * log.debug("processGroupName: adding MIB objects from group: " + * groupName); group is processed. * * @param cName * Collection name * @param groupName * Name of the group to process * @param ifType * Interface type * @param mibObjectList * List of MibObject objects being built. */ private void processGroupName(final String cName, final String groupName, final int ifType, final List<MibObject> mibObjectList) { ThreadCategory log = log(); // Using the collector name retrieve the group map final Map<String, Group> groupMap = getCollectionGroupMap().get(cName); // Next use the groupName to access the Group object final Group group = groupMap.get(groupName); // Verify that we have a valid Group object...generate // warning message if not... if (group == null) { log.warn("DataCollectionConfigFactory.processGroupName: unable to retrieve group information for group name '" + groupName + "': check DataCollection.xml file."); return; } if (log.isDebugEnabled()) { log.debug("processGroupName: processing group: " + groupName + " groupIfType: " + group.getIfType() + " ifType: " + ifType); } // Process any sub-groups contained within this group for (final String includeGroup : group.getIncludeGroupCollection()) { processGroupName(cName, includeGroup, ifType, mibObjectList); } // Add this group's objects to the object list provided // that the group's ifType string does not exclude the // provided ifType parm. // // ifType parm of -1 indicates that only node-level // objects are to be added // // Any other ifType parm value must be compared with // the group's ifType value to verify that they match // (if group's ifType is "all" then the objects will // automatically be added. final String ifTypeStr = String.valueOf(ifType); String groupIfType = group.getIfType(); boolean addGroupObjects = false; if (ifType == NODE_ATTRIBUTES) { if (groupIfType.equals("ignore")) { addGroupObjects = true; } } else { if (groupIfType.equals("all")) { addGroupObjects = true; } else if ("ignore".equals(groupIfType)) { // Do nothing } else if (ifType == ALL_IF_ATTRIBUTES) { addGroupObjects = true; } else { // First determine if the group's ifType value contains // a single type value or a list of values. In the case // of a list the ifType values will be delimited by commas. boolean isList = false; if (groupIfType.indexOf(',') != -1) isList = true; // Next compare the provided ifType parameter with the // group's ifType value to determine if the group's OIDs // should be added to the MIB object list. // // If the group ifType value is a single value then only // a simple comparison is needed to see if there is an // exact match. // // In the case of the group ifType value being a list // of ifType values it is more complicated...each comma // delimited substring which starts with the provided // ifType parm must be extracted and compared until an // EXACT match is found.. if (!isList) { if (ifTypeStr.equals(groupIfType)) addGroupObjects = true; } else { int tmpIndex = groupIfType.indexOf(ifTypeStr); while (tmpIndex != -1) { groupIfType = groupIfType.substring(tmpIndex); // get substring starting at tmpIndex to // either the end of the groupIfType string // or to the first comma after tmpIndex final int nextComma = groupIfType.indexOf(','); String parsedType = null; if (nextComma == -1) // No comma, this is last type // value { parsedType = groupIfType; } else // Found comma { parsedType = groupIfType.substring(0, nextComma); } if (ifTypeStr.equals(parsedType)) { addGroupObjects = true; break; } // No more commas indicates no more ifType values to // compare...we're done if (nextComma == -1) break; // Get next substring and reset tmpIndex to // once again point to the first occurrence of // the ifType string parm. groupIfType = groupIfType.substring(nextComma + 1); tmpIndex = groupIfType.indexOf(ifTypeStr); } } } } if (addGroupObjects) { if (log.isDebugEnabled()) { log.debug("processGroupName: OIDs from group '" + group.getName() + ":" + group.getIfType() + "' are included for ifType: " + ifType); } processObjectList(groupName, groupIfType, group.getMibObjCollection(), mibObjectList); } else { if (log.isDebugEnabled()) log.debug("processGroupName: OIDs from group '" + group.getName() + ":" + group.getIfType() + "' are excluded for ifType: " + ifType); } } /** * Takes a list of castor generated MibObj objects iterates over them * creating corresponding MibObject objects and adding them to the supplied * MibObject list. * @param groupName TODO * @param groupIfType TODO * @param objectList * List of MibObject objects parsed from * 'datacollection-config.xml' * @param mibObjectList * List of MibObject objects currently being built */ private void processObjectList(final String groupName, final String groupIfType, final List<MibObj> objectList, final List<MibObject> mibObjectList) { for (final MibObj mibObj : objectList) { // Create a MibObject from the castor MibObj final MibObject aMibObject = new MibObject(); aMibObject.setGroupName(groupName); aMibObject.setGroupIfType(groupIfType); aMibObject.setOid(mibObj.getOid()); aMibObject.setAlias(mibObj.getAlias()); aMibObject.setType(mibObj.getType()); aMibObject.setInstance(mibObj.getInstance()); aMibObject.setMaxval(mibObj.getMaxval()); aMibObject.setMinval(mibObj.getMinval()); final ResourceType resourceType = getConfiguredResourceTypes().get(mibObj.getInstance()); if (resourceType != null) { aMibObject.setResourceType(resourceType); } // Add the MIB object provided it isn't already in the list if (!mibObjectList.contains(aMibObject)) { mibObjectList.add(aMibObject); } } } private Map<String,Map<String,Group>> getCollectionGroupMap() { // Build collection map which is a hash map of Collection // objects indexed by collection name...also build // collection group map which is a hash map indexed // by collection name with a hash map as the value // containing a map of the collections's group names // to the Group object containing all the information // for that group. So the associations are: // // CollectionMap // collectionName -> Collection // // CollectionGroupMap // collectionName -> groupMap // // GroupMap // groupMapName -> Group // // This is parsed and built at initialization for // faster processing at run-timne. // final Map<String,Map<String,Group>> collectionGroupMap = new HashMap<String,Map<String,Group>>(); for (final SnmpCollection collection : getContainer().getObject().getSnmpCollectionCollection()) { // Build group map for this collection final Map<String,Group> groupMap = new HashMap<String,Group>(); final Groups groups = collection.getGroups(); if (groups != null) { for (final Group group : groups.getGroupCollection()) { groupMap.put(group.getName(), group); } } collectionGroupMap.put(collection.getName(), groupMap); } return Collections.unmodifiableMap(collectionGroupMap); } private void validateResourceTypes(final Set<String> allowedResourceTypes) { final String configuredString; if (allowedResourceTypes.size() == 0) { configuredString = "(none)"; } else { configuredString = StringUtils.join(allowedResourceTypes, ", "); } final String allowableValues = "any positive number, 'ifIndex', or any of the configured resourceTypes: " + configuredString; for (final SnmpCollection collection : getContainer().getObject().getSnmpCollectionCollection()) { final Groups groups = collection.getGroups(); if (groups != null) { for (final Group group : groups.getGroupCollection()) { for (final MibObj mibObj : group.getMibObjCollection()) { final String instance = mibObj.getInstance(); if (instance == null) continue; if (MibObject.INSTANCE_IFINDEX.equals(instance)) continue; if (allowedResourceTypes.contains(instance)) continue; try { // Check to see if the value is a non-negative integer if (Integer.parseInt(instance.trim()) >= 0) { continue; } } catch (NumberFormatException e) {} // XXX this should be a better exception throw new IllegalArgumentException("instance '" + instance + "' invalid in mibObj definition for OID '" + mibObj.getOid() + "' in collection '" + collection.getName() + "' for group '" + group.getName() + "'. Allowable instance values: " + allowableValues); } } } } } }