/* * Sun Public License * * The contents of this file are subject to the Sun Public License Version * 1.0 (the "License"). You may not use this file except in compliance with * the License. A copy of the License is available at http://www.sun.com/ * * The Original Code is the SLAMD Distributed Load Generation Engine. * The Initial Developer of the Original Code is Neil A. Wilson. * Portions created by Neil A. Wilson are Copyright (C) 2004-2010. * Some preexisting portions Copyright (C) 2002-2006 Sun Microsystems, Inc. * All Rights Reserved. * * Contributor(s): Bertold Kolics, Neil A. Wilson */ package com.slamd.resourcemonitor; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.TreeSet; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.slamd.common.Constants; import com.slamd.common.SLAMDException; import com.slamd.stat.StatTracker; import com.unboundid.ldap.sdk.Attribute; import com.unboundid.ldap.sdk.Entry; import com.unboundid.ldap.sdk.LDAPConnection; import com.unboundid.ldap.sdk.LDAPException; import com.unboundid.ldap.sdk.ResultCode; import com.unboundid.ldif.LDIFException; import com.unboundid.ldif.LDIFReader; /** * The LDAP resource monitor allows a resource with statistics exposed over * LDAP. */ public class LDAPResourceMonitor extends ResourceMonitor { /** * The name of the configuration property that specifies the address of the * directory server. */ public static final String PROPERTY_LDAP_HOST = "ldap_address"; /** * The default address to use for the LDAP server. */ public static final String DEFAULT_LDAP_HOST = "127.0.0.1"; /** * The name of the configuration property that specifies the port of the * LDAP server. */ public static final String PROPERTY_LDAP_PORT = "ldap_port"; /** * The default port to use for the LDAP server. */ public static final int DEFAULT_LDAP_PORT = 389; /** * The name of the configuration property that specifies the DN to use when * binding to the server. */ public static final String PROPERTY_LDAP_BIND_DN = "ldap_bind_dn"; /** * The default DN to use when binding to the LDAP server. */ public static final String DEFAULT_LDAP_BIND_DN = ""; /** * The name of the configuration property that specifies the password to use * when binding to the server. */ public static final String PROPERTY_LDAP_BIND_PW = "ldap_bind_pw"; /** * The default password to use when binding to the LDAP server. */ public static final String DEFAULT_LDAP_BIND_PW = ""; // The information we will use to create the stat trackers later. private int collectionInterval; // The information to use to connect to the directory server. private String ldapHost; private int ldapPort; private String bindDN; private String bindPW; // A flag that indicates whether the monitor information was retrieved during // initialization, and a variable used to hold the reason for the failure if // it was not successful. private boolean monitorRetrievedInInit = false; private String failureReason = null; // Array of monitored entries tracked by this resource monitor private LDAPMonitoredEntry[] monitoredEntries; /** * Utility method to allow parsing an LDIF file and print out the * corresponding configuration file. * @param args argument array * @throws IOException if the provided file cannot be opened for some reason */ public static void main(String args[]) throws IOException { if (args.length != 1) { System.err.println("Usage: java " + "com.slamd.resourcemonitor.LDAPResourceMonitor " + "<ldif file>"); return; } int counter = 0; LDIFReader ldifReader = new LDIFReader(args[0]); while (true) { Entry entry; try { entry = ldifReader.readEntry(); if (entry == null) { // All entries have been processed break; } } catch (LDIFException le) { if (le.mayContinueReading()) { System.err.println("A recoverable occurred while attempting to " + "read an entry at or near line number " + le.getLineNumber() + ": " + le.getMessage()); System.err.println("The entry will be skipped."); continue; } else { System.err.println("An unrecoverable occurred while attempting to " + "read an entry at or near line number " + le.getLineNumber() + ": " + le.getMessage()); System.err.println("LDIF processing will be aborted."); break; } } catch (IOException ioe) { System.err.println("An I/O error occurred while attempting to read " + "from the LDIF file: " + ioe.getMessage()); System.err.println("LDIF processing will be aborted."); break; } String propBase = String.format("entry%d.", counter); System.out.print(propBase); System.out.print("dn="); System.out.println(entry.getDN()); for (Attribute a : entry.getAttributes()) { String attrName = a.getBaseName(); String attrValue = a.getValue(); String tracker = "LongValueTracker"; try { double d = Double.parseDouble(attrValue); if (d != (long)d) { tracker = "FloatValueTracker"; } } catch (NumberFormatException e) { // it's not a number, skip this attribute continue; } String attrBase = String.format("%sattr.%s.", propBase, attrName); System.out.print(attrBase); System.out.print("display="); System.out.println(attrName); System.out.print(attrBase); System.out.print("tracker="); System.out.println(tracker); } System.out.println(); counter++; } ldifReader.close(); } /** * {@inheritDoc} */ public String getMonitorName() { return "LDAP Resource Monitor"; } /** * {@inheritDoc} */ public boolean clientSupported() { return true; } /** * {@inheritDoc} */ public void initializeStatistics(String clientID, String threadID, int collectionInterval) { // Capture the information now so that we can use it later. this.collectionInterval = collectionInterval; if (monitoredEntries != null) { final String prefix = String.format("ldap://%s:%d/: ", this.ldapHost, this.ldapPort); for (LDAPMonitoredEntry e : monitoredEntries) { Collection<StatTracker> trackers = e.getStatTrackers(); for (StatTracker t : trackers) { t.setClientID(clientID); t.setThreadID(threadID); t.setCollectionInterval(collectionInterval); t.setDisplayName(prefix + t.getDisplayName()); } } } } /** * {@inheritDoc} */ public ResourceMonitor newInstance() throws SLAMDException { final ResourceMonitor monitor = new LDAPResourceMonitor(); monitor.initialize(getMonitorClient(), getMonitorProperties()); return monitor; } /** * {@inheritDoc} */ @Override public void initializeMonitor() throws SLAMDException { this.ldapHost = getProperty(PROPERTY_LDAP_HOST, DEFAULT_LDAP_HOST); this.ldapPort = getProperty(PROPERTY_LDAP_PORT, DEFAULT_LDAP_PORT); this.bindDN = getProperty(PROPERTY_LDAP_BIND_DN, DEFAULT_LDAP_BIND_DN); this.bindPW = getProperty(PROPERTY_LDAP_BIND_PW, DEFAULT_LDAP_BIND_PW); // flag to indicate that error occured during initialization boolean failure = false; failureReason = null; monitorRetrievedInInit = false; final LDAPConnection conn = new LDAPConnection(); try { conn.connect(ldapHost, ldapPort); conn.bind(bindDN, bindPW); } catch (LDAPException le) { failure = true; failureReason = String.format( "Unable to establish an LDAP connection to %s:%d -- %s (%s)", this.ldapHost, this.ldapPort, le.getResultCode(), le.getMessage()); } if (! failure) { try { parseConf(); } catch (IllegalArgumentException e) { failure = true; } } monitorRetrievedInInit = (! failure); } /** * Parses the resource monitor configuration from the monitor properties. */ @SuppressWarnings("unchecked") private void parseConf() { // expand variables in property values Map<String,String> preProcessedProps = LDAPResourceMonitor.preProcess(getMonitorProperties()); // this will hold all properties under the same property base // key: property base // value: properties under this property base Map<String,Map<String,String>> entryProps = new HashMap<String,Map<String,String>>(); // walk over all the properties once for (Map.Entry<String,String> e : preProcessedProps.entrySet()) { final String key = e.getKey(); final String value = e.getValue(); int dot = key.indexOf("."); if (dot <= 0) { // configuration keys have _ in their name if (key.indexOf("_") < 0) { // neither a configuration key nor a property related to monitoring throw new IllegalArgumentException("Invalid property name: " + key); } continue; } String propertyBase = key.substring(0, dot); Map<String,String> monitoredEntry = entryProps.get(propertyBase); if (monitoredEntry == null) { monitoredEntry = new HashMap<String,String>(); entryProps.put(propertyBase, monitoredEntry); } monitoredEntry.put( key.substring(dot + 1), // remove the property base from the key value ); } List<LDAPMonitoredEntry> entries = new ArrayList<LDAPMonitoredEntry>(); for (Map.Entry<String,Map<String,String>> entry : entryProps.entrySet()) { entries.add(new LDAPMonitoredEntry( entry.getKey(), // property base entry.getValue() // properties under the base )); } this.monitoredEntries = entries.toArray(new LDAPMonitoredEntry[entries.size()]); } /** * Preprocesses the monitor properties and executes variable substitution. * Variable names are enclosed within '@' characters. * @param props properties to process * @return map of key, value pairs with expanded variables */ private static Map<String, String> preProcess(Properties props) { // pattern matches property names enclosed within '@' characters // no space and no '@' is allowed in property names final Pattern varPattern = Pattern.compile("@([^@ ]+)@"); final Map<String,String> preProcessedMap = new HashMap<String,String>(); for (Map.Entry<Object,Object> e : props.entrySet()) { final String key = e.getKey().toString(); String value = e.getValue().toString(); Matcher varPatternMatcher = varPattern.matcher(value); // loop until matches found while (varPatternMatcher.find()) { String varName = varPatternMatcher.group(1); if (key.equals(varName)) { // circular reference found, stop expansion break; } // get the value of the string enclosed within the '@'-s (e.g. // ldap_port) String replacement = props.get(varName).toString(); if (replacement != null) { // replace first occurrence value = varPatternMatcher.replaceFirst(replacement); // create a new matcher for the new string // there could be other variables in the same value varPatternMatcher = varPattern.matcher(value); } } preProcessedMap.put(key, value); } return preProcessedMap; } /** * {@inheritDoc} */ public StatTracker[] getResourceStatistics() { // this set will sort the trackers according to their display names Set<StatTracker> stats = new TreeSet<StatTracker>( new Comparator<StatTracker>() { public int compare(StatTracker t1, StatTracker t2) { // display names are always set in the LDAP Resource Monitor return t1.getDisplayName().compareTo(t2.getDisplayName()); } } ); if (monitoredEntries != null) { for (LDAPMonitoredEntry e : monitoredEntries) { // populate stat trackers first for (LDAPMonitoredAttr a : e.getMonitoredAttrs()) { a.collectCapturedData(); } // retrive the stat trackers stats.addAll(e.getStatTrackers()); } } return stats.toArray(new StatTracker[stats.size()]); } /** * {@inheritDoc} */ public int runMonitor() { if (! monitorRetrievedInInit) { if (failureReason == null) { logMessage("Unable to retrieve LDAP server monitor information " + "during initialization"); } else { logMessage("Unable to retrieve LDAP server monitor information " + "during initialization: " + failureReason); } return Constants.JOB_STATE_STOPPED_DUE_TO_ERROR; } if (monitoredEntries == null) { logMessage("Resource monitor has nothing to monitor"); return Constants.JOB_STATE_STOPPED_DUE_TO_ERROR; } LDAPConnection conn = new LDAPConnection(); try { conn.connect(this.ldapHost, this.ldapPort); conn.bind(this.bindDN, this.bindPW); } catch (LDAPException le) { logMessage( String.format( "Unable to connect to directory server %s:%d -- %s (%s)", this.ldapHost, this.ldapPort, le.getResultCode(), le.getMessage() ) ); return Constants.JOB_STATE_STOPPED_DUE_TO_ERROR; } while (! shouldStop()) { long nextStartTime = System.currentTimeMillis() + (1000 * collectionInterval); try { captureData(conn); } catch (LDAPException le) { try { conn.close(); } catch (Exception e) { // ignore } logMessage( String.format( "Exception encountered while trying to process monitor " + "data: %s (%s) ", le.getResultCode(), le.getMessage() ) ); return Constants.JOB_STATE_STOPPED_DUE_TO_ERROR; } long sleepTime = nextStartTime - System.currentTimeMillis(); if (sleepTime > 0) { try { Thread.sleep(sleepTime); } catch (InterruptedException ie) { // ignore } } } try { conn.close(); } catch (Exception e) { // ignore } return Constants.JOB_STATE_COMPLETED_SUCCESSFULLY; } /** * Retrieves the monitor information and processes it as necessary. * * @param conn The connection to the directory server to use when reading * the information. * * @throws LDAPException If a problem occurs while interacting with the * directory server. */ private void captureData(LDAPConnection conn) throws LDAPException { for (LDAPMonitoredEntry e : this.monitoredEntries) { final String[] monitoredAttrNames = e.getMonitoredAttrNames(); // fetch the monitored LDAP entry Entry ldapEntry = conn.getEntry(e.getDN(), e.getMonitoredAttrNames()); if (ldapEntry == null) { // skip missing entries continue; } // capture data for all monitored attributes in the monitored entry for (String monitoredAttrName : monitoredAttrNames) { String ldapValue = ldapEntry.getAttributeValue(monitoredAttrName); if (ldapValue == null) { // attribute is missing, set value to 0 ldapValue = "0"; } final LDAPMonitoredAttr monitoredAttr = e.getMonitoredAttr(monitoredAttrName); try { monitoredAttr.addCapturedData(ldapValue); } catch (NumberFormatException e1) { throw new LDAPException(ResultCode.NO_RESULTS_RETURNED, String.format("Unable to parse the value of %s in %s as %s.", monitoredAttrName, e.getDN(), monitoredAttr.getAttrType()) ); } } } } }