/******************************************************************************* * This file is part of OpenNMS(R). * * Copyright (C) 2006-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.capsd; import static org.opennms.core.utils.InetAddressUtils.addr; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.lang.builder.HashCodeBuilder; import org.opennms.core.utils.DBUtils; import org.opennms.core.utils.ThreadCategory; import org.opennms.netmgt.EventConstants; import org.opennms.netmgt.eventd.EventIpcManagerFactory; import org.opennms.netmgt.model.events.EventBuilder; /** * This class is designed to reparent interfaces in the database based on the * SMB protocol. Specifically, if two nodes in the 'node' table have identical * NetBIOS names it is assumed that those two nodes actually represent different * interfaces (physical or alias'd) on the same box. The node with the lowest * nodeID becomes the "reparent node" and the other nodes are considered * duplicates. All interfaces under each duplicate node are then reparented * under the "reparent node" and the duplicate node(s) are flagged in the * database as deleted (nodeType='D'). * * @author <A HREF="mike@opennms.org">Mike </A> * @author <A HREF="http://www.opennms.org/">OpenNMS </A> * @author <A HREF="mike@opennms.org">Mike </A> * @author <A HREF="http://www.opennms.org/">OpenNMS </A> * @version 1.1.1.1 */ public final class ReparentViaSmb { /** * SQL Statements */ final static String SQL_DB_RETRIEVE_NODES = "SELECT nodeid,nodenetbiosname FROM node WHERE nodeType!='D' AND nodenetbiosname is not null ORDER BY nodeid"; final static String SQL_DB_RETRIEVE_NODE = "SELECT nodesysname,nodesysdescription,nodelabel,nodelabelsource FROM node WHERE nodeid=? AND nodeType!='D'"; final static String SQL_DB_RETRIEVE_INTERFACES = "SELECT ipaddr,iphostname FROM ipinterface WHERE nodeid=? AND isManaged!='D'"; final static String SQL_DB_REPARENT_IP_INTERFACE = "UPDATE ipinterface SET nodeID=? WHERE nodeID=? AND isManaged!='D'"; final static String SQL_DB_REPARENT_SNMP_INTERFACE = "UPDATE snmpinterface SET nodeID=? WHERE nodeID=?"; final static String SQL_DB_REPARENT_IF_SERVICES = "UPDATE ifservices SET nodeID=? WHERE nodeID=? AND status!='D'"; final static String SQL_DB_DELETE_NODE = "UPDATE node SET nodeType='D' WHERE nodeID=?"; /** * Database connection */ private java.sql.Connection m_connection; /** * List of LightWeightNodeEntry objects intialized from the content of the * 'node' table. */ private List<LightWeightNodeEntry> m_existingNodeList; /** * Contains a mapping of reparent nodes and the list of interfaces which * were reparented under them. */ private Map<LightWeightNodeEntry, List<LightWeightIfEntry>> m_reparentedIfMap; /** * Contains of mapping of reparent nodes and the list of duplicate nodes * associated with them. */ private Map<LightWeightNodeEntry, List<LightWeightNodeEntry>> m_reparentNodeMap; /** * Contains hard-coded list of NetBIOS names which are not subject to * reparenting via SMB. */ private static List<String> m_netbiosNamesToSkip; // // Static initialization block to initialize list of NetBIOS names // which should not be considered for reparenting // static { m_netbiosNamesToSkip = new ArrayList<String>(4); m_netbiosNamesToSkip.add("WORKSTATION"); m_netbiosNamesToSkip.add("DEFAULT"); m_netbiosNamesToSkip.add("OEMCOMPUTER"); m_netbiosNamesToSkip.add("COMPUTER"); } /** * <P> * LightWeightIfEntry is designed to hold specific information about an IP * interface in the database such as its IP address, its parent node id, and * its managed status and represents a lighter weight version of the * DbIpInterfaceEntry class. * </P> */ private static final class LightWeightIfEntry { private String m_address; private String m_hostname; private int m_nodeId; private int m_oldNodeId; /** * <P> * Constructs a new LightWeightIfEntry object. * </P> * * @param address * Interface's ip address * @param hostname * Interface's ip host name * @param nodeId * Interface's parent node id * @param oldNodeId * Interface's original parent node id */ public LightWeightIfEntry(String address, String hostname, int nodeId, int oldNodeId) { m_address = address; m_hostname = hostname; m_nodeId = nodeId; m_oldNodeId = oldNodeId; } /** * <P> * Returns the IP address of the interface. * </P> */ public String getAddress() { return m_address; } /** * <P> * Returns the IP hostname of the interface. * </P> */ public String getHostName() { return m_hostname; } /** * <P> * Returns the parent node id of the interface. * </P> */ public int getParentNodeId() { return m_nodeId; } /** * <P> * Returns the old parent node id of the interface. * </P> */ public int getOldParentNodeId() { return m_oldNodeId; } } /** * This class is a lighter weight version of the DbNodeEntry class for use * in SMB reparenting. */ private static final class LightWeightNodeEntry { private int m_nodeId; private String m_netbiosName; private boolean m_duplicate; private DbNodeEntry m_hwNodeEntry; /** * <P> * Constructs a new LightWeightNodeEntry object. * </P> * * @param nodeID * Node's identifier * @param netbiosName * Node's NetBIOS name */ LightWeightNodeEntry(int nodeID, String netbiosName) { m_nodeId = nodeID; if (netbiosName != null) m_netbiosName = netbiosName.toUpperCase(); else m_netbiosName = null; m_duplicate = false; m_hwNodeEntry = null; } /** * <P> * Returns the node identifer. * </P> */ int getNodeId() { return m_nodeId; } /** * <P> * Returns the NetBIOS name of the node. * </P> */ String getNetbiosName() { return m_netbiosName; } /** * <P> * Sets the duplicate flag for the node.. * </P> * * @param dupFlag * the state for the duplicate flag */ void setDuplicate(boolean dupFlag) { m_duplicate = dupFlag; } /** * <P> * Returns true if this LightWeightNodeEntry object has been marked as a * duplicate, false otherwise. * </P> */ boolean isDuplicate() { return m_duplicate; } /** * */ void setHeavyWeightNodeEntry(DbNodeEntry hwNodeEntry) { m_hwNodeEntry = hwNodeEntry; } /** * */ DbNodeEntry getHeavyWeightNodeEntry() { return m_hwNodeEntry; } /** * */ boolean hasHeavyWeightNodeEntry() { if (m_hwNodeEntry == null) return false; else return true; } /** * <P> * Node equality test...currently returns true if the * LightWeightNodeEntry objects have the same NetBIOS name. * </P> * * @return true if this and the passed object are equivalent. */ @Override public boolean equals(final Object o) { if (o == null) return false; if (!(o instanceof LightWeightNodeEntry)) return false; LightWeightNodeEntry node = (LightWeightNodeEntry) o; if (m_netbiosName == null || node.getNetbiosName() == null) return false; else if (node.getNetbiosName().equals(m_netbiosName)) return true; else return false; } @Override public int hashCode() { return new HashCodeBuilder(7, 23) .append(m_nodeId) .append(m_netbiosName) .append(m_duplicate) .append(m_hwNodeEntry) .toHashCode(); } } /** * Class constructor. * * @param connection * Database connection */ public ReparentViaSmb(java.sql.Connection connection) { m_connection = connection; m_existingNodeList = null; m_reparentedIfMap = null; m_reparentNodeMap = null; } /** * This method is responsible for building a list of existing nodes from the * 'node' table and then processing that list of nodes in order to determine * if there are any nodes which must be reparented because they share the * same NetBIOS name with another node. During this processing the reparent * node map is built which contains a mapping of reparent nodes to their * duplicate node lists. * * @throws SQLException * if an error occurs querying the database. */ private void buildNodeLists() throws SQLException { ThreadCategory log = ThreadCategory.getInstance(getClass()); m_existingNodeList = new ArrayList<LightWeightNodeEntry>(); final DBUtils d = new DBUtils(getClass()); try { PreparedStatement stmt = m_connection.prepareStatement(SQL_DB_RETRIEVE_NODES); d.watch(stmt); ResultSet rs = stmt.executeQuery(); d.watch(rs); // Process result set // Build list of LightWeightNodeEntry objects representing each of // the // nodes pulled from the 'node' table while (rs.next()) { m_existingNodeList.add(new LightWeightNodeEntry(rs.getInt(1), rs.getString(2))); } } finally { d.cleanUp(); } // // Loop through node list and verify that all of the nodes // have unique NetBIOS names. If any two nodes have the same // NetBIOS name then an entry will be added to the reparenting // map. // // Currently the nodeID with the lowest nodeID will have all // the interfaces associated with the other node(s) reparented // under it and it's LightWeightNodeEntry object will serve as the map // key. // Each of the other (duplicate) nodes will be added to a reparent // list and then added to the map under the reparent node key. // Iterator<LightWeightNodeEntry> outer = m_existingNodeList.iterator(); while (outer.hasNext()) { LightWeightNodeEntry outerEntry = outer.next(); String outerNetbiosName = outerEntry.getNetbiosName(); // Skip this node if NetBIOS name is null or is in list to skip if (outerNetbiosName == null || m_netbiosNamesToSkip.contains(outerNetbiosName)) continue; // If node is already marked as a duplicate just move on if (outerEntry.isDuplicate()) continue; List<LightWeightNodeEntry> duplicateNodeList = null; Iterator<LightWeightNodeEntry> inner = m_existingNodeList.iterator(); while (inner.hasNext()) { LightWeightNodeEntry innerEntry = inner.next(); String innerNetbiosName = innerEntry.getNetbiosName(); // Skip if inner node id is less than or equal to // the current outer node id (since these have already // been processed as an outer node). if (innerEntry.getNodeId() <= outerEntry.getNodeId()) continue; // Skip this node if NetBIOS name is null or is in list to skip if (innerNetbiosName == null || m_netbiosNamesToSkip.contains(innerNetbiosName)) continue; // Skip if current node is already marked as a duplicate if (innerEntry.isDuplicate()) continue; if (innerNetbiosName.equals(outerNetbiosName)) { // We've found two nodes with same NetBIOS name // Add innerEntry to duplicate node list if (duplicateNodeList == null) duplicateNodeList = new ArrayList<LightWeightNodeEntry>(); innerEntry.setDuplicate(true); // mark node as duplicate duplicateNodeList.add(innerEntry); // add to current dup // list if (log.isDebugEnabled()) log.debug("ReparentViaSmb.retrieveNodeData: found that nodeid " + innerEntry.getNodeId() + " is a duplicate of nodeid " + outerEntry.getNodeId()); } } // end inner while() // Anything need reparenting? if (duplicateNodeList != null) { // We found duplicates...add to reparent map if (m_reparentNodeMap == null) m_reparentNodeMap = new HashMap<LightWeightNodeEntry, List<LightWeightNodeEntry>>(); if (log.isDebugEnabled()) log.debug("ReparentViaSmb.retrieveNodeData: adding dup list w/ " + duplicateNodeList.size() + " to reparent Map for reparent nodeid " + outerEntry.getNodeId()); m_reparentNodeMap.put(outerEntry, duplicateNodeList); } }// end outer while() } /** * Performs reparenting if necessary and generates appropriate events to * inform other OpenNMS processes of any database changes.. * * @throws java.sql.SQLException * if error occurs updating the database */ public void sync() throws SQLException { // Build node lists buildNodeLists(); // Reparent interfaces if necessary if (m_reparentNodeMap != null && !m_reparentNodeMap.isEmpty()) { reparentInterfaces(); // Generate 'interfaceReparented' events if necessary if (m_reparentedIfMap != null && !m_reparentedIfMap.isEmpty()) generateEvents(); } } /** * This method is responsible for reparenting interfaces belonging to * duplicate nodes under the appropriate reparent node id. During this * processing the reparented interface map is generated. This map contains a * list of reparented interfaces associated with each reparent node. This * list will make it possible to generate 'interfaceReparented' events for * each reparented interface. * * During reparenting the 'ipInterface', 'snmpInterface', and 'ifServices' * tables are all updated to reflect the new parent node id for the * reparented interface. * * @throws SQLException * if error occurs updating the database */ private void reparentInterfaces() throws SQLException { ThreadCategory log = ThreadCategory.getInstance(getClass()); List<LightWeightIfEntry> reparentedIfList = null; m_reparentedIfMap = null; final DBUtils d = new DBUtils(getClass()); try { PreparedStatement ipInterfaceStmt = m_connection.prepareStatement(SQL_DB_REPARENT_IP_INTERFACE); d.watch(ipInterfaceStmt); PreparedStatement snmpInterfaceStmt = m_connection.prepareStatement(SQL_DB_REPARENT_SNMP_INTERFACE); d.watch(snmpInterfaceStmt); PreparedStatement ifServicesStmt = m_connection.prepareStatement(SQL_DB_REPARENT_IF_SERVICES); d.watch(ifServicesStmt); Set<LightWeightNodeEntry> keys = m_reparentNodeMap.keySet(); Iterator<LightWeightNodeEntry> iter = keys.iterator(); while (iter.hasNext()) { LightWeightNodeEntry reparentNode = iter.next(); int reparentNodeID = reparentNode.getNodeId(); // Now construct a "heavier weight" DbNodeEntry object for this // node...sysName, sysDescription and other fields from the node // table will be necessary later when the reparentInterface // event is generated. reparentNode.setHeavyWeightNodeEntry(DbNodeEntry.get(reparentNodeID)); // Retrieve duplicate node list for this reparent node key List<LightWeightNodeEntry> dupList = m_reparentNodeMap.get(reparentNode); log.debug("ReparentViaSmb.retrieveNodeData: duplicate node list retrieved, list size=" + dupList.size()); Iterator<LightWeightNodeEntry> dupIter = dupList.iterator(); while (dupIter.hasNext()) { LightWeightNodeEntry dupNode = dupIter.next(); int dupNodeID = dupNode.getNodeId(); try { if (log.isDebugEnabled()) log.debug("reparentInterfaces: reparenting all interfaces/services for nodeID " + dupNodeID + " under reparent nodeID " + reparentNodeID); // // Prior to reparenting the interfaces associated with the // duplicate node retrieve a list of the node's interface // IP addresses and add them to the m_reparentedIfMap. This // list will allow us to generate 'interfaceReparented' // events for each one // PreparedStatement stmt = m_connection.prepareStatement(SQL_DB_RETRIEVE_INTERFACES); d.watch(stmt); stmt.setInt(1, dupNodeID); // Issue database query if (log.isDebugEnabled()) log.debug("reparentInterfaces: issuing db query..."); ResultSet rs = stmt.executeQuery(); d.watch(rs); // Process result set // Build list of LightWeightIfEntry objects representing // each of the // interfaces pulled from the 'ipInterface' table while (rs.next()) { String ifAddress = rs.getString(1); String hostName = rs.getString(2); LightWeightIfEntry lwIfEntry = new LightWeightIfEntry(ifAddress, hostName, reparentNodeID, dupNodeID); if (reparentedIfList == null) { reparentedIfList = new ArrayList<LightWeightIfEntry>(); } reparentedIfList.add(lwIfEntry); if (log.isDebugEnabled()) log.debug("reparentInterfaces: will reparent " + lwIfEntry.getAddress() + " : oldNodeId: " + lwIfEntry.getOldParentNodeId() + " newNodeId: " + lwIfEntry.getParentNodeId()); } // Update the 'ipInterface' table so that all interfaces // associated with the duplicate node are reparented. ipInterfaceStmt.setInt(1, reparentNodeID); ipInterfaceStmt.setInt(2, dupNodeID); // execute and log ipInterfaceStmt.executeUpdate(); // Update the 'snmpinterface' table so that all interfaces // associated with the duplicate node are reparented snmpInterfaceStmt.setInt(1, reparentNodeID); snmpInterfaceStmt.setInt(2, dupNodeID); // execute and log snmpInterfaceStmt.executeUpdate(); // Update the 'ifservices' table so that all services // associated // with the duplicate node are reparented ifServicesStmt.setInt(1, reparentNodeID); ifServicesStmt.setInt(2, dupNodeID); // execute and log ifServicesStmt.executeUpdate(); } catch (SQLException sqlE) { log.error("SQLException while reparenting duplicate node w/ nodeID " + dupNodeID); throw sqlE; } // // Now that all the interfaces have been reparented...lets // delete this duplicate node from the 'node' table // if (log.isDebugEnabled()) log.debug("reparentInterfaces: deleting duplicate node id: " + dupNodeID); PreparedStatement deleteNodeStmt = m_connection.prepareStatement(SQL_DB_DELETE_NODE); d.watch(deleteNodeStmt); deleteNodeStmt.setInt(1, dupNodeID); // execute update deleteNodeStmt.executeUpdate(); } // end while(dupIter.hasNext()) // Should have a reparented interface list now...add it to // the reparented interface map with the reparent node as the key if (reparentedIfList != null && !reparentedIfList.isEmpty()) { if (m_reparentedIfMap == null) { m_reparentedIfMap = new HashMap<LightWeightNodeEntry, List<LightWeightIfEntry>>(); } m_reparentedIfMap.put(reparentNode, reparentedIfList); } } // end while(iter.hasNext()) } finally { d.cleanUp(); } } /** * Generates appropriate events to inform other OpenNMS processes of the * database changes. Loops through the keys of the reparent interface and * generates 'interfaceReparented' events for each reparented interface. */ private void generateEvents() { // // iterate through the reparent interface list // ThreadCategory log = ThreadCategory.getInstance(getClass()); if (log.isDebugEnabled()) log.debug("generateEvents: Generating reparent events...reparentedIfMap size: " + m_reparentedIfMap.size()); Set<LightWeightNodeEntry> keys = m_reparentedIfMap.keySet(); Iterator<LightWeightNodeEntry> iter = keys.iterator(); while (iter.hasNext()) { // Get reparent node object LightWeightNodeEntry reparentNode = iter.next(); if (!reparentNode.hasHeavyWeightNodeEntry()) { log.warn("generateEvents: No valid reparent node entry for node " + reparentNode.getNodeId() + ". Unable to generate reparenting events."); continue; } if (log.isDebugEnabled()) log.debug("generateEvents: generating events for reparent node w/ id/netbiosName: " + reparentNode.getNodeId() + "/" + reparentNode.getNetbiosName()); // Get list of interface objects associated with this reparent node List<LightWeightIfEntry> ifList = m_reparentedIfMap.get(reparentNode); if (ifList != null && !ifList.isEmpty()) { Iterator<LightWeightIfEntry> ifIter = ifList.iterator(); while (ifIter.hasNext()) { LightWeightIfEntry lwIfEntry = ifIter.next(); // Generate interfaceReparented event sendInterfaceReparentedEvent(lwIfEntry.getAddress(), lwIfEntry.getHostName(), lwIfEntry.getParentNodeId(), lwIfEntry.getOldParentNodeId(), reparentNode.getHeavyWeightNodeEntry()); if (log.isDebugEnabled()) log.debug("generateEvents: sent interfaceReparented event for interface " + lwIfEntry.getAddress()); } } } if (log.isDebugEnabled()) log.debug("generateEvents: completed all event generation..."); } /** * This method is responsible for generating a interfaceReparented event and * sending it to Eventd. * * @param ipAddr * IP address of interface which was reparented * @param ipHostName * IP Host Name for the interface * @param newNodeId * Interface's new nodeID * @param oldNodeId * Interface's old nodeID * @param reparentNodeEntry * DbNodeEntry object with all info associated with the reparent * node */ private synchronized void sendInterfaceReparentedEvent(String ipAddr, String ipHostName, int newNodeId, int oldNodeId, DbNodeEntry reparentNodeEntry) { ThreadCategory log = ThreadCategory.getInstance(getClass()); if (log.isDebugEnabled()) log.debug("sendInterfaceReparentedEvent: ipAddr: " + ipAddr + " ipHostName: " + ipHostName + " newNodeId: " + newNodeId + " oldNodeId: " + oldNodeId); // Make sure host name not null if (ipHostName == null) ipHostName = ""; // create the event to be sent EventBuilder bldr = new EventBuilder(EventConstants.INTERFACE_REPARENTED_EVENT_UEI, "OpenNMS.Capsd"); bldr.setNodeid(newNodeId); bldr.setHost(Capsd.getLocalHostAddress()); bldr.setInterface(addr(ipAddr)); bldr.addParam(EventConstants.PARM_IP_HOSTNAME, ipHostName); bldr.addParam(EventConstants.PARM_OLD_NODEID, oldNodeId); bldr.addParam(EventConstants.PARM_NEW_NODEID, newNodeId); bldr.addParam(EventConstants.PARM_NODE_LABEL, reparentNodeEntry.getLabel()); bldr.addParam(EventConstants.PARM_NODE_LABEL_SOURCE, reparentNodeEntry.getLabelSource()); if (reparentNodeEntry.getSystemName() != null) { bldr.addParam(EventConstants.PARM_NODE_SYSNAME, reparentNodeEntry.getSystemName()); } if (reparentNodeEntry.getSystemDescription() != null) { bldr.addParam(EventConstants.PARM_NODE_SYSDESCRIPTION, reparentNodeEntry.getSystemDescription()); } // Send event to Eventd try { EventIpcManagerFactory.getIpcManager().sendNow(bldr.getEvent()); } catch (Throwable t) { log.warn("run: unexpected throwable exception caught during send to middleware", t); } } }