From 74fea9c73aa679eebe68f78d34ae80fa0f263c24 Mon Sep 17 00:00:00 2001
From: Fabio Pistolesi <fabio.pistolesi@forgerock.com>
Date: Fri, 21 Oct 2016 09:41:24 +0000
Subject: [PATCH] OPENDJ-3400 Fake a no-operation storage for offline commands when a suffix has no files

---
 opendj-server-legacy/src/main/java/org/opends/server/backends/pdb/PDBStorage.java |  285 ++++++++++++++++++++++++++++++++++++++++++--------------
 1 files changed, 213 insertions(+), 72 deletions(-)

diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pdb/PDBStorage.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pdb/PDBStorage.java
index f51a980..eaffd4d 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/pdb/PDBStorage.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/pdb/PDBStorage.java
@@ -32,6 +32,7 @@
 import java.nio.file.Paths;
 import java.rmi.RemoteException;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -52,6 +53,7 @@
 import org.forgerock.util.Reject;
 import org.opends.server.api.Backupable;
 import org.opends.server.api.DiskSpaceMonitorHandler;
+import org.opends.server.backends.pluggable.spi.EmptyCursor;
 import org.opends.server.backends.pluggable.spi.AccessMode;
 import org.opends.server.backends.pluggable.spi.Cursor;
 import org.opends.server.backends.pluggable.spi.Importer;
@@ -388,6 +390,8 @@
 
   /** Common interface for internal WriteableTransaction implementations. */
   private interface StorageImpl extends WriteableTransaction, Closeable {
+    <T>T read(ReadOperation<T> operation) throws Exception;
+    void write(WriteOperation operation) throws Exception;
   }
 
   /** PersistIt implementation of the {@link WriteableTransaction} interface. */
@@ -589,6 +593,65 @@
       }
       exchanges.clear();
     }
+
+    @Override
+    public <T> T read(ReadOperation<T> operation) throws Exception
+    {
+      final Transaction txn = db.getTransaction();
+      for (;;)
+      {
+        txn.begin();
+        try
+        {
+          final T result = operation.run(this);
+          txn.commit(commitPolicy);
+          return result;
+        }
+        catch (final RollbackException e)
+        {
+          // retry
+        }
+        catch (final Exception e)
+        {
+          txn.rollback();
+          throw e;
+        }
+        finally
+        {
+          txn.end();
+        }
+      }
+    }
+
+    @Override
+    public void write(WriteOperation operation) throws Exception
+    {
+      final Transaction txn = db.getTransaction();
+      for (;;)
+      {
+        txn.begin();
+        try
+        {
+          operation.run(this);
+          txn.commit(commitPolicy);
+          return;
+        }
+        catch (final RollbackException e)
+        {
+          // retry after random sleep (reduces transactions collision. Drawback: increased latency)
+          Thread.sleep((long) (Math.random() * MAX_SLEEP_ON_RETRY_MS));
+        }
+        catch (final Exception e)
+        {
+          txn.rollback();
+          throw e;
+        }
+        finally
+        {
+          txn.end();
+        }
+      }
+    }
   }
 
   /** PersistIt read-only implementation of {@link StorageImpl} interface. */
@@ -673,6 +736,91 @@
     {
       throw new ReadOnlyStorageException();
     }
+
+    @Override
+    public <T> T read(ReadOperation<T> operation) throws Exception
+    {
+      return delegate.read(operation);
+    }
+
+    @Override
+    public void write(WriteOperation operation) throws Exception
+    {
+      operation.run(this);
+    }
+  }
+
+  /** No operation storage faking database files are present and empty. */
+  private final class ReadOnlyEmptyStorageImpl implements StorageImpl
+  {
+    @Override
+    public void close() throws IOException
+    {
+      // Nothing to do
+    }
+
+    @Override
+    public void openTree(TreeName name, boolean createOnDemand)
+    {
+      if (createOnDemand)
+      {
+        throw new ReadOnlyStorageException();
+      }
+    }
+
+    @Override
+    public void deleteTree(TreeName name)
+    {
+      throw new ReadOnlyStorageException();
+    }
+
+    @Override
+    public void put(TreeName treeName, ByteSequence key, ByteSequence value)
+    {
+      throw new ReadOnlyStorageException();
+    }
+
+    @Override
+    public boolean update(TreeName treeName, ByteSequence key, UpdateFunction f)
+    {
+      throw new ReadOnlyStorageException();
+    }
+
+    @Override
+    public boolean delete(TreeName treeName, ByteSequence key)
+    {
+      throw new ReadOnlyStorageException();
+    }
+
+    @Override
+    public ByteString read(TreeName treeName, ByteSequence key)
+    {
+      return null;
+    }
+
+    @Override
+    public Cursor<ByteString, ByteString> openCursor(TreeName treeName)
+    {
+      return new EmptyCursor<>();
+    }
+
+    @Override
+    public long getRecordCount(TreeName treeName)
+    {
+      return 0;
+    }
+
+    @Override
+    public <T> T read(ReadOperation<T> operation) throws Exception
+    {
+      return operation.run(this);
+    }
+
+    @Override
+    public void write(WriteOperation operation) throws Exception
+    {
+      operation.run(this);
+    }
   }
 
   Exchange getNewExchange(final TreeName treeName, final boolean create) throws PersistitException
@@ -693,6 +841,12 @@
   }
 
   private StorageImpl newStorageImpl() {
+    // If no persistent files have been created yet and we're opening READ-ONLY
+    // there is no volume and no db to use, since open was not called. Fake it.
+    if (db == null)
+    {
+      return new ReadOnlyEmptyStorageImpl();
+    }
     final WriteableStorageImpl writeableStorage = new WriteableStorageImpl();
     return accessMode.isWriteable() ? writeableStorage : new ReadOnlyStorageImpl(writeableStorage);
   }
@@ -703,7 +857,9 @@
   private final File backendDirectory;
   private CommitPolicy commitPolicy;
   private AccessMode accessMode;
+  /** It is NULL when opening the storage READ-ONLY and no files have been created yet. */
   private Persistit db;
+  /** It is NULL when opening the storage READ-ONLY and no files have been created yet, same as {@link #db}. */
   private Volume volume;
   private PDBBackendCfg config;
   private DiskSpaceMonitor diskMonitor;
@@ -785,16 +941,22 @@
         throw new IllegalStateException(e);
       }
     }
-    if (config.getDBCacheSize() > 0)
+    if (memQuota != null)
     {
-      memQuota.releaseMemory(config.getDBCacheSize());
-    }
-    else
-    {
-      memQuota.releaseMemory(memQuota.memPercentToBytes(config.getDBCachePercent()));
+      if (config.getDBCacheSize() > 0)
+      {
+        memQuota.releaseMemory(config.getDBCacheSize());
+      }
+      else
+      {
+        memQuota.releaseMemory(memQuota.memPercentToBytes(config.getDBCachePercent()));
+      }
     }
     config.removePDBChangeListener(this);
-    diskMonitor.deregisterMonitoredDirectory(getDirectory(), this);
+    if (diskMonitor != null)
+    {
+      diskMonitor.deregisterMonitoredDirectory(getDirectory(), this);
+    }
   }
 
   private static BufferPoolConfiguration getBufferPoolCfg(Configuration dbCfg)
@@ -806,9 +968,32 @@
   public void open(AccessMode accessMode) throws ConfigException, StorageRuntimeException
   {
     Reject.ifNull(accessMode, "accessMode must not be null");
+    if (isBackendIncomplete(accessMode))
+    {
+      // Do not open volume on disk
+      return;
+    }
     open0(buildConfiguration(accessMode));
   }
 
+  private boolean isBackendIncomplete(AccessMode accessMode)
+  {
+    return !accessMode.isWriteable() && (!backendDirectory.exists() || backendDirectoryIncomplete());
+  }
+
+  // TODO: it belongs to disk-based Storage Interface.
+  private boolean backendDirectoryIncomplete()
+  {
+    try
+    {
+      return !getFilesToBackup().hasNext();
+    }
+    catch (DirectoryException ignored)
+    {
+      return true;
+    }
+  }
+
   private void open0(final Configuration dbCfg) throws ConfigException
   {
     setupStorageFiles(backendDirectory, config.getDBDirectoryPermissions(), config.dn());
@@ -843,42 +1028,18 @@
   @Override
   public <T> T read(final ReadOperation<T> operation) throws Exception
   {
-    // This check may be unnecessary for PDB, but it will help us detect bad business logic
-    // in the pluggable backend that would cause problems for JE.
-    final Transaction txn = db.getTransaction();
-    for (;;)
+    try (final StorageImpl storageImpl = newStorageImpl())
     {
-      txn.begin();
-      try
+      final T result = storageImpl.read(operation);
+      return result;
+    }
+    catch (final StorageRuntimeException e)
+    {
+      if (e.getCause() != null)
       {
-        try (final StorageImpl storageImpl = newStorageImpl())
-        {
-          final T result = operation.run(storageImpl);
-          txn.commit(commitPolicy);
-          return result;
-        }
-        catch (final StorageRuntimeException e)
-        {
-          if (e.getCause() != null)
-          {
-              throw (Exception) e.getCause();
-          }
-          throw e;
-        }
+        throw (Exception) e.getCause();
       }
-      catch (final RollbackException e)
-      {
-        // retry
-      }
-      catch (final Exception e)
-      {
-        txn.rollback();
-        throw e;
-      }
-      finally
-      {
-        txn.end();
-      }
+      throw e;
     }
   }
 
@@ -892,41 +1053,17 @@
   @Override
   public void write(final WriteOperation operation) throws Exception
   {
-    final Transaction txn = db.getTransaction();
-    for (;;)
+    try (final StorageImpl storageImpl = newStorageImpl())
     {
-      txn.begin();
-      try
+      storageImpl.write(operation);
+    }
+    catch (final StorageRuntimeException e)
+    {
+      if (e.getCause() != null)
       {
-        try (final StorageImpl storageImpl = newStorageImpl())
-        {
-          operation.run(storageImpl);
-          txn.commit(commitPolicy);
-          return;
-        }
-        catch (final StorageRuntimeException e)
-        {
-          if (e.getCause() != null)
-          {
-            throw (Exception) e.getCause();
-          }
-          throw e;
-        }
+        throw (Exception) e.getCause();
       }
-      catch (final RollbackException e)
-      {
-        // retry after random sleep (reduces transactions collision. Drawback: increased latency)
-        Thread.sleep((long) (Math.random() * MAX_SLEEP_ON_RETRY_MS));
-      }
-      catch (final Exception e)
-      {
-        txn.rollback();
-        throw e;
-      }
-      finally
-      {
-        txn.end();
-      }
+      throw e;
     }
   }
 
@@ -1101,6 +1238,10 @@
   @Override
   public Set<TreeName> listTrees()
   {
+    if (volume == null)
+    {
+      return Collections.<TreeName>emptySet();
+    }
     try
     {
       String[] treeNames = volume.getTreeNames();

--
Gitblit v1.10.0