/******************************************************************************* * 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.utils; import java.net.InetAddress; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.opennms.core.resource.Vault; import org.opennms.core.utils.ByteArrayComparator; import org.opennms.core.utils.DBUtils; import org.opennms.core.utils.InetAddressUtils; import org.opennms.core.utils.ThreadCategory; import org.opennms.netmgt.dao.NodeDao; /** * <P> * This class contains convenience functions for retrieving and modifying the * label associated with a managed node. The 'node' table contains a 'nodelabel' * and 'nodelabelsource' field. The 'nodelabel' is a user-friendly name * associated with the node. This name can be user-defined (via the WEB UI) or * can be auto-generated based on what OpenNMS knows about the node and its * interfaces. The 'nodelabelsource' field is a single character flag which * indicates what the source for the node label was. * </P> * * <PRE> * * Valid values for node label source are: 'U' User defined 'H' Primary * interface's IP host name 'S' Node's MIB-II sysName 'A' Primary interface's IP * address * * </PRE> * * @author <A HREF="mike@opennms.org">Mike </A> * @author <A HREF="http://www.opennms.org/">OpenNMS </A> */ public class NodeLabel { /** * The SQL statement to update the 'nodelabel' and 'nodelabelsource' fields * of 'node' table */ private final static String SQL_DB_UPDATE_NODE_LABEL = "UPDATE node SET nodelabel=?,nodelabelsource=? WHERE nodeid=?"; /** * The SQL statement to retrieve the NetBIOS name associated with a * particular nodeID */ private final static String SQL_DB_RETRIEVE_NETBIOS_NAME = "SELECT nodenetbiosname FROM node WHERE nodeid=?"; /** * The SQL statement to retrieve all managed IP address & hostName values * associated with a particular nodeID */ private final static String SQL_DB_RETRIEVE_MANAGED_INTERFACES = "SELECT ipaddr,iphostname FROM ipinterface WHERE nodeid=? AND ismanaged='M'"; /** * The SQL statement to retrieve all non-managed IP address & hostName * values associated with a particular nodeID */ private final static String SQL_DB_RETRIEVE_NON_MANAGED_INTERFACES = "SELECT ipaddr,iphostname FROM ipinterface WHERE nodeid=? AND ismanaged!='M'"; /** * The SQL statement to retrieve the MIB-II sysname field from the node * table */ private final static String SQL_DB_RETRIEVE_SYSNAME = "SELECT nodesysname FROM node WHERE nodeid=?"; /** * The SQL statement to retrieve the current node label and node label * source values associated with a node. */ private final static String SQL_DB_RETRIEVE_NODELABEL = "SELECT nodelabel,nodelabelsource FROM node WHERE nodeid=?"; /** * Valid values for node label source flag */ public final static char SOURCE_USERDEFINED = 'U'; /** Constant <code>SOURCE_NETBIOS='N'</code> */ public final static char SOURCE_NETBIOS = 'N'; /** Constant <code>SOURCE_HOSTNAME='H'</code> */ public final static char SOURCE_HOSTNAME = 'H'; /** Constant <code>SOURCE_SYSNAME='S'</code> */ public final static char SOURCE_SYSNAME = 'S'; /** Constant <code>SOURCE_ADDRESS='A'</code> */ public final static char SOURCE_ADDRESS = 'A'; /** * Initialization value for node label source flag */ public final static char SOURCE_UNKNOWN = 'X'; /** * Maximum length for node label */ public final static int MAX_NODE_LABEL_LENGTH = 256; /** * Primary interface selection method MIN. Using this selection method the * interface with the smallest numeric IP address is considered the primary * interface. */ private final static String SELECT_METHOD_MIN = "min"; /** * Primary interface selection method MAX. Using this selection method the * interface with the greatest numeric IP address is considered the primary * interface. */ private final static String SELECT_METHOD_MAX = "max"; /** * Default primary interface select method. */ private final static String DEFAULT_SELECT_METHOD = SELECT_METHOD_MIN; /** * Node label */ private final String m_nodeLabel; /** * Flag describing source of node label */ private final char m_nodeLabelSource; /** * The property string in the properties file which specifies the method to * use for determining which interface is primary on a multi-interface box. */ public static final String PROP_PRIMARY_INTERFACE_SELECT_METHOD = "org.opennms.bluebird.dp.primaryInterfaceSelectMethod"; /** * Default constructor */ public NodeLabel() { m_nodeLabel = null; m_nodeLabelSource = SOURCE_UNKNOWN; } public NodeLabel(String nodeLabel, String nodeLabelSource) { this(nodeLabel, nodeLabelSource.charAt(0)); } /** * Constructor * * @param nodeLabel * Node label * @param nodeLabelSource * Flag indicating source of node label */ public NodeLabel(String nodeLabel, char nodeLabelSource) { switch(nodeLabelSource) { case SOURCE_ADDRESS: case SOURCE_HOSTNAME: case SOURCE_NETBIOS: case SOURCE_SYSNAME: case SOURCE_UNKNOWN: case SOURCE_USERDEFINED: break; default: throw new IllegalArgumentException("Invalid value for node label source: " + nodeLabelSource); } m_nodeLabel = nodeLabel; m_nodeLabelSource = nodeLabelSource; } /** * Returns the node label . * * @return node label */ public String getLabel() { return m_nodeLabel; } /** * Returns the node label source flag . * * @return node label source flag */ public char getSource() { return m_nodeLabelSource; } /** * This method queries the 'node' table for the value of the 'nodelabel' and * 'nodelabelsource' fields for the node with the provided nodeID. A * NodeLabel object is returned initialized with the retrieved values. * * WARNING: A properly instantiated and initialized Vault class object is * required prior to calling this method. This method will initially only be * called from the WEB UI. * * @param nodeID * Unique identifier of the node to be updated. * @return Object containing label and source values. * @throws java.sql.SQLException if any. * * @deprecated Use a {@link NodeDao#load(Integer)} method call instead */ public static NodeLabel retrieveLabel(int nodeID) throws SQLException { NodeLabel label = null; Connection dbConnection = Vault.getDbConnection(); try { label = retrieveLabel(nodeID, dbConnection); } finally { Vault.releaseDbConnection(dbConnection); } return label; } /** * This method queries the 'node' table for the value of the 'nodelabel' and * 'nodelabelsource' fields for the node with the provided nodeID. A * NodeLabel object is returned initialized with the retrieved values. * * @param nodeID * Unique ID of node whose label info is to be retrieved * @param dbConnection * SQL database connection * @return object initialized with node label & source flag * @throws java.sql.SQLException if any. * * @deprecated Use a {@link NodeDao#load(Integer)} method call instead */ public static NodeLabel retrieveLabel(int nodeID, Connection dbConnection) throws SQLException { String nodeLabel = null; String nodeLabelSource = null; PreparedStatement stmt = null; ResultSet rs = null; final DBUtils d = new DBUtils(NodeLabel.class); if (log().isDebugEnabled()) { log().debug("NodeLabel.retrieveLabel: sql: " + SQL_DB_RETRIEVE_NODELABEL + " node id: " + nodeID); } try { stmt = dbConnection.prepareStatement(SQL_DB_RETRIEVE_NODELABEL); d.watch(stmt); stmt.setInt(1, nodeID); // Issue database query rs = stmt.executeQuery(); d.watch(rs); // Process result set, retrieve node's sysname if (rs.next()) { nodeLabel = rs.getString(1); nodeLabelSource = rs.getString(2); } } finally { d.cleanUp(); } if (nodeLabelSource != null) { char[] temp = nodeLabelSource.toCharArray(); return new NodeLabel(nodeLabel, temp[0]); } else return new NodeLabel(nodeLabel, SOURCE_UNKNOWN); } /** * This method updates the 'nodelabel' and 'nodelabelsource' fields of the * 'node' table for the specified nodeID. A database connection is retrieved * from the Vault. * * WARNING: A properly instantiated and initialized Vault class object is * required prior to calling this method. This method will initially only be * called from the WEB UI. * * @param nodeID * Unique identifier of the node to be updated. * @param nodeLabel * Object containing label and source values. * @throws java.sql.SQLException if any. * * @deprecated Use a {@link NodeDao#update(org.opennms.netmgt.model.OnmsNode)} method call instead */ public static void assignLabel(int nodeID, NodeLabel nodeLabel) throws SQLException { Connection dbConnection = Vault.getDbConnection(); try { assignLabel(nodeID, nodeLabel, dbConnection); } finally { Vault.releaseDbConnection(dbConnection); } } /** * This method updates the 'nodelabel' and 'nodelabelsource' fields of the * 'node' table for the specified nodeID. * * If nodeLabel parameter is NULL the method will first call computeLabel() * and use the resulting NodeLabel object to update the database. * * @param nodeID * Unique identifier of the node to be updated. * @param nodeLabel * Object containing label and source values. * @param dbConnection * SQL database connection * @throws java.sql.SQLException if any. * * @deprecated Use a {@link NodeDao#update(org.opennms.netmgt.model.OnmsNode)} method call instead */ public static void assignLabel(int nodeID, NodeLabel nodeLabel, Connection dbConnection) throws SQLException { if (nodeLabel == null) { nodeLabel = computeLabel(nodeID, dbConnection); } PreparedStatement stmt = null; final DBUtils d = new DBUtils(NodeLabel.class); try { // Issue SQL update to assign the 'nodelabel' && 'nodelabelsource' fields of the 'node' table stmt = dbConnection.prepareStatement(SQL_DB_UPDATE_NODE_LABEL); d.watch(stmt); int column = 1; // Node Label if (log().isDebugEnabled()) { log().debug("NodeLabel.assignLabel: Node label: " + nodeLabel.getLabel() + " source: " + nodeLabel.getSource()); } if (nodeLabel.getLabel() != null) { // nodeLabel may not exceed MAX_NODELABEL_LEN.if it does truncate it String label = nodeLabel.getLabel(); if (label.length() > MAX_NODE_LABEL_LENGTH) { label = label.substring(0, MAX_NODE_LABEL_LENGTH); } stmt.setString(column++, label); } else { stmt.setNull(column++, java.sql.Types.VARCHAR); } // Node Label Source stmt.setString(column++, String.valueOf(nodeLabel.getSource())); // Node ID stmt.setInt(column++, nodeID); stmt.executeUpdate(); } finally { d.cleanUp(); } } /** * This method determines what label should be associated with a particular * node. A database connection is retrieved from the Vault. * * WARNING: A properly instantiated and initialized Vault class object is * required prior to calling this method. This method will initially only be * called from the WEB UI. * * @param nodeID * Unique identifier of the node to be updated. * @return NodeLabel Object containing label and source values * @throws java.sql.SQLException if any. * * @deprecated Update this to use modern DAO methods instead of raw SQL */ public static NodeLabel computeLabel(int nodeID) throws SQLException { Connection dbConnection = Vault.getDbConnection(); try { return computeLabel(nodeID, dbConnection); } finally { Vault.releaseDbConnection(dbConnection); } } /** * This method determines what label should be associated with a particular * node. * * Algorithm for determining a node's label is as follows: 1) If node has a * NetBIOS name associated with it, the NetBIOS name is used as the node's * label. 2) If no NetBIOS name available, retrieve all the 'ipinterface' * table entries associated with the node with an 'isManaged' field value of * 'M' 3) Find the primary interface where "primary" is defined as the * managed interface with the smallest IP address (each IP address is * converted to an integer value -- the IP address with the smallest integer * value wins). 4) IF the primary interface's IP host name is known it * becomes the node's label. ELSE IF the node's MIB-II sysName value is * known it becomes the node's label ELSE the primary interface's IP address * becomes the node's label. * * NOTE: If for some reason a node has no "managed" interfaces null is * returned for the NodeLabel. * * @param nodeID * Unique identifier of the node to be updated. * @param dbConnection * SQL database connection * @return NodeLabel Object containing label and source values or null if * node does not have a primary interface. * @throws java.sql.SQLException if any. * * @deprecated Update this to use modern DAO methods instead of raw SQL */ public static NodeLabel computeLabel(int nodeID, Connection dbConnection) throws SQLException { // Issue SQL query to retrieve NetBIOS name associated with the node String netbiosName = null; PreparedStatement stmt = null; ResultSet rs = null; final DBUtils d = new DBUtils(NodeLabel.class); try { stmt = dbConnection.prepareStatement(SQL_DB_RETRIEVE_NETBIOS_NAME); d.watch(stmt); stmt.setInt(1, nodeID); rs = stmt.executeQuery(); d.watch(rs); // Process result set, retrieve node's sysname while (rs.next()) { netbiosName = rs.getString(1); } if (netbiosName != null) { // Truncate sysName if it exceeds max node label length if (netbiosName.length() > MAX_NODE_LABEL_LENGTH) { netbiosName = netbiosName.substring(0, MAX_NODE_LABEL_LENGTH); } if (log().isDebugEnabled()) { log().debug("NodeLabel.computeLabel: returning NetBIOS name as nodeLabel: " + netbiosName); } NodeLabel nodeLabel = new NodeLabel(netbiosName, SOURCE_NETBIOS); return nodeLabel; } } finally { d.cleanUp(); } // OK, if we get this far the node has no NetBIOS name associated with it so, // retrieve the primary interface select method property which indicates // the method to use for determining which interface on a multi-interface // system is to be deemed the primary interface. The primary interface // will then determine what the node's label is. String method = System.getProperty(NodeLabel.PROP_PRIMARY_INTERFACE_SELECT_METHOD); if (method == null) { method = DEFAULT_SELECT_METHOD; } if (!method.equals(SELECT_METHOD_MIN) && !method.equals(SELECT_METHOD_MAX)) { log().warn("Interface selection method is '" + method + "'. Valid values are 'min' & 'max'. Will use default value: " + DEFAULT_SELECT_METHOD); method = DEFAULT_SELECT_METHOD; } List<InetAddress> ipv4AddrList = new ArrayList<InetAddress>(); List<String> ipHostNameList = new ArrayList<String>(); // Issue SQL query to retrieve all managed interface IP addresses from 'ipinterface' table try { stmt = dbConnection.prepareStatement(SQL_DB_RETRIEVE_MANAGED_INTERFACES); d.watch(stmt); stmt.setInt(1, nodeID); rs = stmt.executeQuery(); d.watch(rs); // Process result set, store retrieved addresses/host names in lists loadAddressList(rs, ipv4AddrList, ipHostNameList); } catch (Throwable e) { log().warn("Exception thrown while fetching managed interfaces: " + e.getMessage(), e); } finally { d.cleanUp(); } InetAddress primaryAddr = selectPrimaryAddress(ipv4AddrList, method); // Make sure we found a primary address!!! // If no primary address was found it means that this node has no // managed interfaces. So lets go after all the non-managed interfaces // and select the primary interface from them. if (primaryAddr == null) { if (log().isDebugEnabled()) { log().debug("NodeLabel.computeLabel: unable to find a primary address for node " + nodeID + ", returning null"); } ipv4AddrList.clear(); ipHostNameList.clear(); try { // retrieve all non-managed interface IP addresses from 'ipinterface' table stmt = dbConnection.prepareStatement(SQL_DB_RETRIEVE_NON_MANAGED_INTERFACES); d.watch(stmt); stmt.setInt(1, nodeID); rs = stmt.executeQuery(); d.watch(rs); loadAddressList(rs, ipv4AddrList, ipHostNameList); } catch (Throwable e) { log().warn("Exception thrown while fetching managed interfaces: " + e.getMessage(), e); } finally { d.cleanUp(); } primaryAddr = selectPrimaryAddress(ipv4AddrList, method); } if (primaryAddr == null) { log().warn("Could not find primary interface for node " + nodeID + ", cannot compute nodelabel"); return new NodeLabel("Unknown", SOURCE_UNKNOWN); } // We now know the IP address of the primary interface so // now see if it has a IP host name int index = ipv4AddrList.indexOf(primaryAddr); String primaryHostName = ipHostNameList.get(index); // If length of string is > 0 then the primary interface has a hostname if (primaryHostName.length() != 0) { // Truncate host name if it exceeds max node label length if (primaryHostName.length() > MAX_NODE_LABEL_LENGTH) { primaryHostName = primaryHostName.substring(0, MAX_NODE_LABEL_LENGTH); } return new NodeLabel(primaryHostName, SOURCE_HOSTNAME); } // If we get this far either the primary interface does not have // a host name or the node does not have a primary interface... // so we need to use the node's sysName if available... // retrieve sysName for the node String primarySysName = null; try { stmt = dbConnection.prepareStatement(SQL_DB_RETRIEVE_SYSNAME); d.watch(stmt); stmt.setInt(1, nodeID); rs = stmt.executeQuery(); d.watch(rs); while (rs.next()) { primarySysName = rs.getString(1); } } finally { d.cleanUp(); } if (primarySysName != null && primarySysName.length() > 0) { // Truncate sysName if it exceeds max node label length if (primarySysName.length() > MAX_NODE_LABEL_LENGTH) { primarySysName = primarySysName.substring(0, MAX_NODE_LABEL_LENGTH); } NodeLabel nodeLabel = new NodeLabel(primarySysName, SOURCE_SYSNAME); return nodeLabel; } // If we get this far the node has no sysName either so we need to // use the ipAddress as the nodeLabel NodeLabel nodeLabel = new NodeLabel(primaryAddr.toString(), SOURCE_ADDRESS); return nodeLabel; } /** * Utility method for loading the address and host name lists from a result * set retrieved from the 'ipInterface' table of the database. * * @param rs * Database result set * @param ipv4AddrList * List of InetAddress objects representing the node's interfaces * @param ipHostNameList * List of IP host names associated with the node's interfaces. * * @throws SQLException * if there is any problem processing the information in the * result set. */ private static void loadAddressList(ResultSet rs, List<InetAddress> ipv4AddrList, List<String> ipHostNameList) throws SQLException { ThreadCategory log = log(); // Process result set, store retrieved addresses/host names in lists while (rs.next()) { InetAddress inetAddr = InetAddressUtils.getInetAddress(rs.getString(1)); ipv4AddrList.add(inetAddr); String hostName = rs.getString(2); // As a hack to get around the fact that the 'iphostname' field // will contain the IP address of the interface if the IP hostname // was not available we check to see if the hostname and address // are equivalent. The hostname is only added if they are different. // If the are the same, an empty string is added to the host name // list. if (hostName == null || hostName.equals(inetAddr.toString())) ipHostNameList.add(""); else ipHostNameList.add(hostName); if (log.isDebugEnabled()) log.debug("NodeLabel.computeLabel: adding address " + inetAddr.toString() + " with hostname: " + hostName); } } /** * Returns the primary interface from a list of addresses based on the * specified selection method. * * @param ipv4AddrList * List of addresses from which to select the primary interface. * @param method * String (either "min" or "max") which indicates how the primary * interface is to be selected. * * @return The InetAddress object from the address list which has been * selected as the primary interface. */ private static InetAddress selectPrimaryAddress(List<InetAddress> ipv4AddrList, String method) { // Determine which interface is the primary interface // (ie, the interface whose IP address when converted to an // integer is the smallest or largest depending upon the // configured selection method.) InetAddress primaryAddr = null; Iterator<InetAddress> iter = ipv4AddrList.iterator(); while (iter.hasNext()) { if (primaryAddr == null) { primaryAddr = iter.next(); } else { InetAddress currentAddr = iter.next(); byte[] current = currentAddr.getAddress(); byte[] primary = primaryAddr.getAddress(); if (method.equals(SELECT_METHOD_MIN)) { // Smallest address wins if (new ByteArrayComparator().compare(current, primary) < 0) { primaryAddr = currentAddr; } } else { // Largest address wins if (new ByteArrayComparator().compare(current, primary) > 0) { primaryAddr = currentAddr; } } } } return primaryAddr; } /** * This method is responsible for returning a String object which represents * the content of this NodeLabel. Primarily used for debugging purposes. * * @return String which represents the content of this NodeLabel */ public String toString() { StringBuffer buffer = new StringBuffer(); // Build the buffer buffer.append(m_nodeLabel); buffer.append(":"); buffer.append(m_nodeLabelSource); return buffer.toString(); } private static ThreadCategory log() { return ThreadCategory.getInstance(NodeLabel.class); } }