/*
* Copyright 2008-2010 UnboundID Corp.
* All Rights Reserved.
*/
/*
* 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) 2008-2010.
* All Rights Reserved.
*
* Contributor(s): Neil A. Wilson
*/
package com.slamd.jobs;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import com.slamd.job.UnableToRunException;
import com.slamd.parameter.IntegerParameter;
import com.slamd.parameter.InvalidValueException;
import com.slamd.parameter.Parameter;
import com.slamd.parameter.ParameterList;
import com.slamd.parameter.PlaceholderParameter;
import com.slamd.parameter.StringParameter;
import com.slamd.stat.CategoricalTracker;
import com.slamd.stat.IncrementalTracker;
import com.slamd.stat.RealTimeStatReporter;
import com.slamd.stat.StatTracker;
import com.slamd.stat.TimeTracker;
import com.unboundid.ldap.sdk.LDAPConnection;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.LDAPResult;
import com.unboundid.ldap.sdk.SearchResultEntry;
import com.unboundid.util.FixedRateBarrier;
/**
* This class provides a SLAMD job class that may be used to perform modify DN
* operations against an LDAP directory server.
*/
public class LDAPModDNRateJobClass
extends LDAPJobClass
{
/**
* The display name for the stat tracker used to track result codes.
*/
private static final String STAT_RESULT_CODES = "Result Codes";
/**
* The display name for the stat tracker used to track operation durations.
*/
private static final String STAT_DURATION = "Modify DN Duration (ms)";
/**
* The display name for the stat tracker used to track operations completed.
*/
private static final String STAT_COMPLETED = "Modify DN Operations Completed";
/**
* The display name for the stat tracker used to track operations exceeding
* the response time threshold.
*/
private static final String STAT_EXCEEDING_THRESHOLD =
"Modify DN Operations Exceeding Response Time Threshold";
// Variables used to hold the values of the parameters.
private static int lowerBound;
private static int responseTimeThreshold;
private static int upperBound;
private static long timeBetweenRequests;
private static String parentDN;
private static String rdnAttribute;
private static String rdnValuePrefix;
private static String rdnValueSuffix;
// Stat trackers used by this job.
private CategoricalTracker resultCodes;
private IncrementalTracker modDNsCompleted;
private IncrementalTracker modDNsExceedingThreshold;
private TimeTracker modDNTimer;
// The number of threads that are currently in the first pass.
private static AtomicInteger activeThreads;
// The entry number counter for this job.
private static AtomicInteger entryNumber;
// The rate limiter for this job.
private static FixedRateBarrier rateLimiter;
// The LDAP connection used by this thread.
private LDAPConnection conn;
// The parameters used by this job.
private IntegerParameter lowerBoundParameter = new IntegerParameter(
"lowerBound", "Lower Bound",
"The lower bound of the numeric portion of the RDN value.", true, 0,
true, 0, false, 0);
private IntegerParameter maxRateParameter = new IntegerParameter("maxRate",
"Max Operation Rate (Ops/Second)",
"Specifies the maximum operation rate (in operations per second) to " +
"attempt to maintain. A value less than or equal to zero " +
"indicates that the client should attempt to process operations " +
"as quickly as possible.",
true, -1);
private IntegerParameter rateLimitDurationParameter = new IntegerParameter(
"maxRateDuration", "Max Rate Enforcement Interval (Seconds)",
"Specifies the duration in seconds of the interval over which to " +
"attempt to maintain the configured maximum rate. A value of " +
"zero indicates that it should be equal to the statistics " +
"collection interval. Large values may allow more variation but " +
"may be more accurate over time. Small values can better " +
"ensure that the rate doesn't exceed the requested level but may " +
"be less able to achieve the desired rate.",
true, 0, true,0, false, 0);
private IntegerParameter thresholdParameter = new IntegerParameter(
"threshold", "Response Time Threshold (ms)",
"Specifies a threshold in milliseconds for which to count the number " +
"of operations that take longer than this time to complete. A " +
"value less than or equal to zero indicates that there will not " +
"be any threshold.",
false, -1);
private IntegerParameter timeBetweenRequestsParameter = new IntegerParameter(
"timeBetweenRequests", "Time Between Requests (ms)",
"The minimum length of time in milliseconds that should pass between " +
"the beginning of one request and the beginning of the next.",
false, 0, true, 0, false, 0);
private IntegerParameter upperBoundParameter = new IntegerParameter(
"upperBound", "Upper Bound",
"The upper bound of the numeric portion of the RDN value.", true, 0,
true, 0, false, 0);
private StringParameter parentDNParameter = new StringParameter("parentDN",
"Parent DN", "The parent DN for all entries to be renamed.", true, null);
private StringParameter rdnAttributeParameter = new StringParameter("rdnAttr",
"RDN Attribute",
"The name of the RDN attribute for the entries to be renamed.", true,
"uid");
private StringParameter rdnPrefixParameter = new StringParameter("rdnPrefix",
"RDN Value Prefix",
"The string (if any) that appears before the numeric portion of the " +
"RDN value.",
false, null);
private StringParameter rdnSuffixParameter = new StringParameter("rdnSuffix",
"RDN Value Suffix",
"The string (if any) that appears after the numeric portion of the " +
"RDN value.",
false, null);
/**
* Creates a new instance of this job class.
*/
public LDAPModDNRateJobClass()
{
super();
}
/**
* {@inheritDoc}
*/
@Override()
public String getJobName()
{
return "LDAP Modify DN Rate";
}
/**
* {@inheritDoc}
*/
@Override()
public String getShortDescription()
{
return "Perform repeated LDAP modify DN operations";
}
/**
* {@inheritDoc}
*/
@Override()
public String[] getLongDescription()
{
return new String[]
{
"This job can be used to perform repeated modify DN operations against " +
"an LDAP directory server. Each entry in the range will be processed " +
"twice. The first set of modify DN operations will rename all entries " +
"in the specified range to append the job ID to the current value, and " +
"second set of operations will remove the job ID so that the entries " +
"are renamed back to their original names."
};
}
/**
* {@inheritDoc}
*/
@Override()
public int overrideNumClients()
{
return 1;
}
/**
* {@inheritDoc}
*/
@Override()
protected List<Parameter> getNonLDAPParameterStubs()
{
return Arrays.asList(
new PlaceholderParameter(),
rdnAttributeParameter,
rdnPrefixParameter,
lowerBoundParameter,
upperBoundParameter,
rdnSuffixParameter,
parentDNParameter,
new PlaceholderParameter(),
thresholdParameter,
maxRateParameter,
rateLimitDurationParameter,
timeBetweenRequestsParameter);
}
/**
* {@inheritDoc}
*/
@Override()
public StatTracker[] getStatTrackerStubs(final String clientID,
final String threadID,
final int collectionInterval)
{
return new StatTracker[]
{
new IncrementalTracker(clientID, threadID, STAT_COMPLETED,
collectionInterval),
new TimeTracker(clientID, threadID, STAT_DURATION,
collectionInterval),
new CategoricalTracker(clientID, threadID, STAT_RESULT_CODES,
collectionInterval),
new IncrementalTracker(clientID, threadID, STAT_EXCEEDING_THRESHOLD,
collectionInterval),
};
}
/**
* {@inheritDoc}
*/
@Override()
public StatTracker[] getStatTrackers()
{
if (responseTimeThreshold > 0)
{
return new StatTracker[]
{
modDNsCompleted,
modDNTimer,
resultCodes,
modDNsExceedingThreshold
};
}
else
{
return new StatTracker[]
{
modDNsCompleted,
modDNTimer,
resultCodes
};
}
}
/**
* {@inheritDoc}
*/
@Override()
protected void validateNonLDAPJobInfo(final int numClients,
final int threadsPerClient,
final int threadStartupDelay,
final Date startTime,
final Date stopTime,
final int duration,
final int collectionInterval,
final ParameterList parameters)
throws InvalidValueException
{
// Make sure that neither a stop time nor a duration were specified.
if ((stopTime != null) || (duration > 0))
{
throw new InvalidValueException("Neither a stop time nor a duration " +
"may be defined when scheduling this job.");
}
// Make sure that the upper bound is greater than the lower bound.
IntegerParameter lowerParam =
parameters.getIntegerParameter(lowerBoundParameter.getName());
IntegerParameter upperParam =
parameters.getIntegerParameter(upperBoundParameter.getName());
if (lowerParam.getIntValue() >= upperParam.getIntValue())
{
throw new InvalidValueException("The upper bound must be greater than " +
"the lower bound.");
}
}
/**
* {@inheritDoc}
*/
@Override()
protected boolean testNonLDAPJobParameters(final ParameterList parameters,
final LDAPConnection connection,
final ArrayList<String> outputMessages)
{
boolean successful = true;
// Ensure that the parent DN exists.
StringParameter parentDNParam =
parameters.getStringParameter(parentDNParameter.getName());
if ((parentDNParam != null) && parentDNParam.hasValue())
{
try
{
String base = parentDNParam.getStringValue();
outputMessages.add("Ensuring that parent entry '" + base +
"' exists....");
SearchResultEntry e = connection.getEntry(base);
if (e == null)
{
outputMessages.add("ERROR: The parent entry does not exist.");
successful = false;
}
else
{
outputMessages.add("The parent entry exists.");
}
}
catch (Exception e)
{
successful = false;
outputMessages.add("Unable to perform the search: " +
stackTraceToString(e));
}
outputMessages.add("");
}
return successful;
}
/**
* {@inheritDoc}
*/
@Override()
protected void initializeClientNonLDAP(final String clientID,
final ParameterList parameters)
throws UnableToRunException
{
activeThreads =
new AtomicInteger(getClientSideJob().getThreadsPerClient());
rdnAttributeParameter =
parameters.getStringParameter(rdnAttributeParameter.getName());
rdnAttribute = rdnAttributeParameter.getStringValue();
rdnValuePrefix = "";
rdnPrefixParameter =
parameters.getStringParameter(rdnPrefixParameter.getName());
if ((rdnPrefixParameter != null) && rdnPrefixParameter.hasValue())
{
rdnValuePrefix = rdnPrefixParameter.getStringValue();
}
lowerBoundParameter =
parameters.getIntegerParameter(lowerBoundParameter.getName());
lowerBound = lowerBoundParameter.getIntValue();
entryNumber = new AtomicInteger(lowerBound);
upperBoundParameter =
parameters.getIntegerParameter(upperBoundParameter.getName());
upperBound = upperBoundParameter.getIntValue();
rdnValueSuffix = "";
rdnSuffixParameter =
parameters.getStringParameter(rdnSuffixParameter.getName());
if ((rdnSuffixParameter != null) && rdnSuffixParameter.hasValue())
{
rdnValueSuffix = rdnSuffixParameter.getStringValue();
}
parentDNParameter =
parameters.getStringParameter(parentDNParameter.getName());
parentDN = parentDNParameter.getStringValue();
responseTimeThreshold = -1;
thresholdParameter =
parameters.getIntegerParameter(thresholdParameter.getName());
if ((thresholdParameter != null) && thresholdParameter.hasValue())
{
responseTimeThreshold = thresholdParameter.getIntValue();
}
timeBetweenRequests = 0L;
timeBetweenRequestsParameter =
parameters.getIntegerParameter(timeBetweenRequestsParameter.getName());
if ((timeBetweenRequestsParameter != null) &&
timeBetweenRequestsParameter.hasValue())
{
timeBetweenRequests = timeBetweenRequestsParameter.getIntValue();
}
rateLimiter = null;
maxRateParameter =
parameters.getIntegerParameter(maxRateParameter.getName());
if ((maxRateParameter != null) && maxRateParameter.hasValue())
{
int maxRate = maxRateParameter.getIntValue();
if (maxRate > 0)
{
int rateIntervalSeconds = 0;
rateLimitDurationParameter = parameters.getIntegerParameter(
rateLimitDurationParameter.getName());
if ((rateLimitDurationParameter != null) &&
rateLimitDurationParameter.hasValue())
{
rateIntervalSeconds = rateLimitDurationParameter.getIntValue();
}
if (rateIntervalSeconds <= 0)
{
rateIntervalSeconds = getClientSideJob().getCollectionInterval();
}
rateLimiter = new FixedRateBarrier(rateIntervalSeconds * 1000L,
maxRate * rateIntervalSeconds);
}
}
}
/**
* {@inheritDoc}
*/
@Override()
public void initializeThread(final String clientID, final String threadID,
final int collectionInterval,
final ParameterList parameters)
throws UnableToRunException
{
modDNsCompleted = new IncrementalTracker(clientID, threadID,
STAT_COMPLETED, collectionInterval);
modDNTimer = new TimeTracker(clientID, threadID, STAT_DURATION,
collectionInterval);
resultCodes = new CategoricalTracker(clientID, threadID, STAT_RESULT_CODES,
collectionInterval);
modDNsExceedingThreshold = new IncrementalTracker(clientID, threadID,
STAT_EXCEEDING_THRESHOLD, collectionInterval);
RealTimeStatReporter statReporter = getStatReporter();
if (statReporter != null)
{
String jobID = getJobID();
modDNsCompleted.enableRealTimeStats(statReporter, jobID);
modDNTimer.enableRealTimeStats(statReporter, jobID);
modDNsExceedingThreshold.enableRealTimeStats(statReporter, jobID);
}
try
{
conn = createConnection();
}
catch (Exception e)
{
throw new UnableToRunException("Unable to establish a connection to " +
"the target server: " + stackTraceToString(e), e);
}
}
/**
* {@inheritDoc}
*/
@Override()
public void finalizeThread()
{
if (conn != null)
{
conn.close();
conn = null;
}
}
/**
* {@inheritDoc}
*/
@Override()
public void runJob()
{
String jobID = getJobID();
String prefix = rdnAttribute + '=' + rdnValuePrefix;
String dnSuffix = rdnValueSuffix + ',' + parentDN;
String newRDNSuffix = rdnValueSuffix + '-' + jobID;
StringBuilder dnBuffer = new StringBuilder();
StringBuilder newRDNBuffer = new StringBuilder();
modDNsCompleted.startTracker();
modDNTimer.startTracker();
resultCodes.startTracker();
modDNsExceedingThreshold.startTracker();
// Iterate through the entries to rename them to include the job ID.
while (! shouldStop())
{
if (rateLimiter != null)
{
if (rateLimiter.await())
{
continue;
}
}
long opStartTime = System.currentTimeMillis();
int i = entryNumber.getAndIncrement();
if (i > upperBound)
{
break;
}
dnBuffer.setLength(0);
dnBuffer.append(prefix);
dnBuffer.append(i);
dnBuffer.append(dnSuffix);
newRDNBuffer.setLength(0);
newRDNBuffer.append(prefix);
newRDNBuffer.append(i);
newRDNBuffer.append(newRDNSuffix);
modDNTimer.startTimer();
try
{
LDAPResult modDNResult = conn.modifyDN(dnBuffer.toString(),
newRDNBuffer.toString(), true);
resultCodes.increment(modDNResult.getResultCode().toString());
}
catch (LDAPException le)
{
resultCodes.increment(le.getResultCode().toString());
}
finally
{
modDNTimer.stopTimer();
modDNsCompleted.increment();
if ((responseTimeThreshold > 0) &&
(modDNTimer.getLastOperationTime() > responseTimeThreshold))
{
modDNsExceedingThreshold.increment();
}
}
if (timeBetweenRequests > 0)
{
long elapsedTime = System.currentTimeMillis() - opStartTime;
long sleepTime = timeBetweenRequests - elapsedTime;
if (sleepTime > 0)
{
try
{
Thread.sleep(sleepTime);
} catch (Exception e) {}
}
}
}
// Wait until all of the threads have completed the first modify DN before
// starting the second.
int remainingActive = activeThreads.decrementAndGet();
if (remainingActive == 0)
{
entryNumber.set(lowerBound);
activeThreads.decrementAndGet();
}
else
{
while (activeThreads.get() >= 0)
{
try
{
Thread.sleep(0, 1);
} catch (Exception e) {}
}
}
// Iterate through the entries to rename them to remove the job ID.
dnSuffix = rdnValueSuffix + '-' + jobID + ',' + parentDN;
newRDNSuffix = rdnValueSuffix;
while (! shouldStop())
{
if (rateLimiter != null)
{
if (rateLimiter.await())
{
continue;
}
}
long opStartTime = System.currentTimeMillis();
int i = entryNumber.getAndIncrement();
if (i > upperBound)
{
break;
}
dnBuffer.setLength(0);
dnBuffer.append(prefix);
dnBuffer.append(i);
dnBuffer.append(dnSuffix);
newRDNBuffer.setLength(0);
newRDNBuffer.append(prefix);
newRDNBuffer.append(i);
newRDNBuffer.append(newRDNSuffix);
modDNTimer.startTimer();
try
{
LDAPResult modDNResult = conn.modifyDN(dnBuffer.toString(),
newRDNBuffer.toString(), true);
resultCodes.increment(modDNResult.getResultCode().toString());
}
catch (LDAPException le)
{
resultCodes.increment(le.getResultCode().toString());
}
finally
{
modDNTimer.stopTimer();
modDNsCompleted.increment();
if ((responseTimeThreshold > 0) &&
(modDNTimer.getLastOperationTime() > responseTimeThreshold))
{
modDNsExceedingThreshold.increment();
}
}
if (timeBetweenRequests > 0)
{
long elapsedTime = System.currentTimeMillis() - opStartTime;
long sleepTime = timeBetweenRequests - elapsedTime;
if (sleepTime > 0)
{
try
{
Thread.sleep(sleepTime);
} catch (Exception e) {}
}
}
}
modDNsCompleted.stopTracker();
modDNTimer.stopTracker();
resultCodes.stopTracker();
modDNsExceedingThreshold.stopTracker();
}
/**
* {@inheritDoc}
*/
@Override()
public synchronized void destroyThread()
{
if (conn != null)
{
conn.close();
conn = null;
}
}
}