/* * 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 * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * 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 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. 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); } }