/*
|
* CDDL HEADER START
|
*
|
* The contents of this file are subject to the terms of the
|
* Common Development and Distribution License, Version 1.0 only
|
* (the "License"). You may not use this file except in compliance
|
* with the License.
|
*
|
* You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
|
* or http://forgerock.org/license/CDDLv1.0.html.
|
* See the License for the specific language governing permissions
|
* and limitations under the License.
|
*
|
* When distributing Covered Code, include this CDDL HEADER in each
|
* file and include the License file at legal-notices/CDDLv1_0.txt.
|
* If applicable, add the following below this CDDL HEADER, with the
|
* fields enclosed by brackets "[]" replaced with your own identifying
|
* information:
|
* Portions Copyright [yyyy] [name of copyright owner]
|
*
|
* CDDL HEADER END
|
*
|
*
|
* Copyright 2009-2010 Sun Microsystems, Inc.
|
*/
|
package org.opends.server.core.networkgroups;
|
|
|
|
import static org.opends.messages.CoreMessages.*;
|
|
import java.util.ArrayList;
|
import java.util.HashMap;
|
import java.util.List;
|
|
import org.opends.messages.Message;
|
import org.opends.server.admin.server.ConfigurationChangeListener;
|
import org.opends.server.admin.std.server.ResourceLimitsQOSPolicyCfg;
|
import org.opends.server.api.ClientConnection;
|
import org.opends.server.api.QOSPolicyFactory;
|
import org.opends.server.config.ConfigException;
|
import org.opends.server.core.DirectoryServer;
|
import org.opends.server.types.ByteString;
|
import org.opends.server.types.ConfigChangeResult;
|
import org.opends.server.types.InitializationException;
|
import org.opends.server.types.RawFilter;
|
import org.opends.server.types.ResultCode;
|
import org.opends.server.types.operation.PreParseOperation;
|
import org.opends.server.types.operation.PreParseSearchOperation;
|
|
|
|
/**
|
* This class defines a factory for creating user configurable resource
|
* limits policies.
|
*/
|
public final class ResourceLimitsPolicyFactory implements
|
QOSPolicyFactory<ResourceLimitsQOSPolicyCfg>
|
{
|
|
/**
|
* Policy implementation.
|
*/
|
private static final class Policy extends ResourceLimitsPolicy
|
implements
|
ConfigurationChangeListener<ResourceLimitsQOSPolicyCfg>
|
{
|
/**
|
* {@inheritDoc}
|
*/
|
public ConfigChangeResult applyConfigurationChange(
|
ResourceLimitsQOSPolicyCfg configuration)
|
{
|
ResultCode resultCode = ResultCode.SUCCESS;
|
boolean adminActionRequired = false;
|
ArrayList<Message> messages = new ArrayList<Message>();
|
|
// Save the configuration.
|
updateConfiguration(configuration);
|
|
return new ConfigChangeResult(resultCode, adminActionRequired,
|
messages);
|
}
|
|
|
|
/**
|
* {@inheritDoc}
|
*/
|
public boolean isConfigurationChangeAcceptable(
|
ResourceLimitsQOSPolicyCfg configuration,
|
List<Message> unacceptableReasons)
|
{
|
return ResourceLimitsPolicyFactory.validateConfiguration(
|
configuration, unacceptableReasons);
|
}
|
|
|
|
// Map containing the connections sorted by incoming IP address.
|
private final HashMap<String, Integer> connectionsPerIPMap =
|
new HashMap<String, Integer>();
|
|
// The maximum number of concurrent operations per connection.
|
private int maxConcurrentOpsPerConnection;
|
|
// The maximum number of connections in the network group.
|
private int maxConnections;
|
|
// The maximum number of connections coming from the same IP
|
// address.
|
private int maxConnectionsFromSameIP;
|
|
// The maximum number of operations per connection.
|
private int maxOpsPerConnection;
|
|
// The minimum substring length in a search.
|
private int minSearchSubstringLength;
|
|
// The lock for connections per IP map.
|
private final Object mutex = new Object();
|
|
// The maximum size for a search.
|
private int sizeLimit;
|
|
// The statistics for the resource limits policy.
|
private final ResourceLimitsPolicyStatistics statistics =
|
new ResourceLimitsPolicyStatistics();
|
|
// The maximum time for a search.
|
private int timeLimit;
|
|
// The time interval for throughput limits
|
private long interval;
|
private long intervalStartTime = 0;
|
|
// The max number of operations during the interval
|
private int maxOperationsPerInterval;
|
private int operationsPerInterval = 0;
|
|
/**
|
* Creates a new resource limits policy.
|
*/
|
private Policy()
|
{
|
// Nothing to do.
|
}
|
|
|
|
/**
|
* {@inheritDoc}
|
*/
|
@Override
|
void addConnection(ClientConnection connection)
|
{
|
synchronized (mutex)
|
{
|
// Update the statistics.
|
statistics.addClientConnection();
|
|
// Increment the number of connections from the given IP
|
// address.
|
String ip = connection.getClientAddress();
|
Integer currentCount = connectionsPerIPMap.get(ip);
|
if (currentCount == null)
|
{
|
connectionsPerIPMap.put(ip, 1);
|
}
|
else
|
{
|
connectionsPerIPMap.put(ip, currentCount + 1);
|
}
|
}
|
}
|
|
|
|
/**
|
* {@inheritDoc}
|
*/
|
@Override
|
int getMinSubstring()
|
{
|
return minSearchSubstringLength;
|
}
|
|
|
|
/**
|
* {@inheritDoc}
|
*/
|
@Override
|
int getSizeLimit()
|
{
|
return sizeLimit;
|
}
|
|
|
|
/**
|
* {@inheritDoc}
|
*/
|
@Override
|
ResourceLimitsPolicyStatistics getStatistics()
|
{
|
return statistics;
|
}
|
|
|
|
/**
|
* {@inheritDoc}
|
*/
|
@Override
|
int getTimeLimit()
|
{
|
return timeLimit;
|
}
|
|
|
|
/**
|
* {@inheritDoc}
|
*/
|
@Override
|
boolean isAllowed(ClientConnection connection,
|
PreParseOperation operation, boolean fullCheck,
|
List<Message> messages)
|
{
|
boolean result = true;
|
|
if (fullCheck)
|
{
|
// Check the total number of connections in the resource group
|
synchronized (mutex)
|
{
|
if (maxConnections > 0
|
&& statistics.getClientConnections() > maxConnections)
|
{
|
messages.add(INFO_ERROR_MAX_CONNECTIONS_LIMIT_EXCEEDED
|
.get());
|
result = false;
|
}
|
}
|
if (!result)
|
{
|
return result;
|
}
|
|
// Check the number of connections coming from the same IP
|
synchronized (mutex)
|
{
|
// Add the connection in the map
|
String ip = connection.getClientAddress();
|
|
Integer currentCount = connectionsPerIPMap.get(ip);
|
if (currentCount == null)
|
{
|
currentCount = new Integer(0);
|
}
|
|
if (maxConnectionsFromSameIP > 0
|
&& currentCount.intValue() > maxConnectionsFromSameIP)
|
{
|
messages
|
.add(INFO_ERROR_MAX_CONNECTIONS_FROM_SAME_IP_LIMIT_EXCEEDED
|
.get());
|
result = false;
|
}
|
}
|
if (!result)
|
{
|
return result;
|
}
|
}
|
|
// Check the max number of operations per connection
|
if (maxOpsPerConnection > 0
|
&& connection.getNumberOfOperations() > maxOpsPerConnection)
|
{
|
messages
|
.add(INFO_ERROR_MAX_OPERATIONS_PER_CONNECTION_LIMIT_EXCEEDED
|
.get());
|
return false;
|
}
|
|
// Check the max number of concurrent operations per connection
|
if (maxConcurrentOpsPerConnection > 0
|
&& connection.getOperationsInProgress().size()
|
> maxConcurrentOpsPerConnection)
|
{
|
messages.add(
|
INFO_ERROR_MAX_CONCURRENT_OPERATIONS_PER_CONNECTION_LIMIT_EXCEEDED
|
.get());
|
return false;
|
}
|
|
// If the operation is a search, check the min search substring
|
// length
|
if (operation != null
|
&& operation instanceof PreParseSearchOperation)
|
{
|
if (!checkSubstringFilter(((PreParseSearchOperation) operation)
|
.getRawFilter()))
|
{
|
messages
|
.add(INFO_ERROR_MIN_SEARCH_SUBSTRING_LENGTH_LIMIT_EXCEEDED
|
.get());
|
return false;
|
}
|
}
|
|
// Check the throughput
|
if (operation != null && maxOperationsPerInterval > 0) {
|
synchronized(mutex) {
|
long now = System.currentTimeMillis();
|
// if the start time has never been set, or the interval has already
|
// expired, reset the start time and number of operations
|
if (intervalStartTime == 0 || now > (intervalStartTime + interval)) {
|
intervalStartTime = now;
|
operationsPerInterval = 0;
|
}
|
|
operationsPerInterval++;
|
if (operationsPerInterval > maxOperationsPerInterval) {
|
messages.add(INFO_ERROR_MAX_THROUGHPUT_EXCEEDED.get(
|
maxOperationsPerInterval,interval));
|
result = false;
|
}
|
}
|
if (!result) {
|
return result;
|
}
|
}
|
|
return true;
|
}
|
|
|
|
/**
|
* {@inheritDoc}
|
*/
|
@Override
|
void removeConnection(ClientConnection connection)
|
{
|
synchronized (mutex)
|
{
|
// Update the statistics.
|
statistics.removeClientConnection();
|
|
// Decrement the number of connections from the given IP
|
// address.
|
String ip = connection.getClientAddress();
|
Integer currentCount = connectionsPerIPMap.get(ip);
|
if (currentCount != null)
|
{
|
if (currentCount == 1)
|
{
|
// This was the last connection.
|
connectionsPerIPMap.remove(ip);
|
}
|
else
|
{
|
connectionsPerIPMap.put(ip, currentCount - 1);
|
}
|
}
|
}
|
}
|
|
|
|
/**
|
* Checks whether a filter enforces minimum substring length. If the
|
* filter is a composed filter (AND, OR, NOT filters), each
|
* component of the filter is recursively checked. When the filter
|
* is a substring filter, this routine checks that the substring
|
* length is greater or equal to the minimum substring length. For
|
* other search filter types, true is returned.
|
*
|
* @param filter
|
* The LDAP search filter to be tested
|
* @return boolean indicating whether the filter conforms to the
|
* minimum substring length rule.
|
*/
|
private boolean checkSubstringFilter(RawFilter filter)
|
{
|
switch (filter.getFilterType())
|
{
|
case AND:
|
case OR:
|
ArrayList<RawFilter> filterComponents =
|
filter.getFilterComponents();
|
if (filterComponents != null)
|
{
|
for (RawFilter element : filterComponents)
|
{
|
if (!checkSubstringFilter(element))
|
{
|
return false;
|
}
|
}
|
}
|
return true;
|
case NOT:
|
return checkSubstringFilter(filter.getNOTComponent());
|
case SUBSTRING:
|
int length = 0;
|
ByteString subInitialElement = filter.getSubInitialElement();
|
if (subInitialElement != null)
|
{
|
length += subInitialElement.length();
|
}
|
ArrayList<ByteString> subAnyElements =
|
filter.getSubAnyElements();
|
if (subAnyElements != null)
|
{
|
for (ByteString element : subAnyElements)
|
{
|
length += element.length();
|
}
|
}
|
ByteString subFinalElement = filter.getSubFinalElement();
|
if (subFinalElement != null)
|
{
|
length += subFinalElement.length();
|
}
|
return length >= minSearchSubstringLength;
|
default:
|
return true;
|
}
|
}
|
|
|
|
// Updates this policy's configuration.
|
private void updateConfiguration(
|
ResourceLimitsQOSPolicyCfg configuration)
|
{
|
maxConnections = configuration.getMaxConnections();
|
maxConnectionsFromSameIP =
|
configuration.getMaxConnectionsFromSameIP();
|
maxOpsPerConnection = configuration.getMaxOpsPerConnection();
|
maxConcurrentOpsPerConnection =
|
configuration.getMaxConcurrentOpsPerConnection();
|
|
Integer tmpSizeLimit = configuration.getSizeLimit();
|
if (tmpSizeLimit != null)
|
{
|
sizeLimit = tmpSizeLimit;
|
}
|
else
|
{
|
sizeLimit = DirectoryServer.getSizeLimit();
|
}
|
|
Long tmpTimeLimit = configuration.getTimeLimit();
|
if (tmpTimeLimit != null)
|
{
|
timeLimit = tmpTimeLimit.intValue();
|
}
|
else
|
{
|
timeLimit = DirectoryServer.getTimeLimit();
|
}
|
|
minSearchSubstringLength = configuration.getMinSubstringLength();
|
|
// Update the Max Ops Per Time Interval parameters
|
long previousInterval = interval;
|
int previousMax = maxOperationsPerInterval;
|
|
interval = configuration.getMaxOpsInterval();
|
maxOperationsPerInterval = configuration.getMaxOpsPerInterval();
|
// If the values have been modified, reset the counters
|
if ((previousInterval != interval)
|
|| (previousMax != maxOperationsPerInterval)) {
|
intervalStartTime = 0;
|
operationsPerInterval = 0;
|
}
|
}
|
}
|
|
|
|
// Validates a configuration.
|
private static boolean validateConfiguration(
|
ResourceLimitsQOSPolicyCfg configuration,
|
List<Message> unacceptableReasons)
|
{
|
// maxOpsPerInterval must be positive
|
long tmpMaxOps = configuration.getMaxOpsInterval();
|
if (tmpMaxOps < 0) {
|
unacceptableReasons.add(ERR_MAX_OPS_PER_INTERVAL.get(tmpMaxOps));
|
return false;
|
}
|
|
return true;
|
}
|
|
|
|
/**
|
* Creates a new resource limits policy factory.
|
*/
|
public ResourceLimitsPolicyFactory()
|
{
|
// Nothing to do.
|
}
|
|
|
|
/**
|
* {@inheritDoc}
|
*/
|
public ResourceLimitsPolicy createQOSPolicy(
|
ResourceLimitsQOSPolicyCfg configuration) throws ConfigException,
|
InitializationException
|
{
|
Policy policy = new Policy();
|
|
// Save the configuration.
|
policy.updateConfiguration(configuration);
|
|
// Register change listener.
|
configuration.addResourceLimitsChangeListener(policy);
|
|
return policy;
|
}
|
|
|
|
/**
|
* {@inheritDoc}
|
*/
|
public boolean isConfigurationAcceptable(
|
ResourceLimitsQOSPolicyCfg configuration,
|
List<Message> unacceptableReasons)
|
{
|
return validateConfiguration(configuration, unacceptableReasons);
|
}
|
}
|