From 005e0af2b1779bdc5c2074c7fad78158c58cce2e Mon Sep 17 00:00:00 2001
From: Yannick Lecaillez <yannick.lecaillez@forgerock.com>
Date: Wed, 03 Jun 2015 08:45:48 +0000
Subject: [PATCH] OPENDJ-2053: ReadOnlyVolumeException when I run verify-index.

---
 opendj-server-legacy/src/main/java/org/opends/server/backends/pdb/PDBStorage.java                         |  133 ++++++++++++++++++++++----
 opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/spi/ReadOnlyStorageException.java |   76 +++++++++++++++
 opendj-server-legacy/src/test/java/org/opends/server/backends/pluggable/PluggableBackendImplTestCase.java |   40 ++++++++
 3 files changed, 229 insertions(+), 20 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 0167f3b..ecf65bb 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 static org.opends.messages.UtilityMessages.*;
 import static org.opends.server.util.StaticUtils.*;
 
+import java.io.Closeable;
 import java.io.File;
 import java.io.FileFilter;
 import java.io.IOException;
@@ -63,6 +64,7 @@
 import org.opends.server.api.DiskSpaceMonitorHandler;
 import org.opends.server.backends.pluggable.spi.Cursor;
 import org.opends.server.backends.pluggable.spi.Importer;
+import org.opends.server.backends.pluggable.spi.ReadOnlyStorageException;
 import org.opends.server.backends.pluggable.spi.ReadOperation;
 import org.opends.server.backends.pluggable.spi.Storage;
 import org.opends.server.backends.pluggable.spi.StorageInUseException;
@@ -96,6 +98,7 @@
 import com.persistit.exception.InUseException;
 import com.persistit.exception.PersistitException;
 import com.persistit.exception.RollbackException;
+import com.persistit.exception.TreeNotFoundException;
 
 /** PersistIt database implementation of the {@link Storage} engine. */
 @SuppressWarnings("javadoc")
@@ -110,7 +113,7 @@
   private static final int BUFFER_SIZE = 16 * 1024;
 
   /** PersistIt implementation of the {@link Cursor} interface. */
-  private final class CursorImpl implements Cursor<ByteString, ByteString>
+  private static final class CursorImpl implements Cursor<ByteString, ByteString>
   {
     private ByteString currentKey;
     private ByteString currentValue;
@@ -125,7 +128,7 @@
     public void close()
     {
       // Release immediately because this exchange did not come from the txn cache
-      db.releaseExchange(exchange);
+      exchange.getPersistitInstance().releaseExchange(exchange);
     }
 
     @Override
@@ -255,7 +258,7 @@
   /** PersistIt implementation of the {@link Importer} interface. */
   private final class ImporterImpl implements Importer
   {
-    private final Map<TreeName, Tree> trees = new HashMap<TreeName, Tree>();
+    private final Map<TreeName, Tree> trees = new HashMap<>();
     private final Queue<Map<TreeName, Exchange>> allExchanges = new ConcurrentLinkedDeque<>();
     private final ThreadLocal<Map<TreeName, Exchange>> exchanges = new ThreadLocal<Map<TreeName, Exchange>>()
     {
@@ -356,10 +359,14 @@
     }
   }
 
+  /** Common interface for internal WriteableTransaction implementations. */
+  private interface StorageImpl extends WriteableTransaction, Closeable {
+  }
+
   /** PersistIt implementation of the {@link WriteableTransaction} interface. */
-  private final class StorageImpl implements WriteableTransaction
+  private final class WriteableStorageImpl implements StorageImpl
   {
-    private final Map<TreeName, Exchange> exchanges = new HashMap<TreeName, Exchange>();
+    private final Map<TreeName, Exchange> exchanges = new HashMap<>();
 
     @Override
     public void put(final TreeName treeName, final ByteSequence key, final ByteSequence value)
@@ -541,7 +548,8 @@
       return exchange;
     }
 
-    private void release()
+    @Override
+    public void close()
     {
       for (final Exchange ex : exchanges.values())
       {
@@ -551,14 +559,107 @@
     }
   }
 
+  /** PersistIt read-only implementation of {@link StorageImpl} interface. */
+  private final class ReadOnlyStorageImpl implements StorageImpl {
+
+    private final WriteableStorageImpl delegate;
+
+    ReadOnlyStorageImpl(WriteableStorageImpl delegate)
+    {
+      this.delegate = delegate;
+    }
+
+    @Override
+    public ByteString read(TreeName treeName, ByteSequence key)
+    {
+      return delegate.read(treeName, key);
+    }
+
+    @Override
+    public Cursor<ByteString, ByteString> openCursor(TreeName treeName)
+    {
+      return delegate.openCursor(treeName);
+    }
+
+    @Override
+    public long getRecordCount(TreeName treeName)
+    {
+      return delegate.getRecordCount(treeName);
+    }
+
+    @Override
+    public void openTree(TreeName treeName)
+    {
+      Exchange ex = null;
+      try
+      {
+        ex = getNewExchange(treeName, false);
+      }
+      catch (final TreeNotFoundException e)
+      {
+        throw new ReadOnlyStorageException();
+      }
+      catch (final PersistitException e)
+      {
+        throw new StorageRuntimeException(e);
+      }
+      finally
+      {
+        db.releaseExchange(ex);
+      }
+    }
+
+    @Override
+    public void close()
+    {
+      delegate.close();
+    }
+
+    @Override
+    public void renameTree(TreeName oldName, TreeName newName)
+    {
+      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();
+    }
+  }
+
   private Exchange getNewExchange(final TreeName treeName, final boolean create) throws PersistitException
   {
     return db.getExchange(volume, mangleTreeName(treeName), create);
   }
 
+  private StorageImpl newStorageImpl() {
+    final WriteableStorageImpl writeableStorage = new WriteableStorageImpl();
+    return accessMode.equals(AccessMode.READ_ONLY) ? new ReadOnlyStorageImpl(writeableStorage) : writeableStorage;
+  }
+
   private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
   private final ServerContext serverContext;
   private final File backendDirectory;
+  private AccessMode accessMode;
   private Persistit db;
   private Volume volume;
   private PDBBackendCfg config;
@@ -586,11 +687,13 @@
 
   private Configuration buildConfiguration(AccessMode accessMode)
   {
+    this.accessMode = accessMode;
+
     final Configuration dbCfg = new Configuration();
     dbCfg.setLogFile(new File(backendDirectory, VOLUME_NAME + ".log").getPath());
     dbCfg.setJournalPath(new File(backendDirectory, JOURNAL_NAME).getPath());
     dbCfg.setVolumeList(asList(new VolumeSpecification(new File(backendDirectory, VOLUME_NAME).getPath(), null,
-        BUFFER_SIZE, 4096, Long.MAX_VALUE / BUFFER_SIZE, 2048, true, false, accessMode.equals(AccessMode.READ_ONLY))));
+        BUFFER_SIZE, 4096, Long.MAX_VALUE / BUFFER_SIZE, 2048, true, false, false)));
     final BufferPoolConfiguration bufferPoolCfg = getBufferPoolCfg(dbCfg);
     bufferPoolCfg.setMaximumCount(Integer.MAX_VALUE);
 
@@ -696,8 +799,7 @@
       txn.begin();
       try
       {
-        final StorageImpl storageImpl = new StorageImpl();
-        try
+        try (final StorageImpl storageImpl = newStorageImpl())
         {
           final T result = operation.run(storageImpl);
           txn.commit();
@@ -711,10 +813,6 @@
           }
           throw e;
         }
-        finally
-        {
-          storageImpl.release();
-        }
       }
       catch (final RollbackException e)
       {
@@ -767,8 +865,7 @@
       txn.begin();
       try
       {
-        final StorageImpl storageImpl = new StorageImpl();
-        try
+        try (final StorageImpl storageImpl = newStorageImpl())
         {
           operation.run(storageImpl);
           txn.commit();
@@ -778,14 +875,10 @@
         {
           if (e.getCause() != null)
           {
-              throw (Exception) e.getCause();
+            throw (Exception) e.getCause();
           }
           throw e;
         }
-        finally
-        {
-          storageImpl.release();
-        }
       }
       catch (final RollbackException e)
       {
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/spi/ReadOnlyStorageException.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/spi/ReadOnlyStorageException.java
new file mode 100644
index 0000000..0eafa28
--- /dev/null
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/spi/ReadOnlyStorageException.java
@@ -0,0 +1,76 @@
+/*
+ * 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 2015 ForgeRock AS
+ */
+package org.opends.server.backends.pluggable.spi;
+
+/**
+ * Thrown when the server or a tool attempts to access the storage while it is read-only.
+ */
+@SuppressWarnings("serial")
+public final class ReadOnlyStorageException extends StorageRuntimeException
+{
+
+  /** Constructor with default error message. */
+  public ReadOnlyStorageException()
+  {
+    this("This storage is read-only.");
+  }
+
+  /**
+   * Constructor with a message and a cause.
+   *
+   * @param message
+   *          the exception message
+   * @param cause
+   *          the cause of the exception
+   */
+  public ReadOnlyStorageException(String message, Throwable cause)
+  {
+    super(message, cause);
+  }
+
+  /**
+   * Constructor with a message.
+   *
+   * @param message
+   *          the exception message
+   */
+  public ReadOnlyStorageException(String message)
+  {
+    super(message);
+  }
+
+  /**
+   * Constructor with a cause.
+   *
+   * @param cause
+   *          the cause of the exception
+   */
+  public ReadOnlyStorageException(Throwable cause)
+  {
+    super(cause);
+  }
+
+}
diff --git a/opendj-server-legacy/src/test/java/org/opends/server/backends/pluggable/PluggableBackendImplTestCase.java b/opendj-server-legacy/src/test/java/org/opends/server/backends/pluggable/PluggableBackendImplTestCase.java
index afdaa9a..8ed3311 100644
--- a/opendj-server-legacy/src/test/java/org/opends/server/backends/pluggable/PluggableBackendImplTestCase.java
+++ b/opendj-server-legacy/src/test/java/org/opends/server/backends/pluggable/PluggableBackendImplTestCase.java
@@ -57,6 +57,12 @@
 import org.opends.server.admin.std.server.PluggableBackendCfg;
 import org.opends.server.api.Backend.BackendOperation;
 import org.opends.server.api.ClientConnection;
+import org.opends.server.backends.pluggable.spi.ReadOnlyStorageException;
+import org.opends.server.backends.pluggable.spi.Storage;
+import org.opends.server.backends.pluggable.spi.Storage.AccessMode;
+import org.opends.server.backends.pluggable.spi.TreeName;
+import org.opends.server.backends.pluggable.spi.WriteOperation;
+import org.opends.server.backends.pluggable.spi.WriteableTransaction;
 import org.opends.server.core.DirectoryServer;
 import org.opends.server.protocols.internal.InternalClientConnection;
 import org.opends.server.protocols.internal.InternalSearchOperation;
@@ -922,4 +928,38 @@
     assertEquals(ldifString.contains(testBaseDN.toString()), true, "Export without rootDN");
     assertEquals(ldifString.contains(searchDN.toString()), true, "Export without rootDN");
   }
+
+  @Test(expectedExceptions=ReadOnlyStorageException.class)
+  public void testReadOnly() throws Exception
+  {
+    C backendCfg = createBackendCfg();
+    when(backendCfg.dn()).thenReturn(testBaseDN);
+    when(backendCfg.getBackendId()).thenReturn(backendTestName);
+    when(backendCfg.getBaseDN()).thenReturn(newSortedSet(testBaseDN));
+    when(backendCfg.listBackendIndexes()).thenReturn(new String[0]);
+    when(backendCfg.listBackendVLVIndexes()).thenReturn(new String[0]);
+
+    final Storage storage = backend.configureStorage(backendCfg, DirectoryServer.getInstance().getServerContext());
+    final RootContainer readOnlyContainer = new RootContainer(backend.getBackendID(), storage, backendCfg);
+
+    // Put backend offline so that export LDIF open read-only container
+    backend.finalizeBackend();
+    try
+    {
+      readOnlyContainer.open(AccessMode.READ_ONLY);
+      readOnlyContainer.getStorage().write(new WriteOperation()
+      {
+        @Override
+        public void run(WriteableTransaction txn) throws Exception
+        {
+          txn.put(new TreeName("dc=test,dc=com", "id2entry"), ByteString.valueOf("key"), ByteString.valueOf("value"));
+        }
+      });
+    }
+    finally
+    {
+      readOnlyContainer.close();
+      backend.openBackend();
+    }
+  }
 }

--
Gitblit v1.10.0