mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

abobrov
08.13.2008 100e551066fc2b42a7578906a0a7a734896a2961
- [Issue 1595] need a fast way to prime the FileSystemEntryCache:
Add generic entry cache pre-load mechanism which is backend and entry cache implementations independent.
3 files added
17 files modified
966 ■■■■■ changed files
opends/resource/config/config.ldif 3 ●●●● patch | view | raw | blame | history
opends/resource/schema/02-config.ldif 13 ●●●●● patch | view | raw | blame | history
opends/src/admin/defn/org/opends/server/admin/std/DefaultEntryCacheConfiguration.xml 69 ●●●●● patch | view | raw | blame | history
opends/src/admin/defn/org/opends/server/admin/std/RootConfiguration.xml 13 ●●●●● patch | view | raw | blame | history
opends/src/messages/messages/extension.properties 19 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/api/Backend.java 23 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/BackupBackend.java 13 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/LDIFBackend.java 20 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/MemoryBackend.java 15 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/MonitorBackend.java 14 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/RootDSEBackend.java 13 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/SchemaBackend.java 14 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/TrustStoreBackend.java 14 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/BackendImpl.java 50 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/task/TaskBackend.java 14 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/EntryCacheConfigManager.java 10 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/ConfigFileHandler.java 14 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/EntryCachePreloader.java 386 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/server/ReplicationBackend.java 14 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/PreloadEntryCacheTestCase.java 235 ●●●●● patch | view | raw | blame | history
opends/resource/config/config.ldif
@@ -20,7 +20,7 @@
#
# CDDL HEADER END
#
#      Portions Copyright 2006-2007 Sun Microsystems, Inc.
#      Portions Copyright 2006-2008 Sun Microsystems, Inc.
#
#
# This file contains the primary Directory Server configuration.  It must not
@@ -490,6 +490,7 @@
dn: cn=Entry Caches,cn=config
objectClass: top
objectClass: ds-cfg-branch
objectClass: ds-cfg-default-entry-cache
cn: Entry Caches
dn: cn=FIFO,cn=Entry Caches,cn=config
opends/resource/schema/02-config.ldif
@@ -21,7 +21,7 @@
# CDDL HEADER END
#
#
#      Portions Copyright 2006-2007 Sun Microsystems, Inc.
#      Portions Copyright 2006-2008 Sun Microsystems, Inc.
#
#
# This file contains the attribute type and objectclass definitions for use
@@ -2177,6 +2177,11 @@
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
  SINGLE-VALUE
  X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.446
  NAME 'ds-cfg-cache-preload'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
  SINGLE-VALUE
  X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.1
  NAME 'ds-cfg-access-control-handler'
  SUP top
@@ -3677,3 +3682,9 @@
  STRUCTURAL
  MUST ( ds-cfg-backend )
  X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.180
  NAME 'ds-cfg-default-entry-cache'
  SUP ds-cfg-branch
  STRUCTURAL
  MAY ds-cfg-cache-preload
  X-ORIGIN 'OpenDS Directory Server' )
opends/src/admin/defn/org/opends/server/admin/std/DefaultEntryCacheConfiguration.xml
New file
@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
  ! 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
  !
  !
  !      Portions Copyright 2008 Sun Microsystems, Inc.
  ! -->
<adm:managed-object name="default-entry-cache" plural-name="default-entry-cache"
  package="org.opends.server.admin.std"
  xmlns:adm="http://www.opends.org/admin"
  xmlns:ldap="http://www.opends.org/admin-ldap"
  xmlns:cli="http://www.opends.org/admin-cli">
  <adm:synopsis>
    <adm:user-friendly-name />
    represents the Directory Server entry cache framework.
  </adm:synopsis>
  <adm:tag name="database" />
  <adm:profile name="ldap">
    <ldap:object-class>
      <ldap:name>ds-cfg-default-entry-cache</ldap:name>
      <ldap:superior>ds-cfg-branch</ldap:superior>
    </ldap:object-class>
  </adm:profile>
  <adm:profile name="cli">
    <cli:managed-object custom="true" />
  </adm:profile>
  <adm:property name="cache-preload" mandatory="false">
    <adm:synopsis>
      Indicates whether or not to preload the entry cache on startup.
    </adm:synopsis>
    <adm:requires-admin-action>
      <adm:server-restart />
    </adm:requires-admin-action>
    <adm:default-behavior>
      <adm:defined>
        <adm:value>false</adm:value>
      </adm:defined>
    </adm:default-behavior>
    <adm:syntax>
      <adm:boolean />
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:name>ds-cfg-cache-preload</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
</adm:managed-object>
opends/src/admin/defn/org/opends/server/admin/std/RootConfiguration.xml
@@ -23,7 +23,7 @@
  ! CDDL HEADER END
  !
  !
  !      Portions Copyright 2007 Sun Microsystems, Inc.
  !      Portions Copyright 2007-2008 Sun Microsystems, Inc.
  ! -->
<adm:root-managed-object xmlns:adm="http://www.opends.org/admin"
  xmlns:ldap="http://www.opends.org/admin-ldap"
@@ -203,6 +203,17 @@
      </cli:relation>
    </adm:profile>
  </adm:relation>
  <adm:relation name="default-entry-cache">
    <adm:one-to-one />
    <adm:profile name="ldap">
      <ldap:rdn-sequence>cn=Entry Caches,cn=config</ldap:rdn-sequence>
    </adm:profile>
    <adm:profile name="cli">
      <cli:relation>
        <cli:default-property name="cache-preload" />
      </cli:relation>
    </adm:profile>
  </adm:relation>
  <adm:relation name="entry-cache">
    <adm:one-to-many />
    <adm:profile name="ldap">
opends/src/messages/messages/extension.properties
@@ -20,7 +20,7 @@
#
# CDDL HEADER END
#
#      Portions Copyright 2006-2007 Sun Microsystems, Inc.
#      Portions Copyright 2006-2008 Sun Microsystems, Inc.
@@ -72,6 +72,21 @@
 to initialize fifo entry cache:  %s
FATAL_ERR_SOFTREFCACHE_CANNOT_INITIALIZE_9=A fatal error occurred while \
 trying to initialize soft reference entry cache:  %s
NOTICE_CACHE_PRELOAD_PROGRESS_START_10=Starting the entry cache preload
NOTICE_CACHE_PRELOAD_PROGRESS_REPORT_11=The entry cache preload has processed \
 %d entries, %d MB free heap memory available
NOTICE_CACHE_PRELOAD_PROGRESS_DONE_12=The entry cache preload is complete \
 with the total of %d entries processed
SEVERE_WARN_CACHE_PRELOAD_INTERRUPTED_13=The entry cache preload has been \
 interrupted
SEVERE_ERR_CACHE_PRELOAD_COLLECTOR_FAILED_14=The entry cache preload was \
 unable to complete collector processing for %s backend, and as a result \
 the entry cache preload for this backend will be incomplete
SEVERE_WARN_CACHE_PRELOAD_BACKEND_FAILED_15=The entry cache preload is not \
 supported by %s backend, and as a result no entries from this backend will \
 be preloaded onto the entry cache
SEVERE_ERR_CACHE_PRELOAD_ENTRY_FAILED_16=Failed to preload %s entry onto \
 the entry cache:  %s
MILD_ERR_EXTOP_PASSMOD_ILLEGAL_REQUEST_ELEMENT_TYPE_32=The password modify \
 extended request sequence included an ASN.1 element of an invalid type:  %s
MILD_ERR_EXTOP_PASSMOD_CANNOT_DECODE_REQUEST_33=An unexpected error occurred \
@@ -1613,4 +1628,4 @@
SEVERE_ERR_SDTUACM_ATTR_UNINDEXED_569=The subject DN to user attribute \
 certificate mapper defined in configuration entry %s references attribute \
 type %s which is does not have an equality index defined in backend %s
opends/src/server/org/opends/server/api/Backend.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
 *      Portions Copyright 2006-2008 Sun Microsystems, Inc.
 */
package org.opends.server.api;
import org.opends.messages.Message;
@@ -30,6 +30,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
@@ -210,6 +211,26 @@
  /**
   * Retrieves the set of all DNs that are stored within this backend.
   * Note that this can be a slow operation depending on a particular
   * backend implementation and might be unsupported by some backends.
   *
   * @param   storedDNs  Collection to retrieve all stored DNs into.
   *          Note that for async operation a thread-safe collection
   *          should be used.
   *
   * @return  {@code true} if all DNs stored within this backend were
   *          successfully retrieved, or {@code false} otherwise.
   *
   * @throws  UnsupportedOperationException if backend implementation
   *          does not support this operation.
   */
  public abstract boolean collectStoredDNs(Collection<DN> storedDNs)
    throws UnsupportedOperationException;
  /**
   * Indicates whether the data associated with this backend may be
   * considered local (i.e., in a repository managed by the Directory
   * Server) rather than remote (i.e., in an external repository
opends/src/server/org/opends/server/backends/BackupBackend.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
 *      Portions Copyright 2006-2008 Sun Microsystems, Inc.
 */
package org.opends.server.backends;
@@ -1297,5 +1297,16 @@
         new AttributeValue(rdnAttrType, rdnStringValue);
    return parentDN.concat(RDN.create(rdnAttrType, attrValue));
  }
  /**
   * {@inheritDoc}
   */
  public boolean collectStoredDNs(Collection<DN> storedDNs)
    throws UnsupportedOperationException
  {
    throw new UnsupportedOperationException("Operation not supported.");
  }
}
opends/src/server/org/opends/server/backends/LDIFBackend.java
@@ -22,13 +22,14 @@
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 *      Portions Copyright 2007-2008 Sun Microsystems, Inc.
 */
package org.opends.server.backends;
import java.io.File;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
@@ -1572,5 +1573,22 @@
    return alerts;
  }
  /**
   * {@inheritDoc}
   */
  public boolean collectStoredDNs(Collection<DN> storedDNs)
    throws UnsupportedOperationException
  {
    backendLock.readLock().lock();
    try {
      storedDNs.addAll(entryMap.keySet());
      return true;
    } finally {
      backendLock.readLock().unlock();
    }
  }
}
opends/src/server/org/opends/server/backends/MemoryBackend.java
@@ -22,12 +22,13 @@
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
 *      Portions Copyright 2006-2008 Sun Microsystems, Inc.
 */
package org.opends.server.backends;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
@@ -987,5 +988,17 @@
    Message message = ERR_MEMORYBACKEND_BACKUP_RESTORE_NOT_SUPPORTED.get();
    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
  }
  /**
   * {@inheritDoc}
   */
  public synchronized boolean collectStoredDNs(Collection<DN> storedDNs)
    throws UnsupportedOperationException
  {
    storedDNs.addAll(entryMap.keySet());
    return true;
  }
}
opends/src/server/org/opends/server/backends/MonitorBackend.java
@@ -22,13 +22,14 @@
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
 *      Portions Copyright 2006-2008 Sun Microsystems, Inc.
 */
package org.opends.server.backends;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
@@ -1276,5 +1277,16 @@
    currentConfig = backendCfg;
    return new ConfigChangeResult(resultCode, adminActionRequired, messages);
  }
  /**
   * {@inheritDoc}
   */
  public boolean collectStoredDNs(Collection<DN> storedDNs)
    throws UnsupportedOperationException
  {
    throw new UnsupportedOperationException("Operation not supported.");
  }
}
opends/src/server/org/opends/server/backends/RootDSEBackend.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
 *      Portions Copyright 2006-2008 Sun Microsystems, Inc.
 */
package org.opends.server.backends;
@@ -1529,5 +1529,16 @@
    return new ConfigChangeResult(resultCode, adminActionRequired, messages);
  }
  /**
   * {@inheritDoc}
   */
  public boolean collectStoredDNs(Collection<DN> storedDNs)
    throws UnsupportedOperationException
  {
    throw new UnsupportedOperationException("Operation not supported.");
  }
}
opends/src/server/org/opends/server/backends/SchemaBackend.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
 *      Portions Copyright 2006-2008 Sun Microsystems, Inc.
 */
package org.opends.server.backends;
@@ -37,6 +37,7 @@
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
@@ -5575,5 +5576,16 @@
    return
          new Attribute(attributeTypesType, ATTR_ATTRIBUTE_TYPES, valueSetCopy);
  }
  /**
   * {@inheritDoc}
   */
  public boolean collectStoredDNs(Collection<DN> storedDNs)
    throws UnsupportedOperationException
  {
    throw new UnsupportedOperationException("Operation not supported.");
  }
}
opends/src/server/org/opends/server/backends/TrustStoreBackend.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 *      Portions Copyright 2007-2008 Sun Microsystems, Inc.
 */
package org.opends.server.backends;
@@ -42,6 +42,7 @@
import java.security.KeyStoreException;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
@@ -1898,5 +1899,16 @@
    }
  }
  /**
   * {@inheritDoc}
   */
  public boolean collectStoredDNs(Collection<DN> storedDNs)
    throws UnsupportedOperationException
  {
    throw new UnsupportedOperationException("Operation not supported.");
  }
}
opends/src/server/org/opends/server/backends/jeb/BackendImpl.java
@@ -22,9 +22,12 @@
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
 *      Portions Copyright 2007-2008 Sun Microsystems, Inc.
 */
package org.opends.server.backends.jeb;
import com.sleepycat.je.Cursor;
import com.sleepycat.je.CursorConfig;
import com.sleepycat.je.DatabaseEntry;
import org.opends.messages.Message;
import java.io.IOException;
@@ -39,6 +42,8 @@
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;
import com.sleepycat.je.RunRecoveryException;
import org.opends.server.admin.std.meta.LocalDBIndexCfgDefn;
@@ -67,6 +72,8 @@
import org.opends.server.admin.std.server.LocalDBBackendCfg;
import org.opends.server.admin.Configuration;
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.types.DN;
/**
 * This is an implementation of a Directory Server Backend which stores entries
@@ -1729,4 +1736,45 @@
      throw new InitializationException(message, e);
    }
  }
  /**
   * {@inheritDoc}
   */
  public boolean collectStoredDNs(Collection<DN> storedDNs)
    throws UnsupportedOperationException
  {
    for (EntryContainer entryContainer : rootContainer.getEntryContainers()) {
      DN2ID dn2id = entryContainer.getDN2ID();
      Cursor cursor = null;
      try {
        cursor = dn2id.openCursor(null, new CursorConfig());
        DatabaseEntry key = new DatabaseEntry();
        DatabaseEntry data = new DatabaseEntry();
        OperationStatus status;
        for (status = cursor.getFirst(key, data, LockMode.DEFAULT);
             status == OperationStatus.SUCCESS;
             status = cursor.getNext(key, data, LockMode.DEFAULT)) {
          DN entryDN = DN.decode(new ASN1OctetString(key.getData()));
          storedDNs.add(entryDN);
        }
      } catch (Exception e) {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
        return false;
      } finally {
        if (cursor != null) {
          try {
            cursor.close();
          } catch (DatabaseException de) {
            if (debugEnabled()) {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }
          }
        }
      }
    }
    return true;
  }
}
opends/src/server/org/opends/server/backends/task/TaskBackend.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
 *      Portions Copyright 2006-2008 Sun Microsystems, Inc.
 */
package org.opends.server.backends.task;
@@ -31,6 +31,7 @@
import java.io.File;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
@@ -1544,5 +1545,16 @@
  {
    return taskScheduler.getRecurringTask(taskEntryDN);
  }
  /**
   * {@inheritDoc}
   */
  public boolean collectStoredDNs(Collection<DN> storedDNs)
    throws UnsupportedOperationException
  {
    throw new UnsupportedOperationException("Operation not supported.");
  }
}
opends/src/server/org/opends/server/core/EntryCacheConfigManager.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
 *      Portions Copyright 2006-2008 Sun Microsystems, Inc.
 */
package org.opends.server.core;
import org.opends.messages.Message;
@@ -57,6 +57,7 @@
import org.opends.server.config.ConfigConstants;
import org.opends.server.config.ConfigEntry;
import org.opends.server.extensions.DefaultEntryCache;
import org.opends.server.extensions.EntryCachePreloader;
import org.opends.server.monitors.EntryCacheMonitorProvider;
import org.opends.server.types.DN;
@@ -225,6 +226,13 @@
        }
      }
    }
    // If requested preload the entry cache.
    if (rootConfiguration.getDefaultEntryCache().isCachePreload()) {
      // Kick off preload arbiter main thread.
      EntryCachePreloader preloadThread = new EntryCachePreloader();
      preloadThread.start();
    }
  }
opends/src/server/org/opends/server/extensions/ConfigFileHandler.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
 *      Portions Copyright 2006-2008 Sun Microsystems, Inc.
 */
package org.opends.server.extensions;
@@ -36,6 +36,7 @@
import java.io.OutputStream;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
@@ -3615,5 +3616,16 @@
      logError(message);
    }
  }
  /**
   * {@inheritDoc}
   */
  public boolean collectStoredDNs(Collection<DN> storedDNs)
    throws UnsupportedOperationException
  {
    throw new UnsupportedOperationException("Operation not supported.");
  }
}
opends/src/server/org/opends/server/extensions/EntryCachePreloader.java
New file
@@ -0,0 +1,386 @@
/*
 * 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
 *
 *
 *      Portions Copyright 2008 Sun Microsystems, Inc.
 */
package org.opends.server.extensions;
import org.opends.messages.Message;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;
import org.opends.server.api.Backend;
import org.opends.server.api.DirectoryThread;
import org.opends.server.api.ServerShutdownListener;
import org.opends.server.core.DirectoryServer;
import org.opends.server.types.DN;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.DirectoryException;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.LockManager;
import static org.opends.server.util.StaticUtils.*;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.loggers.ErrorLogger.logError;
import static org.opends.messages.ExtensionMessages.*;
/**
 * This class defines a utility that will be used to pre-load the Directory
 * Server entry cache.  Pre-loader is multi-threaded and consist of the
 * following threads:
 *
 * - The Arbiter thread which monitors overall pre-load progress and manages
 *   pre-load worker threads by adding or removing them as deemed necessary.
 *
 * - The Collector thread which collects all entry DNs stored within every
 *   configured and active backend to a shared object workers consume from.
 *
 * - Worker threads which are responsible for monitoring the collector feed
 *   and requesting the actual entries for retrieval and in cache storage.
 *
 * This implementation is entry cache and backend independent and can be
 * used to pre-load from any backend to any entry cache as long as both
 * are capable of initiating and sustaining such pre-load activity.
 *
 * This implementation is fully synchronized and safe to use with the server
 * online which pre-load activities going in parallel with server operations.
 *
 * This implementation is self-adjusting to any system workload and does not
 * require any configuration parameters to optimize for initial system
 * resources availability and/or any subsequent fluctuations.
 */
public class EntryCachePreloader
  extends DirectoryThread
  implements ServerShutdownListener
{
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();
  /**
   * Interrupt flag for the arbiter to terminate worker threads.
   */
  private AtomicBoolean interruptFlag = new AtomicBoolean(false);
  /**
   * Processed entries counter.
   */
  private AtomicLong processedEntries = new AtomicLong(0);
  /**
   * Progress report resolution.
   */
  private static final long progressInterval = 5000;
  /**
   * Default arbiter resolution time.
   */
  public static final long
    PRELOAD_ARBITER_DEFAULT_SLEEP_TIME = 1000;
  /**
   * Effective arbiter resolution time.
   */
  private static long arbiterSleepTime;
  /**
   * Pre-load arbiter thread name.
   */
  private String preloadArbiterThreadName;
  /**
   * Pre-load arbiter thread.
   */
  private Thread preloadArbiterThread;
  /**
   * Worker threads.
   */
  private List<Thread> preloadThreads =
    Collections.synchronizedList(
    new LinkedList<Thread>());
  /**
   * DN Collector thread.
   */
  private EntryCacheDNCollector dnCollector =
    new EntryCacheDNCollector();
  /**
   * This queue is for workers to take from.
   */
  private LinkedBlockingQueue<DN> dnQueue =
      new LinkedBlockingQueue<DN>();
  /**
   * The number of bytes in a megabyte.
   */
  private static final int bytesPerMegabyte = 1024*1024;
  /**
   * Default constructor.
   */
  public EntryCachePreloader() {
    super("Entry Cache Preload Arbiter");
    preloadArbiterThreadName = getName();
    DirectoryServer.registerShutdownListener(this);
    // This should not be exposed as configuration
    // parameter and is only useful for testing.
    arbiterSleepTime = Long.getLong(
      "org.opends.server.entrycache.preload.sleep",
      PRELOAD_ARBITER_DEFAULT_SLEEP_TIME);
  }
  /**
   * The Arbiter thread.
   */
  @Override
  public void run() {
    preloadArbiterThread = Thread.currentThread();
    logError(NOTE_CACHE_PRELOAD_PROGRESS_START.get());
    // Start DN collector thread first.
    dnCollector.start();
    // Kick off a single worker.
    EntryCachePreloadWorker singleWorkerThread =
      new EntryCachePreloadWorker();
    singleWorkerThread.start();
    preloadThreads.add(singleWorkerThread);
    // Progress report timer task.
    Timer timer = new Timer();
    TimerTask progressTask = new TimerTask() {
      // Persistent state restore progress report.
      public void run() {
        if (processedEntries.get() > 0) {
          long freeMemory =
            Runtime.getRuntime().freeMemory() / bytesPerMegabyte;
          Message message = NOTE_CACHE_PRELOAD_PROGRESS_REPORT.get(
            processedEntries.get(), freeMemory);
          logError(message);
        }
      }
    };
    timer.scheduleAtFixedRate(progressTask, progressInterval,
      progressInterval);
    // Cycle to monitor progress and adjust workers.
    long processedEntriesDeltaLow  = 0;
    long processedEntriesDeltaHigh = 0;
    long lastKnownProcessedEntries = 0;
    try {
      while (!dnQueue.isEmpty() || dnCollector.isAlive()) {
        long processedEntriesCycle = processedEntries.get();
        long processedEntriesDelta =
          processedEntriesCycle - lastKnownProcessedEntries;
        lastKnownProcessedEntries = processedEntriesCycle;
        // Spawn another worker if scaling up.
        if (processedEntriesDelta > processedEntriesDeltaHigh) {
          processedEntriesDeltaLow = processedEntriesDeltaHigh;
          processedEntriesDeltaHigh = processedEntriesDelta;
          EntryCachePreloadWorker workerThread =
            new EntryCachePreloadWorker();
          workerThread.start();
          preloadThreads.add(workerThread);
        }
        // Interrupt random worker if scaling down.
        if (processedEntriesDelta < processedEntriesDeltaLow) {
          processedEntriesDeltaHigh = processedEntriesDeltaLow;
          processedEntriesDeltaLow = processedEntriesDelta;
          // Leave at least one worker to progress.
          if (preloadThreads.size() > 1) {
            interruptFlag.set(true);
          }
        }
        Thread.sleep(arbiterSleepTime);
      }
      // Join the collector.
      dnCollector.join();
      // Join all spawned workers.
      for (Thread workerThread : preloadThreads) {
        workerThread.join();
      }
      // Cancel progress report task and report done.
      timer.cancel();
      Message message = NOTE_CACHE_PRELOAD_PROGRESS_DONE.get(
        processedEntries.get());
      logError(message);
    } catch (InterruptedException ex) {
      if (debugEnabled()) {
        TRACER.debugCaught(DebugLogLevel.ERROR, ex);
      }
      // Interrupt the collector.
      dnCollector.interrupt();
      // Interrupt all preload threads.
      for (Thread thread : preloadThreads) {
        thread.interrupt();
      }
      logError(WARN_CACHE_PRELOAD_INTERRUPTED.get());
    } finally {
      // Kill the task in case of exception.
      timer.cancel();
    }
  }
  /**
   * The worker thread.
   */
  private class EntryCachePreloadWorker extends DirectoryThread {
    public EntryCachePreloadWorker() {
      super("Entry Cache Preload Worker");
    }
    @Override
    public void run() {
      while (!dnQueue.isEmpty() || dnCollector.isAlive()) {
        // Check if interrupted.
        if (Thread.interrupted()) {
          break;
        }
        if (interruptFlag.compareAndSet(true, false)) {
          break;
        }
        // Dequeue the next entry DN.
        try {
          DN entryDN = dnQueue.take();
          Lock readLock = null;
          try {
            // Acquire a read lock on the entry.
            readLock = LockManager.lockRead(entryDN);
            if (readLock == null) {
              // It is cheaper to put this DN back on the
              // queue then pick it up and process later.
              dnQueue.add(entryDN);
              continue;
            }
            // Even if getEntry() below fails the entry is
            // still treated as a processed entry anyways.
            processedEntries.getAndIncrement();
            // getEntry() will trigger putEntryIfAbsent() to the
            // cache if given entry is not in the cache already.
            DirectoryServer.getEntry(entryDN);
          } catch (DirectoryException ex) {
            if (debugEnabled()) {
              TRACER.debugCaught(DebugLogLevel.ERROR, ex);
            }
            Message message = ERR_CACHE_PRELOAD_ENTRY_FAILED.get(
              entryDN.toNormalizedString(),
              (ex.getCause() != null ? ex.getCause().getMessage() :
                stackTraceToSingleLineString(ex)));
            logError(message);
          } finally {
            LockManager.unlock(entryDN, readLock);
          }
        } catch (InterruptedException ex) {
          break;
        }
      }
      preloadThreads.remove(Thread.currentThread());
    }
  }
  /**
   * The Collector thread.
   */
  private class EntryCacheDNCollector extends DirectoryThread {
    public EntryCacheDNCollector() {
      super("Entry Cache Preload Collector");
    }
    @Override
    public void run() {
      Map<DN, Backend> baseDNMap =
        DirectoryServer.getPublicNamingContexts();
      Set<Backend> proccessedBackends = new HashSet<Backend>();
      // Collect all DNs from every active public backend.
      for (Backend backend : baseDNMap.values()) {
        // Check if interrupted.
        if (Thread.interrupted()) {
          return;
        }
        if (!proccessedBackends.contains(backend)) {
          proccessedBackends.add(backend);
          try {
            if (!backend.collectStoredDNs(dnQueue)) {
              // DN collection is incomplete, likely
              // due to some backend problem occured.
              // Log an error message and carry on.
              Message message =
                ERR_CACHE_PRELOAD_COLLECTOR_FAILED.get(
                backend.getBackendID());
              logError(message);
            }
          } catch (UnsupportedOperationException ex) {
            // Some backends dont have collectStoredDNs()
            // method implemented, log a warning, skip
            // such backend and continue.
            if (debugEnabled()) {
              TRACER.debugCaught(DebugLogLevel.ERROR, ex);
            }
            Message message =
              WARN_CACHE_PRELOAD_BACKEND_FAILED.get(
              backend.getBackendID());
            logError(message);
          }
        }
      }
    }
  }
  /**
   * {@inheritDoc}
   */
  public String getShutdownListenerName() {
    return preloadArbiterThreadName;
  }
  /**
   * {@inheritDoc}
   */
  public void processServerShutdown(Message reason) {
    if ((preloadArbiterThread != null) &&
         preloadArbiterThread.isAlive()) {
      // Interrupt the arbiter so it can interrupt
      // the collector and all spawned workers.
      preloadArbiterThread.interrupt();
      try {
        // This should be quick although if it
        // gets interrupted it is no big deal.
        preloadArbiterThread.join();
      } catch (InterruptedException ex) {
        if (debugEnabled()) {
          TRACER.debugCaught(DebugLogLevel.ERROR, ex);
        }
      }
    }
    DirectoryServer.deregisterShutdownListener(this);
  }
}
opends/src/server/org/opends/server/replication/server/ReplicationBackend.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 *      Portions Copyright 2007-2008 Sun Microsystems, Inc.
 */
package org.opends.server.replication.server;
import static org.opends.messages.BackendMessages.*;
@@ -40,6 +40,7 @@
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@@ -1325,5 +1326,16 @@
      return new LDIFReader(config);
    }
  }
  /**
   * {@inheritDoc}
   */
  public boolean collectStoredDNs(Collection<DN> storedDNs)
    throws UnsupportedOperationException
  {
    throw new UnsupportedOperationException("Operation not supported.");
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/PreloadEntryCacheTestCase.java
New file
@@ -0,0 +1,235 @@
/*
 * 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
 *
 *
 *      Portions Copyright 2008 Sun Microsystems, Inc.
 */
package org.opends.server.extensions;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import org.opends.server.TestCaseUtils;
import org.opends.server.admin.server.AdminTestCaseUtils;
import org.testng.annotations.BeforeClass;
import org.opends.server.admin.std.meta.*;
import org.opends.server.admin.std.server.EntryCacheCfg;
import org.opends.server.admin.std.server.FileSystemEntryCacheCfg;
import org.opends.server.core.DirectoryServer;
import org.opends.server.types.Entry;
import org.opends.server.util.ServerConstants;
import org.testng.annotations.AfterClass;
import org.testng.annotations.Test;
import static org.testng.Assert.*;
/**
 * The entry cache pre-load test class.
 */
@Test(groups = { "entrycache", "slow" }, sequential=true)
public class PreloadEntryCacheTestCase
       extends ExtensionsTestCase
{
  /**
   * Number of unique dummy test entries.
   */
  protected int NUMTESTENTRIES = 1000;
  /**
   * Dummy test entries.
   */
  protected ArrayList<Entry> testEntriesList;
  /**
   * Entry cache configuration instance.
   */
  protected EntryCacheCfg configuration;
  /**
   * Initialize the entry cache pre-load test.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @BeforeClass()
  @SuppressWarnings("unchecked")
  public void preloadEntryCacheTestInit()
         throws Exception
  {
    // Ensure that the server is running.
    TestCaseUtils.startServer();
    // Initialize a backend with a base entry.
    TestCaseUtils.clearJEBackend(true, "userRoot", "dc=example,dc=com");
    // Configure the entry cache, use FileSystemEntryCache.
    Entry cacheConfigEntry = TestCaseUtils.makeEntry(
      "dn: cn=File System,cn=Entry Caches,cn=config",
      "objectClass: ds-cfg-file-system-entry-cache",
      "objectClass: ds-cfg-entry-cache",
      "objectClass: top",
      "cn: File System",
      "ds-cfg-cache-level: 1",
      "ds-cfg-java-class: " +
      "org.opends.server.extensions.FileSystemEntryCache",
      "ds-cfg-enabled: true");
    configuration = AdminTestCaseUtils.getConfiguration(
      EntryCacheCfgDefn.getInstance(), cacheConfigEntry);
    // Make some dummy test entries.
    testEntriesList = new ArrayList<Entry>(NUMTESTENTRIES);
    for(int i = 0; i < NUMTESTENTRIES; i++ ) {
      Entry testEntry = TestCaseUtils.makeEntry(
        "dn: uid=test" + Integer.toString(i) + ".user" + Integer.toString(i)
         + ",dc=example,dc=com",
        "objectClass: person",
        "objectClass: inetorgperson",
        "objectClass: top",
        "objectClass: organizationalperson",
        "postalAddress: somewhere in Testville" + Integer.toString(i),
        "street: Under Construction Street" + Integer.toString(i),
        "l: Testcounty" + Integer.toString(i),
        "st: Teststate" + Integer.toString(i),
        "telephoneNumber: +878 8378 8378" + Integer.toString(i),
        "mobile: +878 8378 8378" + Integer.toString(i),
        "homePhone: +878 8378 8378" + Integer.toString(i),
        "pager: +878 8378 8378" + Integer.toString(i),
        "mail: test" + Integer.toString(i) + ".user" + Integer.toString(i)
         + "@testdomain.net",
        "postalCode: 8378" + Integer.toString(i),
        "userPassword: testpassword" + Integer.toString(i),
        "description: description for Test" + Integer.toString(i) + "User"
         + Integer.toString(i),
        "cn: Test" + Integer.toString(i) + "User" + Integer.toString(i),
        "sn: User" + Integer.toString(i),
        "givenName: Test" + Integer.toString(i),
        "initials: TST" + Integer.toString(i),
        "employeeNumber: 8378" + Integer.toString(i),
        "uid: test" + Integer.toString(i) + ".user" + Integer.toString(i)
      );
      testEntriesList.add(testEntry);
      TestCaseUtils.addEntry(testEntry);
    }
    // Initialize the cache reflecting on DirectoryServer
    // and EntryCacheConfigManager.
    final Field[] directoryFields =
        DirectoryServer.class.getDeclaredFields();
    for (int i = 0; i < directoryFields.length; ++i) {
      if (directoryFields[i].getName().equals("entryCacheConfigManager")) {
        directoryFields[i].setAccessible(true);
        final Method[] cacheManagerMethods =
          directoryFields[i].getType().getDeclaredMethods();
        for (int j = 0; j < cacheManagerMethods.length; ++j) {
          if (cacheManagerMethods[j].getName().equals(
            "loadAndInstallEntryCache")) {
            cacheManagerMethods[j].setAccessible(true);
            cacheManagerMethods[j].invoke(directoryFields[i].get(
              DirectoryServer.getInstance()),
              configuration.getJavaClass(), configuration);
          }
        }
      }
    }
    // Attempt to force GC to possibly free some memory.
    System.gc();
  }
  /**
   * Tests the entry cache pre-load.
   */
  @Test()
  public void testEntryCachePreload()
         throws Exception
  {
    // Make sure the entry cache is empty.
    assertNull(toVerboseString(),
      "Expected empty cache.  " + "Cache contents:" + ServerConstants.EOL +
      toVerboseString());
    // Start pre-load and wait for it to complete.
    EntryCachePreloader preloadThread = new EntryCachePreloader();
    preloadThread.start();
    preloadThread.join();
    // Check that all test entries are preloaded.
    for(int i = 0; i < NUMTESTENTRIES; i++ ) {
      assertNotNull(DirectoryServer.getEntryCache().getEntry(
        testEntriesList.get(i).getDN()), "Expected to find " +
        testEntriesList.get(i).getDN().toString() +
        " in the cache.  Cache contents:" +
        ServerConstants.EOL + toVerboseString());
    }
  }
  /**
   * Finalize the entry cache pre-load test.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @AfterClass()
  public void preloadEntryCacheTestFini()
         throws Exception
  {
    // Clear backend.
    TestCaseUtils.clearJEBackend(false, "userRoot", "dc=example,dc=com");
    // Sanity in-core restart.
    TestCaseUtils.restartServer();
    // Remove default FS cache JE environment.
    FileSystemEntryCacheCfg config =
      (FileSystemEntryCacheCfg) configuration;
    TestCaseUtils.deleteDirectory(new File(config.getCacheDirectory()));
  }
  /**
   * Reflection of the toVerboseString implementation method.
   */
  protected String toVerboseString()
            throws Exception
  {
    final Method[] cacheMethods =
        DirectoryServer.getEntryCache().getClass().getDeclaredMethods();
    for (int i = 0; i < cacheMethods.length; ++i) {
      if (cacheMethods[i].getName().equals("toVerboseString")) {
        cacheMethods[i].setAccessible(true);
        Object verboseString =
          cacheMethods[i].invoke(DirectoryServer.getEntryCache(),
          (Object[]) null);
        return (String) verboseString;
      }
    }
    return null;
  }
}