/* * 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): Neil A. Wilson */ package com.slamd.resourcemonitor; import netscape.ldap.LDAPAttribute; import netscape.ldap.LDAPConnection; import netscape.ldap.LDAPEntry; import netscape.ldap.LDAPInterruptedException; import netscape.ldap.LDAPModification; import netscape.ldap.LDAPSearchConstraints; import netscape.ldap.LDAPSearchResults; import netscape.ldap.controls.LDAPPersistSearchControl; import com.slamd.common.Constants; import com.slamd.common.SLAMDException; import com.slamd.stat.RealTimeStatReporter; import com.slamd.stat.StatTracker; import com.slamd.stat.TimeTracker; /** * This class defines a SLAMD resource monitor that can be used to monitor the * latency associated with replication between two instances of the Sun ONE * Directory Server. It operates by periodically performing a modify operation * on a master directory and using a persistent search on a replica to detect * that change. * * * @author Neil A. Wilson */ public class ReplicationLatencyResourceMonitor extends ResourceMonitor { /** * The display name of the stat tracker used to keep track of replication * latency. */ public static final String STAT_TRACKER_REPLICATION_LATENCY = "Replication Latency (ms)"; /** * The name of the configuration property that specifies the address of the * master directory server. */ public static final String PROPERTY_MASTER_HOST = "master_host"; /** * The name of the configuration property that specifies the port of the * master directory server. */ public static final String PROPERTY_MASTER_PORT = "master_port"; /** * The name of the configuration property that specifies the DN to use to bind * to the master directory server. */ public static final String PROPERTY_MASTER_BIND_DN = "master_bind_dn"; /** * The name of the configuration property that specifies the password to use * to bind to the master directory server. */ public static final String PROPERTY_MASTER_BIND_PW = "master_bind_pw"; /** * The name of the configuration property that specifies the address of the * replica directory server. */ public static final String PROPERTY_REPLICA_HOST = "replica_host"; /** * The name of the configuration property that specifies the port of the * replica directory server. */ public static final String PROPERTY_REPLICA_PORT = "replica_port"; /** * The name of the configuration property that specifies the DN to use to bind * to the replica directory server. */ public static final String PROPERTY_REPLICA_BIND_DN = "replica_bind_dn"; /** * The name of the configuration property that specifies the password to use * to bind to the replica directory server. */ public static final String PROPERTY_REPLICA_BIND_PW = "replica_bind_pw"; /** * The name of the configuration property that specifies the DN of the entry * to modify on the master and to monitor on the replica. */ public static final String PROPERTY_LATENCY_CHECK_ENTRY_DN = "latency_check_entry_dn"; /** * The name of the configuration property that specifies the name of the * attribute that should be modified on the master directory to trigger * notification on the replica. */ public static final String PROPERTY_LATENCY_CHECK_MOD_ATTR = "attr_to_modify"; /** * The name of the configuration property that specifies the minimum delay in * milliseconds between modifications to the latency check entry DN on the * master server. */ public static final String PROPERTY_LATENCY_CHECK_DELAY = "latency_check_delay"; // Information used to connect to the servers. private int masterPort; private int replicaPort; private String masterBindDN; private String masterBindPW; private String masterHost; private String replicaBindDN; private String replicaBindPW; private String replicaHost; // Information about the modifications that should be made for latency // checking. private int latencyCheckDelay; private String attrToModify; private String latencyCheckEntryDN; // Variables used by the monitor thread to help ensure that this thread stops // in a timely manner when appropriate. protected boolean isStopped; protected boolean waitingOnPSearch; // The stat tracker used to measure replication latency. private TimeTracker latencyTimer; /** * Performs any initialization specific to this resource monitor. * * @throws SLAMDException If a problem occurs while performing the * initialization. */ @Override() public void initializeMonitor() throws SLAMDException { // Get the information needed to connect to the master server. masterHost = getProperty(PROPERTY_MASTER_HOST, null); if ((masterHost == null) || (masterHost.length() == 0)) { throw new SLAMDException("ERROR: No value provided for \"" + PROPERTY_MASTER_HOST + "\" configuration property"); } masterPort = getProperty(PROPERTY_MASTER_PORT, -1); if ((masterPort <= 0) || (masterPort > 65535)) { throw new SLAMDException("ERROR: Missing or invalid value for \"" + PROPERTY_MASTER_PORT + "\" configuration property"); } masterBindDN = getProperty(PROPERTY_MASTER_BIND_DN); masterBindPW = getProperty(PROPERTY_MASTER_BIND_PW); // Get the information needed to connect to the replica server. replicaHost = getProperty(PROPERTY_REPLICA_HOST, null); if ((replicaHost == null) || (replicaHost.length() == 0)) { throw new SLAMDException("ERROR: No value provided for \"" + PROPERTY_REPLICA_HOST + "\" configuration property"); } replicaPort = getProperty(PROPERTY_REPLICA_PORT, -1); if ((replicaPort <= 0) || (replicaPort > 65535)) { throw new SLAMDException("ERROR: Missing or invalid value for \"" + PROPERTY_REPLICA_PORT + "\" configuration property"); } replicaBindDN = getProperty(PROPERTY_REPLICA_BIND_DN); replicaBindPW = getProperty(PROPERTY_REPLICA_BIND_PW); // Get the other information needed for the latency checking process. latencyCheckEntryDN = getProperty(PROPERTY_LATENCY_CHECK_ENTRY_DN, null); if ((latencyCheckEntryDN == null) || (latencyCheckEntryDN.length() == 0)) { throw new SLAMDException("ERROR: No value provided for \"" + PROPERTY_LATENCY_CHECK_ENTRY_DN + "\" configuration property"); } attrToModify = getProperty(PROPERTY_LATENCY_CHECK_MOD_ATTR, null); if ((attrToModify == null) || (attrToModify.length() == 0)) { throw new SLAMDException("ERROR: No value provided for \"" + PROPERTY_LATENCY_CHECK_MOD_ATTR + "\" configuration property"); } latencyCheckDelay = getProperty(PROPERTY_LATENCY_CHECK_DELAY, -1); if (latencyCheckDelay < 0) { throw new SLAMDException("ERROR: Missing or invalid value for \"" + PROPERTY_LATENCY_CHECK_DELAY + "\" configuration property"); } isStopped = true; waitingOnPSearch = false; } /** * Indicates whether the current client system is supported for this resource * monitor. * * @return <CODE>true</CODE> if the current client system is supported for * this resource monitor, or <CODE>false</CODE> if not. */ @Override() public boolean clientSupported() { return true; } /** * Creates a new instance of this resource monitor thread. Note that the * <CODE>initialize()</CODE> method should have been called on the new * instance before it is returned. * * @return A new instance of this resource monitor thread. * * @throws SLAMDException If a problem occurs while creating or initializing * the resource monitor. */ @Override() public ResourceMonitor newInstance() throws SLAMDException { ReplicationLatencyResourceMonitor monitor = new ReplicationLatencyResourceMonitor(); monitor.initialize(getMonitorClient(), getMonitorProperties()); return monitor; } /** * Initializes the stat trackers maintained by this resource monitor. * * @param clientID The client ID to use for the stubs. * @param threadID The thread ID to use for the stubs. * @param collectionInterval The collection interval to use for the stubs. */ @Override() public void initializeStatistics(String clientID, String threadID, int collectionInterval) { String displayName = masterHost + ':' + masterPort + "->" + replicaHost + ':' + replicaPort + ' ' + STAT_TRACKER_REPLICATION_LATENCY; latencyTimer = new TimeTracker(clientID, threadID, displayName, collectionInterval); ResourceMonitorJob monitorJob = getMonitorJob(); if (monitorJob.enableRealTimeStats()) { String jobID = monitorJob.getJobID(); RealTimeStatReporter statReporter = monitorJob.getStatReporter(); latencyTimer.enableRealTimeStats(statReporter, jobID); } } /** * Retrieves the name to use for this resource monitor. * * @return The name to use for this resource monitor. */ @Override() public String getMonitorName() { return "LDAP Replication Latency"; } /** * Retrieves the statistical data collected by this resource monitor. * * @return The statistical data collected by this resource monitor. */ @Override() public StatTracker[] getResourceStatistics() { StatTracker[] trackers = new StatTracker[] { latencyTimer }; return trackers; } /** * Performs the work of actually collecting resource statistics. This method * should periodically call the <CODE>shouldStop()</CODE> method to determine * whether to stop collecting statistics. * * @return A value that indicates the status of the monitor when it * completed. */ @Override() public int runMonitor() { // Establish the connection to the master directory server. LDAPConnection masterConn = new LDAPConnection(); try { masterConn.connect(3, masterHost, masterPort, masterBindDN, masterBindPW); } catch (Exception e) { logMessage("Unable to connect to master directory server " + masterHost + ':' + masterPort + " -- " + e); return Constants.JOB_STATE_STOPPED_DUE_TO_ERROR; } // Establish the connection to the replica directory server. LDAPConnection replicaConn = new LDAPConnection(); try { replicaConn.connect(3, replicaHost, replicaPort, replicaBindDN, replicaBindPW); } catch (Exception e) { logMessage("Unable to connect to replica directory server " + replicaHost + ':' + masterPort + " -- " + e); return Constants.JOB_STATE_STOPPED_DUE_TO_ERROR; } // Register the persistent search against the replica directory. LDAPSearchResults psearchResults; try { LDAPPersistSearchControl psearchControl = new LDAPPersistSearchControl(LDAPPersistSearchControl.MODIFY, true, false, true); LDAPSearchConstraints searchConstraints = replicaConn.getSearchConstraints(); searchConstraints.setMaxResults(0); searchConstraints.setServerControls(psearchControl); psearchResults = replicaConn.search(latencyCheckEntryDN, LDAPConnection.SCOPE_BASE, "(|(objectClass=*)(objectClass=ldapSubentry))", null, false, searchConstraints); } catch (Exception e) { logMessage("Unable to register a persistent search with replica " + "directory server " + replicaHost + ':' + replicaPort + " -- " + e); return Constants.JOB_STATE_STOPPED_DUE_TO_ERROR; } // Create a thread that will be used to watch this resource monitor and // stop it as appropriate. isStopped = false; waitingOnPSearch = false; ReplicationLatencyResourceMonitorThread monitorThread = new ReplicationLatencyResourceMonitorThread(this); monitorThread.start(); // Start the latency checking stat tracker. latencyTimer.startTracker(); // Loop, making a change on the master server and then detecting it on the // replica. int jobState = Constants.JOB_STATE_COMPLETED_SUCCESSFULLY; while (! shouldStop()) { // Get the current time so that we can decide how long to sleep between // modifications of the latency check entry. long latencyCheckStartTime = System.currentTimeMillis(); // Modify the latency check entry. try { LDAPAttribute attr = new LDAPAttribute(attrToModify, String.valueOf(System.currentTimeMillis())); masterConn.modify(latencyCheckEntryDN, new LDAPModification(LDAPModification.REPLACE, attr)); } catch (Exception e) { logMessage("Unable to modify entry \"" + latencyCheckEntryDN + "\" on master server " + masterHost + ':' + masterPort + " -- " + e); jobState = Constants.JOB_STATE_STOPPED_DUE_TO_ERROR; break; } // Start the timer, since we are only interested in how long it took to // replicate the change, not in how long it takes to perform the change // and replicate it. latencyTimer.startTimer(); // Wait for the change to occur on the replica. waitingOnPSearch = true; if (psearchResults.hasMoreElements()) { try { Object element = psearchResults.nextElement(); if (element instanceof LDAPEntry) { // We detected a change to the entry, so stop the timer. latencyTimer.stopTimer(); } else if (element instanceof LDAPInterruptedException) { // This means that the user either cancelled the job, or that the // monitor thread interrupted the persistent search when it was time // to stop for one reason or another. break; } else { logMessage("Unexpected object returned by the persistent " + "search: " + element); jobState = Constants.JOB_STATE_STOPPED_DUE_TO_ERROR; break; } } catch (Exception e) { logMessage("An error occurred while trying to detect the change on " + "replica " + replicaHost + ':' + replicaPort + " -- " + e); jobState = Constants.JOB_STATE_STOPPED_DUE_TO_ERROR; break; } } else { logMessage("Unexpected end of persistent search results on replica " + replicaHost + ':' + replicaPort); jobState = Constants.JOB_STATE_STOPPED_DUE_TO_ERROR; break; } waitingOnPSearch = false; // Determine if we should sleep before the next check. if ((latencyCheckDelay > 0) && (! shouldStop())) { long elapsedTime = System.currentTimeMillis() - latencyCheckStartTime; if (latencyCheckDelay > elapsedTime) { try { Thread.sleep(latencyCheckDelay - elapsedTime); } catch (InterruptedException ie) {} } } } // Stop the tracker, close the connections to the server, and return. latencyTimer.stopTracker(); isStopped = true; waitingOnPSearch = false; monitorThread.requestStop(); try { masterConn.disconnect(); } catch (Exception e) {} try { replicaConn.disconnect(); } catch (Exception e) {} return jobState; } }