From 7eda83737e5c2a09bef758ac2bcd3b7ea8b32ce3 Mon Sep 17 00:00:00 2001
From: boli <boli@localhost>
Date: Wed, 20 Jun 2007 18:27:41 +0000
Subject: [PATCH] This refactoring includes the following changes to the JE backend: - Extracted common interface DatabaseContainer from DN2ID, ID2Entry, etc... classes. - Moved database read and write methods from EntryContainer to DatabaseContainer. - Added index configuration to the XML based admin framework. - Removed redundant configuration objects (Config, IndexConfig). - Added exclusive/shared lock to EntryContainer. All access to an EntryContainer must acquire a lock before using the internal  DatabaseContainers or making configuration changes. - Added the ability to add/remove/modify indexes with the backend online. Server will issue rebuild required warning when adding new indexes  or sub-indexes (equality, substring, presence...). - Added the ability to change the index entry limit for both the backend and each index with the backend online. Server will issue rebuild  required warning if the previous limit has been exceeded. - Added the ability to change entry compression and index substring length setting while the backend is online. - Added a persistent state database to each EntryContainer to persist backend configuration between server restarts. Server will issue  rebuild required warning if a new index is added when the backend is offline. - Added a trusted flag to indexes so that non existent keys will not be interpreted as an empty entry ID set when an index is untrusted. An  index is untrusted when it is added to an non-empty EntryContainer or an inconsistency is detected. Server will issue warning on startup to  rebuild the index.  - Fixed a issue where the LDIF import process stops responding if the temporary import dir is full or unwritable. 

---
 opends/src/server/org/opends/server/backends/jeb/RootContainer.java |  386 ++++++++++++++++++++++++++++++++++--------------------
 1 files changed, 244 insertions(+), 142 deletions(-)

diff --git a/opends/src/server/org/opends/server/backends/jeb/RootContainer.java b/opends/src/server/org/opends/server/backends/jeb/RootContainer.java
index 73a582e..0587ade 100644
--- a/opends/src/server/org/opends/server/backends/jeb/RootContainer.java
+++ b/opends/src/server/org/opends/server/backends/jeb/RootContainer.java
@@ -49,10 +49,17 @@
 import org.opends.server.loggers.debug.DebugTracer;
 import static org.opends.server.messages.MessageHandler.getMessage;
 import static org.opends.server.messages.JebMessages.*;
+import static org.opends.server.messages.ConfigMessages.
+    MSGID_CONFIG_BACKEND_MODE_INVALID;
+import static org.opends.server.messages.ConfigMessages.
+    MSGID_CONFIG_BACKEND_INSANE_MODE;
 import org.opends.server.api.Backend;
 import org.opends.server.admin.std.server.JEBackendCfg;
 import org.opends.server.admin.server.ConfigurationChangeListener;
 import org.opends.server.core.DirectoryServer;
+import org.opends.server.config.ConfigException;
+import static org.opends.server.util.StaticUtils.getFileForPath;
+import org.opends.server.util.StaticUtils;
 
 /**
  * Wrapper class for the JE environment. Root container holds all the entry
@@ -76,7 +83,7 @@
   /**
    * The backend configuration.
    */
-  private Config config;
+  private JEBackendCfg config;
 
   /**
    * The backend to which this entry root container belongs.
@@ -106,29 +113,63 @@
    * @param backend A reference to the JE back end that is creating this
    *                root container.
    */
-  public RootContainer(Config config, Backend backend)
+  public RootContainer(Backend backend, JEBackendCfg config)
   {
     this.env = null;
     this.monitor = null;
     this.entryContainers = new ConcurrentHashMap<DN, EntryContainer>();
     this.backend = backend;
     this.config = config;
+
+    config.addJEChangeListener(this);
   }
 
   /**
-   * Helper method to apply database directory permissions and create a new
-   * JE environment.
+   * Opens the root container using the JE configuration object provided.
    *
-   * @param backendDirectory The environment home directory for JE.
-   * @param backendPermission The file permissions for the environment home
-   *                          directory.
    * @param envConfig The JE environment configuration.
    * @throws DatabaseException If an error occurs when creating the environment.
+   * @throws ConfigException If an configuration error occurs while creating
+   * the enviornment.
    */
-  private void open(File backendDirectory,
-                    FilePermission backendPermission,
-                    EnvironmentConfig envConfig) throws DatabaseException
+  public void open(EnvironmentConfig envConfig)
+      throws DatabaseException, ConfigException
   {
+    // Determine the backend database directory.
+    File backendDirectory = getFileForPath(config.getBackendDirectory());
+
+    //Make sure the directory is valid.
+    if (!backendDirectory.isDirectory())
+    {
+      int msgID = MSGID_JEB_DIRECTORY_INVALID;
+      String message = getMessage(msgID, backendDirectory.getPath());
+      throw new ConfigException(MSGID_JEB_DIRECTORY_INVALID, message);
+    }
+
+    FilePermission backendPermission;
+    try
+    {
+      backendPermission =
+          FilePermission.decodeUNIXMode(config.getBackendMode());
+    }
+    catch(Exception e)
+    {
+      int msgID = MSGID_CONFIG_BACKEND_MODE_INVALID;
+      String message = getMessage(msgID, config.dn().toString());
+      throw new ConfigException(msgID, message);
+    }
+
+    //Make sure the mode will allow the server itself access to
+    //the database
+    if(!backendPermission.isOwnerWritable() ||
+        !backendPermission.isOwnerReadable() ||
+        !backendPermission.isOwnerExecutable())
+    {
+      int msgID = MSGID_CONFIG_BACKEND_INSANE_MODE;
+      String message = getMessage(msgID);
+      throw new ConfigException(msgID, message);
+    }
+
     // Get the backend database backendDirectory permissions and apply
     if(FilePermission.canSetPermissions())
     {
@@ -164,79 +205,25 @@
           "config: %n%s", JEVersion.CURRENT_VERSION.toString(),
                           env.getConfig().toString());
 
-          // Get current size of heap in bytes
-    long heapSize = Runtime.getRuntime().totalMemory();
+      // Get current size of heap in bytes
+      long heapSize = Runtime.getRuntime().totalMemory();
 
-    // Get maximum size of heap in bytes. The heap cannot grow beyond this size.
-    // Any attempt will result in an OutOfMemoryException.
-    long heapMaxSize = Runtime.getRuntime().maxMemory();
+      // Get maximum size of heap in bytes. The heap cannot grow beyond this
+      // size.
+      // Any attempt will result in an OutOfMemoryException.
+      long heapMaxSize = Runtime.getRuntime().maxMemory();
 
-    // Get amount of free memory within the heap in bytes. This size will
+      // Get amount of free memory within the heap in bytes. This size will
       // increase
-    // after garbage collection and decrease as new objects are created.
-    long heapFreeSize = Runtime.getRuntime().freeMemory();
+      // after garbage collection and decrease as new objects are created.
+      long heapFreeSize = Runtime.getRuntime().freeMemory();
 
       TRACER.debugInfo("Current size of heap: %d bytes", heapSize);
       TRACER.debugInfo("Max size of heap: %d bytes", heapMaxSize);
       TRACER.debugInfo("Free memory in heap: %d bytes", heapFreeSize);
     }
-  }
 
-  /**
-   * Opens the root container.
-   *
-   * @throws DatabaseException If an error occurs when opening the container.
-   */
-  public void open() throws DatabaseException
-  {
-    open(config.getBackendDirectory(),
-         config.getBackendPermission(),
-         config.getEnvironmentConfig());
-  }
-
-  /**
-   * Opens the root container using the configuration parameters provided. Any
-   * configuration parameters provided will override the parameters in the
-   * JE configuration object.
-   *
-   * @param backendDirectory The environment home directory for JE.
-   * @param backendPermission he file permissions for the environment home
-   *                          directory.
-   * @param readOnly Open the container in read only mode.
-   * @param allowCreate Allow creating new entries in the container.
-   * @param transactional Use transactions on operations.
-   * @param txnNoSync Use asynchronous transactions.
-   * @param isLocking Create the environment with locking.
-   * @param runCheckPointer Start the checkpointer.
-   * @throws DatabaseException If an error occurs when openinng the container.
-   */
-  public void open(File backendDirectory,
-                   FilePermission backendPermission,
-                   boolean readOnly,
-                   boolean allowCreate,
-                   boolean transactional,
-                   boolean txnNoSync,
-                   boolean isLocking,
-                   boolean runCheckPointer) throws DatabaseException
-  {
-    EnvironmentConfig envConfig;
-    if(config.getEnvironmentConfig() != null)
-    {
-      envConfig = config.getEnvironmentConfig();
-    }
-    else
-    {
-      envConfig = new EnvironmentConfig();
-    }
-    envConfig.setReadOnly(readOnly);
-    envConfig.setAllowCreate(allowCreate);
-    envConfig.setTransactional(transactional);
-    envConfig.setTxnNoSync(txnNoSync);
-    envConfig.setConfigParam("je.env.isLocking", String.valueOf(isLocking));
-    envConfig.setConfigParam("je.env.runCheckpointer",
-                             String.valueOf(runCheckPointer));
-
-    open(backendDirectory, backendPermission, envConfig);
+    openEntryContainers(config.getBackendBaseDN());
   }
 
   /**
@@ -251,28 +238,22 @@
    * @return The opened entry container.
    * @throws DatabaseException If an error occurs while opening the entry
    *                           container.
+   * @throws ConfigException If an configuration error occurs while opening
+   *                         the entry container.
    */
-  public EntryContainer openEntryContainer(DN baseDN) throws DatabaseException
+  public EntryContainer openEntryContainer(DN baseDN)
+      throws DatabaseException, ConfigException
   {
     EntryContainer ec = new EntryContainer(baseDN, backend, config, env, this);
     EntryContainer ec1=this.entryContainers.get(baseDN);
+
     //If an entry container for this baseDN is already open we don't allow
     //another to be opened.
-      if (ec1 != null)
-          throw new DatabaseException("Entry container for baseDN " +
-                  baseDN.toString() + " already is open.");
-    if(env.getConfig().getReadOnly())
-    {
-      ec.openReadOnly();
-    }
-    else if(!env.getConfig().getTransactional())
-    {
-      ec.openNonTransactional(true);
-    }
-    else
-    {
-      ec.open();
-    }
+    if (ec1 != null)
+      throw new DatabaseException("Entry container for baseDN " +
+          baseDN.toString() + " already is open.");
+
+    ec.open();
     this.entryContainers.put(baseDN, ec);
 
     return ec;
@@ -284,8 +265,11 @@
    * @param baseDNs The base DNs of the entry containers to open.
    * @throws DatabaseException If an error occurs while opening the entry
    *                           container.
+   * @throws ConfigException if a configuration error occurs while opening the
+   *                         container.
    */
-  public void openEntryContainers(DN[] baseDNs) throws DatabaseException
+  private void openEntryContainers(Set<DN> baseDNs)
+      throws DatabaseException, ConfigException
   {
     EntryID id;
     EntryID highestID = null;
@@ -311,8 +295,17 @@
    */
   public void closeEntryContainer(DN baseDN) throws DatabaseException
   {
-    getEntryContainer(baseDN).close();
-    entryContainers.remove(baseDN);
+    EntryContainer ec = getEntryContainer(baseDN);
+    ec.exclusiveLock.lock();
+    try
+    {
+      ec.close();
+      entryContainers.remove(baseDN);
+    }
+    finally
+    {
+      ec.exclusiveLock.unlock();
+    }
   }
 
   /**
@@ -324,9 +317,19 @@
    */
   public void removeEntryContainer(DN baseDN) throws DatabaseException
   {
-    getEntryContainer(baseDN).close();
-    getEntryContainer(baseDN).removeContainer();
-    entryContainers.remove(baseDN);
+    EntryContainer ec = getEntryContainer(baseDN);
+    ec.exclusiveLock.lock();
+    try
+    {
+      ec.close();
+      ec.removeContainer();
+      entryContainers.remove(baseDN);
+    }
+    finally
+    {
+      ec.exclusiveLock.unlock();
+    }
+
   }
 
   /**
@@ -340,7 +343,7 @@
     if(monitor == null)
     {
       String monitorName = backend.getBackendID() + " Database Environment";
-      monitor = new DatabaseEnvironmentMonitor(monitorName, env);
+      monitor = new DatabaseEnvironmentMonitor(monitorName, this);
     }
 
     return monitor;
@@ -349,18 +352,27 @@
   /**
    * Preload the database cache. There is no preload if the configured preload
    * time limit is zero.
+   *
+   * @param timeLimit The time limit for the preload process.
    */
-  public void preload()
+  public void preload(long timeLimit)
   {
-    long timeLimit = config.getPreloadTimeLimit();
-
     if (timeLimit > 0)
     {
       // Get a list of all the databases used by the backend.
-      ArrayList<Database> dbList = new ArrayList<Database>();
+      ArrayList<DatabaseContainer> dbList =
+          new ArrayList<DatabaseContainer>();
       for (EntryContainer ec : entryContainers.values())
       {
-        ec.listDatabases(dbList);
+        ec.sharedLock.lock();
+        try
+        {
+          ec.listDatabases(dbList);
+        }
+        finally
+        {
+          ec.sharedLock.unlock();
+        }
       }
 
       // Sort the list in order of priority.
@@ -376,7 +388,7 @@
         PreloadConfig preloadConfig = new PreloadConfig();
         preloadConfig.setLoadLNs(true);
 
-        for (Database db : dbList)
+        for (DatabaseContainer db : dbList)
         {
           // Calculate the remaining time.
           long timeRemaining = timeEnd - System.currentTimeMillis();
@@ -390,7 +402,7 @@
 
           if(debugEnabled())
           {
-            TRACER.debugInfo("file=" + db.getDatabaseName() +
+            TRACER.debugInfo("file=" + db.getName() +
                       " LNs=" + preloadStats.getNLNsLoaded());
           }
 
@@ -487,11 +499,14 @@
   {
     for(DN baseDN : entryContainers.keySet())
     {
-      entryContainers.get(baseDN).close();
-      entryContainers.remove(baseDN);
+      closeEntryContainer(baseDN);
     }
 
-    if (env != null) env.close();
+    if (env != null)
+    {
+      env.close();
+      env = null;
+    }
   }
 
   /**
@@ -554,6 +569,38 @@
   }
 
   /**
+   * Get the environment lock stats of the JE environment used in this
+   * root container.
+   *
+   * @param statsConfig The configuration to use for the EnvironmentStats
+   *                    object.
+   * @return The environment status of the JE environment.
+   * @throws DatabaseException If an error occurs while retriving the stats
+   *                           object.
+   */
+  public LockStats getEnvironmentLockStats(StatsConfig statsConfig)
+      throws DatabaseException
+  {
+    return env.getLockStats(statsConfig);
+  }
+
+  /**
+   * Get the environment transaction stats of the JE environment used
+   * in this root container.
+   *
+   * @param statsConfig The configuration to use for the EnvironmentStats
+   *                    object.
+   * @return The environment status of the JE environment.
+   * @throws DatabaseException If an error occurs while retriving the stats
+   *                           object.
+   */
+  public TransactionStats getEnvironmentTransactionStats(
+      StatsConfig statsConfig) throws DatabaseException
+  {
+    return env.getTransactionStats(statsConfig);
+  }
+
+  /**
    * Get the environment config of the JE environment used in this root
    * container.
    *
@@ -567,6 +614,16 @@
   }
 
   /**
+   * Get the backend configuration used by this root container.
+   *
+   * @return The JE backend configuration used by this root container.
+   */
+  public JEBackendCfg getConfiguration()
+  {
+    return config;
+  }
+
+  /**
    * Get the total number of entries in this root container.
    *
    * @return The number of entries in this root container
@@ -578,7 +635,15 @@
     long entryCount = 0;
     for(EntryContainer ec : this.entryContainers.values())
     {
-      entryCount += ec.getEntryCount();
+      ec.sharedLock.lock();
+      try
+      {
+        entryCount += ec.getEntryCount();
+      }
+      finally
+      {
+        ec.sharedLock.unlock();
+      }
     }
 
     return entryCount;
@@ -620,12 +685,45 @@
    * {@inheritDoc}
    */
   public boolean isConfigurationChangeAcceptable(
-       JEBackendCfg cfg,
-       List<String> unacceptableReasons)
+      JEBackendCfg cfg,
+      List<String> unacceptableReasons)
   {
     boolean acceptable = true;
 
-    // This listener handles only the changes to JE properties.
+    File backendDirectory = getFileForPath(cfg.getBackendDirectory());
+    //Make sure the directory is valid.
+    if (!backendDirectory.isDirectory())
+    {
+      int msgID = MSGID_JEB_DIRECTORY_INVALID;
+      String message = getMessage(msgID, backendDirectory.getPath());
+      unacceptableReasons.add(message);
+      acceptable = false;
+    }
+
+    try
+    {
+      FilePermission newBackendPermission =
+          FilePermission.decodeUNIXMode(cfg.getBackendMode());
+
+      //Make sure the mode will allow the server itself access to
+      //the database
+      if(!newBackendPermission.isOwnerWritable() ||
+          !newBackendPermission.isOwnerReadable() ||
+          !newBackendPermission.isOwnerExecutable())
+      {
+        int msgID = MSGID_CONFIG_BACKEND_INSANE_MODE;
+        String message = getMessage(msgID);
+        unacceptableReasons.add(message);
+        acceptable = false;
+      }
+    }
+    catch(Exception e)
+    {
+      int msgID = MSGID_CONFIG_BACKEND_MODE_INVALID;
+      String message = getMessage(msgID, cfg.dn().toString());
+      unacceptableReasons.add(message);
+      acceptable = false;
+    }
 
     try
     {
@@ -653,50 +751,54 @@
 
     try
     {
-      // Check if any JE non-mutable properties were changed.
-      EnvironmentConfig oldEnvConfig = env.getConfig();
-      EnvironmentConfig newEnvConfig =
-           ConfigurableEnvironment.parseConfigEntry(cfg);
-      Map paramsMap = EnvironmentParams.SUPPORTED_PARAMS;
-      for (Object o : paramsMap.values())
+      if(env != null)
       {
-        ConfigParam param = (ConfigParam) o;
-        if (!param.isMutable())
+        // Check if any JE non-mutable properties were changed.
+        EnvironmentConfig oldEnvConfig = env.getConfig();
+        EnvironmentConfig newEnvConfig =
+            ConfigurableEnvironment.parseConfigEntry(cfg);
+        Map paramsMap = EnvironmentParams.SUPPORTED_PARAMS;
+        for (Object o : paramsMap.values())
         {
-          String oldValue = oldEnvConfig.getConfigParam(param.getName());
-          String newValue = newEnvConfig.getConfigParam(param.getName());
-          if (!oldValue.equalsIgnoreCase(newValue))
+          ConfigParam param = (ConfigParam) o;
+          if (!param.isMutable())
           {
-            adminActionRequired = true;
-            String configAttr = ConfigurableEnvironment.
-                 getAttributeForProperty(param.getName());
-            if (configAttr != null)
+            String oldValue = oldEnvConfig.getConfigParam(param.getName());
+            String newValue = newEnvConfig.getConfigParam(param.getName());
+            if (!oldValue.equalsIgnoreCase(newValue))
             {
-              int msgID = MSGID_JEB_CONFIG_ATTR_REQUIRES_RESTART;
-              messages.add(getMessage(msgID, configAttr));
-            }
-            if(debugEnabled())
-            {
-              TRACER.debugInfo("The change to the following property will " +
-                        "take effect when the backend is restarted: " +
-                        param.getName());
+              adminActionRequired = true;
+              String configAttr = ConfigurableEnvironment.
+                  getAttributeForProperty(param.getName());
+              if (configAttr != null)
+              {
+                int msgID = MSGID_JEB_CONFIG_ATTR_REQUIRES_RESTART;
+                messages.add(getMessage(msgID, configAttr));
+              }
+              if(debugEnabled())
+              {
+                TRACER.debugInfo("The change to the following property will " +
+                    "take effect when the backend is restarted: " +
+                    param.getName());
+              }
             }
           }
         }
-      }
 
-      // This takes care of changes to the JE environment for those
-      // properties that are mutable at runtime.
-      env.setMutableConfig(newEnvConfig);
+        // This takes care of changes to the JE environment for those
+        // properties that are mutable at runtime.
+        env.setMutableConfig(newEnvConfig);
 
-      if (debugEnabled())
-      {
-        TRACER.debugInfo(env.getConfig().toString());
+        if (debugEnabled())
+        {
+          TRACER.debugInfo(env.getConfig().toString());
+        }
       }
+      this.config = cfg;
     }
     catch (Exception e)
     {
-      messages.add(e.getMessage());
+      messages.add(StaticUtils.stackTraceToSingleLineString(e));
       ccr = new ConfigChangeResult(DirectoryServer.getServerErrorResultCode(),
                                    adminActionRequired,
                                    messages);

--
Gitblit v1.10.0