From 7bc10f9671b187cecc2be8f5c52f7b4a7272f9f0 Mon Sep 17 00:00:00 2001
From: vharseko <vharseko@3a-systems.ru>
Date: Fri, 22 Sep 2023 07:30:49 +0000
Subject: [PATCH] Allow store LDAP catalog data in CASSANDRA noSQL cluster --backendType cas (ldapv3 to cassandra) (#302)

---
 opendj-server-legacy/src/test/java/org/opends/server/TestCaseUtils.java                                          |   16 
 opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/OnDiskMergeImporter.java                 |    1 
 opendj-server-legacy/src/main/java/org/opends/server/backends/cassandra/Storage.java                             |  521 ++++++++++++++++++++++++++++++++++
 opendj-maven-plugin/src/main/resources/config/stylesheets/abbreviations.xsl                                      |    3 
 opendj-server-legacy/resource/schema/02-config.ldif                                                              |    7 
 opendj-server-legacy/src/test/java/org/opends/server/backends/cassandra/EncryptedTestCase.java                   |   62 ++++
 opendj-server-legacy/src/main/java/org/opends/server/backends/cassandra/Backend.java                             |   16 +
 opendj-server-legacy/src/test/java/org/opends/server/backends/pluggable/PluggableBackendImplTestCase.java        |   12 
 opendj-server-legacy/src/main/java/org/opends/server/tools/BackendTypeHelper.java                                |    6 
 opendj-server-legacy/src/test/java/org/opends/server/backends/cassandra/TestCase.java                            |   56 +++
 pom.xml                                                                                                          |    2 
 opendj-server-legacy/src/test/java/org/opends/server/TestListener.java                                           |    2 
 opendj-server-legacy/pom.xml                                                                                     |   14 
 opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/DefaultTCPNIOTransport.java                            |    1 
 opendj-server-legacy/src/main/java/org/opends/quicksetup/installer/Installer.java                                |   12 
 .github/workflows/build.yml                                                                                      |   36 +
 opendj-maven-plugin/src/main/resources/config/xml/org/forgerock/opendj/server/config/CASBackendConfiguration.xml |   79 +++++
 opendj-server-legacy/src/main/java/org/opends/server/backends/cassandra/package-info.java                        |   18 +
 18 files changed, 841 insertions(+), 23 deletions(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index d0f7fef..b70df88 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -41,31 +41,49 @@
          path: ~/.m2/repository
          key: ${{ runner.os }}-m2-repository-${{ hashFiles('**/pom.xml') }}
          restore-keys: ${{ runner.os }}-m2-repository
-    - name: Set Integration Test Environment
-      id: maven-profile-flag
+    - name: Run docker cassandra
+      if: runner.os == 'Linux'
+      run:   |
+        docker run --rm -it -d -p 9042:9042 --name cassandra cassandra
+        timeout 5m bash -c 'until docker logs cassandra | grep -q "Created default superuser role"; do sleep 5; done'
+    - name: Set Integration Test Environment 
+      id: failsafe
       if: runner.os != 'Windows'
-      run:   | 
-        echo "MAVEN_PROFILE_FLAG=-P precommit" >> $GITHUB_OUTPUT 
+      run:   |
+        echo "MAVEN_PROFILE_FLAG=-P precommit" >> $GITHUB_OUTPUT
     - name: Build with Maven
+      timeout-minutes: 180
       env:
         MAVEN_OPTS: -Dhttps.protocols=TLSv1.2 -Dmaven.wagon.httpconnectionManager.ttlSeconds=120 -Dmaven.wagon.http.retryHandler.requestSentEnabled=true -Dmaven.wagon.http.retryHandler.count=10
-      run: mvn --batch-mode --errors --update-snapshots verify --file pom.xml ${{ steps.maven-profile-flag.outputs.MAVEN_PROFILE_FLAG }}
+      run: mvn --batch-mode --errors --update-snapshots verify --file pom.xml ${{ steps.failsafe.outputs.MAVEN_PROFILE_FLAG }}
     - name: Test on Unix
       if: runner.os != 'Windows'
       run:   |
         export OPENDJ_JAVA_ARGS="-server -Xmx1g"
-        opendj-server-legacy/target/package/opendj/setup -h localhost -p 1389 --ldapsPort 1636 --adminConnectorPort 4444 --enableStartTLS --generateSelfSignedCertificate --rootUserDN "cn=Directory Manager" --rootUserPassword password --baseDN dc=example,dc=com --sampleData 50000 --cli --acceptLicense --no-prompt
+        opendj-server-legacy/target/package/opendj/setup -h localhost -p 1389 --ldapsPort 1636 --adminConnectorPort 4444 --enableStartTLS --generateSelfSignedCertificate --rootUserDN "cn=Directory Manager" --rootUserPassword password --baseDN dc=example,dc=com --sampleData 5000 --cli --acceptLicense --no-prompt
         opendj-server-legacy/target/package/opendj/bin/status --bindDN "cn=Directory Manager" --bindPassword password
         opendj-server-legacy/target/package/opendj/bin/ldapsearch --hostname localhost --port 1636 --bindDN "cn=Directory Manager" --bindPassword password --useSsl --trustAll --baseDN "dc=example,dc=com" --searchScope base "(objectClass=*)" 1.1
-        opendj-server-legacy/target/package/opendj/bin/ldapsearch --hostname localhost --port 1636 --bindDN "cn=Directory Manager" --bindPassword password --useSsl --trustAll --baseDN "ou=people,dc=example,dc=com" --searchScope sub "(uid=user.*)" dn | grep ^dn: | wc -l | grep -q 50000
+        opendj-server-legacy/target/package/opendj/bin/ldapsearch --hostname localhost --port 1636 --bindDN "cn=Directory Manager" --bindPassword password --useSsl --trustAll --baseDN "ou=people,dc=example,dc=com" --searchScope sub "(uid=user.*)" dn | grep ^dn: | wc -l | grep -q 5000
+        opendj-server-legacy/target/package/opendj/bin/stop-ds
+    - name: Test LDAP in Cassandra 
+      if: runner.os == 'Linux'
+      run:   |
+        rm -rf opendj-server-legacy/target/package/opendj/config opendj-server-legacy/target/package/opendj/db opendj-server-legacy/target/package/opendj/changelogDb opendj-server-legacy/target/package/opendj/logs
+        export OPENDJ_JAVA_ARGS="-server -Xmx1g -Ddatastax-java-driver.basic.contact-points.0=localhost:9042 -Ddatastax-java-driver.basic.load-balancing-policy.local-datacenter=datacenter1"
+        opendj-server-legacy/target/package/opendj/setup --backendType cas -h localhost -p 1389 --ldapsPort 1636 --adminConnectorPort 4444 --enableStartTLS --generateSelfSignedCertificate --rootUserDN "cn=Directory Manager" --rootUserPassword password --baseDN dc=example,dc=com --sampleData 5000 --cli --acceptLicense --no-prompt
+        opendj-server-legacy/target/package/opendj/bin/status --bindDN "cn=Directory Manager" --bindPassword password
+        opendj-server-legacy/target/package/opendj/bin/ldapsearch --hostname localhost --port 1636 --bindDN "cn=Directory Manager" --bindPassword password --useSsl --trustAll --baseDN "dc=example,dc=com" --searchScope base "(objectClass=*)" 1.1
+        opendj-server-legacy/target/package/opendj/bin/ldapsearch --hostname localhost --port 1636 --bindDN "cn=Directory Manager" --bindPassword password --useSsl --trustAll --baseDN "ou=people,dc=example,dc=com" --searchScope sub "(uid=user.*)" dn | grep ^dn: | wc -l | grep -q 5000
+        opendj-server-legacy/target/package/opendj/bin/stop-ds
     - name: Test on Windows
       if: runner.os == 'Windows'
       run:   |
         set OPENDJ_JAVA_ARGS="-server -Xmx1g"
-        opendj-server-legacy\target\package\opendj\setup.bat -h localhost -p 1389 --ldapsPort 1636 --adminConnectorPort 4444 --enableStartTLS --generateSelfSignedCertificate --rootUserDN "cn=Directory Manager" --rootUserPassword password --baseDN dc=example,dc=com --sampleData 50000 --cli --acceptLicense --no-prompt
+        opendj-server-legacy\target\package\opendj\setup.bat -h localhost -p 1389 --ldapsPort 1636 --adminConnectorPort 4444 --enableStartTLS --generateSelfSignedCertificate --rootUserDN "cn=Directory Manager" --rootUserPassword password --baseDN dc=example,dc=com --sampleData 5000 --cli --acceptLicense --no-prompt
         opendj-server-legacy\target\package\opendj\bat\status.bat --bindDN "cn=Directory Manager" --bindPassword password
         opendj-server-legacy\target\package\opendj\bat\ldapsearch.bat --hostname localhost --port 1636 --bindDN "cn=Directory Manager" --bindPassword password --useSsl --trustAll --baseDN "dc=example,dc=com" --searchScope base "(objectClass=*)" 1.1
-        opendj-server-legacy\target\package\opendj\bat\ldapsearch.bat --hostname localhost --port 1636 --bindDN "cn=Directory Manager" --bindPassword password --useSsl --trustAll --baseDN "dc=example,dc=com" --searchScope sub "(uid=user.*)" dn | find /c '"dn:"' | findstr "50000"
+        opendj-server-legacy\target\package\opendj\bat\ldapsearch.bat --hostname localhost --port 1636 --bindDN "cn=Directory Manager" --bindPassword password --useSsl --trustAll --baseDN "dc=example,dc=com" --searchScope sub "(uid=user.*)" dn | find /c '"dn:"' | findstr "5000"
+        opendj-server-legacy\target\package\opendj\bat\stop-ds.bat
     - name: Upload artifacts OpenDJ Server
       uses: actions/upload-artifact@v3
       with:
diff --git a/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/DefaultTCPNIOTransport.java b/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/DefaultTCPNIOTransport.java
index 39c0313..9e2c56f 100644
--- a/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/DefaultTCPNIOTransport.java
+++ b/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/DefaultTCPNIOTransport.java
@@ -136,7 +136,6 @@
             builder.setReuseAddress(Boolean.parseBoolean(reuseAddressStr));
         }
         builder.setMemoryManager(new PooledMemoryManager(true));
-        
 
         final TCPNIOTransport transport = builder.build();
 
diff --git a/opendj-maven-plugin/src/main/resources/config/stylesheets/abbreviations.xsl b/opendj-maven-plugin/src/main/resources/config/stylesheets/abbreviations.xsl
index 58d33cc..563e7b7 100644
--- a/opendj-maven-plugin/src/main/resources/config/stylesheets/abbreviations.xsl
+++ b/opendj-maven-plugin/src/main/resources/config/stylesheets/abbreviations.xsl
@@ -13,6 +13,7 @@
 
   Copyright 2008-2009 Sun Microsystems, Inc.
   Portions copyright 2011-2016 ForgeRock AS.
+  Portions copyright 2023 3A Systems LLC
   ! -->
 <xsl:stylesheet version="1.0"
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
@@ -43,7 +44,7 @@
               or $value = 'des' or $value = 'aes' or $value = 'rc4'
               or $value = 'db' or $value = 'snmp' or $value = 'qos'
               or $value = 'ecl' or $value = 'ttl' or $value = 'jpeg'
-              or $value = 'pbkdf2' or $value = 'pkcs5s2' or $value = 'pdb'
+              or $value = 'pbkdf2' or $value = 'pkcs5s2' or $value = 'pdb' or $value = 'cas'
              "/>
   </xsl:template>
 </xsl:stylesheet>
diff --git a/opendj-maven-plugin/src/main/resources/config/xml/org/forgerock/opendj/server/config/CASBackendConfiguration.xml b/opendj-maven-plugin/src/main/resources/config/xml/org/forgerock/opendj/server/config/CASBackendConfiguration.xml
new file mode 100644
index 0000000..c1d62c8
--- /dev/null
+++ b/opendj-maven-plugin/src/main/resources/config/xml/org/forgerock/opendj/server/config/CASBackendConfiguration.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  The contents of this file are subject to the terms of the Common Development and
+  Distribution License (the License). You may not use this file except in compliance with the
+  License.
+
+  You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+  specific language governing permission and limitations under the License.
+
+  When distributing Covered Software, include this CDDL Header Notice in each file and include
+  the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+  Header, with the fields enclosed by brackets [] replaced by your own identifying
+  information: "Portions Copyright [year] [name of copyright owner]".
+
+  Copyright 2023- 3A Systems LLC
+  ! -->
+<adm:managed-object name="cas-backend" plural-name="cas-backends"
+  package="org.forgerock.opendj.server.config"
+  extends="pluggable-backend" xmlns:adm="http://opendj.forgerock.org/admin"
+  xmlns:ldap="http://opendj.forgerock.org/admin-ldap"
+  xmlns:cli="http://opendj.forgerock.org/admin-cli">
+  <adm:synopsis>
+    A <adm:user-friendly-name/> stores application data in a Apache Cassandra cluster.
+  </adm:synopsis>
+  <adm:description>
+    It is the traditional "directory server" backend and is similar to
+    the backends provided by the Sun Java System Directory Server. The
+    <adm:user-friendly-name />
+    stores the entries in an encoded form and also provides indexes that
+    can be used to quickly locate target entries based on different
+    kinds of criteria.
+  </adm:description>
+  <adm:profile name="ldap">
+    <ldap:object-class>
+      <ldap:name>ds-cfg-cas-backend</ldap:name>
+      <ldap:superior>ds-cfg-pluggable-backend</ldap:superior>
+    </ldap:object-class>
+  </adm:profile>
+  <adm:property-override name="java-class" advanced="true">
+    <adm:default-behavior>
+      <adm:defined>
+        <adm:value>
+          org.opends.server.backends.cassandra.Backend
+        </adm:value>
+      </adm:defined>
+    </adm:default-behavior>
+  </adm:property-override>
+  
+  <adm:property name="db-directory" mandatory="true">
+    <adm:TODO>Default this to the db/backend-id</adm:TODO>
+    <adm:synopsis>
+      Specifies the keyspace name
+    </adm:synopsis>
+    <adm:description>
+      The path may be either an absolute path or a path relative to the
+      directory containing the base of the <adm:product-name /> directory server
+      installation. The path may be any valid directory path in which
+      the server has appropriate permissions to read and write files and
+      has sufficient space to hold the database contents.
+    </adm:description>
+    <adm:requires-admin-action>
+      <adm:component-restart />
+    </adm:requires-admin-action>
+    <adm:default-behavior>
+      <adm:defined>
+        <adm:value>ldap_opendj</adm:value>
+      </adm:defined>
+    </adm:default-behavior>
+    <adm:syntax>
+      <adm:string />
+    </adm:syntax>
+    <adm:profile name="ldap">
+      <ldap:attribute>
+        <ldap:name>ds-cfg-db-directory</ldap:name>
+      </ldap:attribute>
+    </adm:profile>
+  </adm:property>
+
+</adm:managed-object>
diff --git a/opendj-server-legacy/pom.xml b/opendj-server-legacy/pom.xml
index 9e9de76..0caee10 100644
--- a/opendj-server-legacy/pom.xml
+++ b/opendj-server-legacy/pom.xml
@@ -245,6 +245,18 @@
       <artifactId>juel-api</artifactId>
       <version>${juel.version}</version>
     </dependency>
+    
+    <dependency>
+	    <groupId>com.datastax.oss</groupId>
+	    <artifactId>java-driver-core</artifactId>
+	    <version>4.17.0</version>
+	    <exclusions>
+	    	<exclusion>
+	    		<groupId>org.reactivestreams</groupId>
+	    		<artifactId>reactive-streams</artifactId>
+	    	</exclusion>
+	    </exclusions>
+    </dependency>
   </dependencies>
 
   <build><finalName>${project.groupId}.${project.artifactId}</finalName>
@@ -1216,7 +1228,7 @@
                     <org.opends.test.pauseOnFailure>false</org.opends.test.pauseOnFailure>
                     <org.opends.test.copyClassesToTestPackage>false</org.opends.test.copyClassesToTestPackage>
                   </systemPropertyVariables>
-                  <argLine> -Xmx2g @{argLine}</argLine>
+                  <argLine> -Xmx1g @{argLine}</argLine>
 	          	  <reuseForks>false</reuseForks>
 	          	  <forkCount>1</forkCount>
 	          	  <parallel>none</parallel>
diff --git a/opendj-server-legacy/resource/schema/02-config.ldif b/opendj-server-legacy/resource/schema/02-config.ldif
index 5f69777..71bb719 100644
--- a/opendj-server-legacy/resource/schema/02-config.ldif
+++ b/opendj-server-legacy/resource/schema/02-config.ldif
@@ -15,6 +15,7 @@
 # Portions Copyright 2011 profiq, s.r.o.
 # Portions Copyright 2012 Manuel Gaupp
 # Portions copyright 2015 Edan Idzerda
+# Portions copyright 2023 3A Systems LLC
 
 # This file contains the attribute type and objectclass definitions for use
 # with the Directory Server configuration.
@@ -6006,6 +6007,12 @@
         ds-cfg-disk-low-threshold $
         ds-cfg-je-property )
   X-ORIGIN 'OpenDJ Directory Server' )
+objectClasses: ( 1.3.6.1.4.1.60142.2.1.2.1
+  NAME 'ds-cfg-cas-backend'
+  SUP ds-cfg-pluggable-backend
+  STRUCTURAL
+  MUST ds-cfg-db-directory
+  X-ORIGIN 'OpenDJ Directory Server' )
 objectClasses: ( 1.3.6.1.4.1.36733.2.1.2.27
   NAME 'ds-task-reset-change-number'
   SUP ds-task
diff --git a/opendj-server-legacy/src/main/java/org/opends/quicksetup/installer/Installer.java b/opendj-server-legacy/src/main/java/org/opends/quicksetup/installer/Installer.java
index b776bd6..b70df04 100644
--- a/opendj-server-legacy/src/main/java/org/opends/quicksetup/installer/Installer.java
+++ b/opendj-server-legacy/src/main/java/org/opends/quicksetup/installer/Installer.java
@@ -41,6 +41,9 @@
 import java.io.FileWriter;
 import java.io.IOException;
 import java.io.PrintStream;
+import java.io.UnsupportedEncodingException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
 import java.security.KeyStore;
 import java.security.KeyStoreException;
 import java.security.NoSuchAlgorithmException;
@@ -616,6 +619,15 @@
     {
       final String tempLogFilePath = tempLogFile.getPath();
       notifyListeners(getFormattedProgress(INFO_GENERAL_PROVIDE_LOG_IN_ERROR.get(tempLogFilePath)));
+    //write log
+      try {
+    	notifyListeners(getLineBreak());
+		notifyListeners(LocalizableMessage.valueOf(new String(Files.readAllBytes(Paths.get(tempLogFilePath)),"UTF-8")));
+      } catch (UnsupportedEncodingException e) {
+		e.printStackTrace();
+      } catch (IOException e) {
+		e.printStackTrace();
+      }
       notifyListeners(getLineBreak());
     }
   }
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/cassandra/Backend.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/cassandra/Backend.java
new file mode 100644
index 0000000..42bcc8b
--- /dev/null
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/cassandra/Backend.java
@@ -0,0 +1,16 @@
+package org.opends.server.backends.cassandra;
+
+import org.forgerock.opendj.config.server.ConfigException;
+import org.forgerock.opendj.server.config.server.CASBackendCfg;
+import org.opends.server.backends.pluggable.BackendImpl;
+import org.opends.server.core.ServerContext;
+
+public class Backend extends BackendImpl<CASBackendCfg>{
+	
+	  @Override
+	  protected Storage configureStorage(CASBackendCfg cfg, ServerContext serverContext) throws ConfigException
+	  {
+	    return new Storage(cfg, serverContext);
+	  }
+
+}
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/cassandra/Storage.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/cassandra/Storage.java
new file mode 100644
index 0000000..b807234
--- /dev/null
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/cassandra/Storage.java
@@ -0,0 +1,521 @@
+/*
+ * The contents of this file are subject to the terms of the Common Development and
+ * Distribution License (the License). You may not use this file except in compliance with the
+ * License.
+ *
+ * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ * specific language governing permission and limitations under the License.
+ *
+ * When distributing Covered Software, include this CDDL Header Notice in each file and include
+ * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ * Header, with the fields enclosed by brackets [] replaced by your own identifying
+ * information: "Portions Copyright [year] [name of copyright owner]".
+ *
+ * Copyright 2023 3A Systems, LLC.
+ */
+package org.opends.server.backends.cassandra;
+
+
+import static org.opends.server.backends.pluggable.spi.StorageUtils.addErrorMessage;
+import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
+
+import java.nio.ByteBuffer;
+import java.time.Duration;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+import java.util.Set;
+
+import org.forgerock.i18n.LocalizableMessage;
+import org.forgerock.i18n.slf4j.LocalizedLogger;
+import org.forgerock.opendj.config.server.ConfigChangeResult;
+import org.forgerock.opendj.config.server.ConfigException;
+import org.forgerock.opendj.config.server.ConfigurationChangeListener;
+import org.forgerock.opendj.ldap.ByteSequence;
+import org.forgerock.opendj.ldap.ByteString;
+import org.forgerock.opendj.server.config.server.CASBackendCfg;
+import org.opends.server.backends.pluggable.spi.AccessMode;
+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.ReadableTransaction;
+import org.opends.server.backends.pluggable.spi.SequentialCursor;
+import org.opends.server.backends.pluggable.spi.StorageRuntimeException;
+import org.opends.server.backends.pluggable.spi.StorageStatus;
+import org.opends.server.backends.pluggable.spi.TreeName;
+import org.opends.server.backends.pluggable.spi.UpdateFunction;
+import org.opends.server.backends.pluggable.spi.WriteOperation;
+import org.opends.server.backends.pluggable.spi.WriteableTransaction;
+import org.opends.server.core.ServerContext;
+import org.opends.server.types.BackupConfig;
+import org.opends.server.types.BackupDirectory;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.RestoreConfig;
+import org.opends.server.util.BackupManager;
+
+import com.datastax.oss.driver.api.core.CqlSession;
+import com.datastax.oss.driver.api.core.config.DriverConfigLoader;
+import com.datastax.oss.driver.api.core.cql.PreparedStatement;
+import com.datastax.oss.driver.api.core.cql.ResultSet;
+import com.datastax.oss.driver.api.core.cql.Row;
+import com.datastax.oss.driver.api.core.cql.Statement;
+import com.datastax.oss.driver.api.core.servererrors.InvalidQueryException;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+
+public class Storage implements org.opends.server.backends.pluggable.spi.Storage, ConfigurationChangeListener<CASBackendCfg>{
+	
+	private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
+
+	//private final ServerContext serverContext;
+	private CASBackendCfg config;
+	  
+	public Storage(CASBackendCfg cfg, ServerContext serverContext) {
+		//this.serverContext = serverContext;
+		this.config = cfg;
+	    cfg.addCASChangeListener(this);
+	}
+
+	//config
+	@Override
+	public boolean isConfigurationChangeAcceptable(CASBackendCfg configuration,List<LocalizableMessage> unacceptableReasons) {
+		return true;
+	}
+
+	@Override
+	public ConfigChangeResult applyConfigurationChange(CASBackendCfg cfg) {
+		final ConfigChangeResult ccr = new ConfigChangeResult();
+	    try
+	    {
+	    	this.config = cfg;
+	    }
+	    catch (Exception e)
+	    {
+	      addErrorMessage(ccr, LocalizableMessage.raw(stackTraceToSingleLineString(e)));
+	    }
+	    return ccr;
+	}
+
+	CqlSession session=null;
+	
+	final LoadingCache<String,PreparedStatement> prepared=CacheBuilder.newBuilder()
+			.expireAfterAccess(Duration.ofMinutes(10))
+			.maximumSize(4096)
+			.build(new CacheLoader<String,PreparedStatement>(){
+				@Override
+				public PreparedStatement load(String query) throws Exception {
+					return session.prepare(query);
+				}
+			});
+	
+	ResultSet execute(Statement<?> statement) {
+		if (logger.isTraceEnabled()) {
+			final ResultSet res=session.execute(statement.setTracing(true));
+			logger.trace(LocalizableMessage.raw(
+					"cassandra: %s"
+					,res.getExecutionInfo().getQueryTrace().getParameters()
+					)
+				);
+			return res;
+		}
+		return session.execute(statement);
+	}
+	
+	AccessMode accessMode=null;
+	@Override
+	public void open(AccessMode accessMode) throws Exception {
+		this.accessMode=accessMode;
+		session=CqlSession.builder()
+				.withApplicationName("OpenDJ "+config.getDBDirectory()+"."+config.getBackendId())
+				.withConfigLoader(DriverConfigLoader.fromDefaults(Storage.class.getClassLoader()))
+				.build();
+		if (AccessMode.READ_WRITE.equals(accessMode)) {
+			execute(prepared.getUnchecked("CREATE KEYSPACE IF NOT EXISTS "+getKeyspaceName()+" WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'};").bind().setExecutionProfileName(profile));
+		}
+		storageStatus = StorageStatus.working();
+	}
+
+	private StorageStatus storageStatus = StorageStatus.lockedDown(LocalizableMessage.raw("closed"));
+	@Override
+	public StorageStatus getStorageStatus() {
+		return storageStatus;
+	}
+	
+	@Override
+	public void close() {
+		storageStatus = StorageStatus.lockedDown(LocalizableMessage.raw("closed"));
+		if (session!=null && !session.isClosed()) {
+			session.close();
+		}
+		session=null;
+	}
+
+	String getKeyspaceName() {
+		return "\""+config.getDBDirectory().replaceAll("[^a-zA-z0-9_]", "_")+"\"";
+	}
+	
+	String getTableName() {
+		return getKeyspaceName()+".\""+config.getBackendId().replaceAll("[^a-zA-z0-9_]", "_")+"\"";
+	}
+	
+	@Override
+	public void removeStorageFiles() throws StorageRuntimeException {
+		final Boolean isOpen=getStorageStatus().isWorking();
+		if (!isOpen) {
+			try {
+				open(AccessMode.READ_WRITE);
+			}catch (Exception e) {
+				throw new StorageRuntimeException(e);
+			}
+		}
+		try {
+			execute(prepared.getUnchecked("TRUNCATE TABLE "+getTableName()+";").bind().setExecutionProfileName(profile));
+		}catch (Throwable e) {}
+		if (!isOpen) {
+			close();
+		}
+	}
+	
+	//operation
+	@Override
+	public <T> T read(ReadOperation<T> readOperation) throws Exception {
+		return readOperation.run(new TransactionImpl(AccessMode.READ_ONLY));
+	}
+
+	@Override
+	public void write(WriteOperation writeOperation) throws Exception {
+		writeOperation.run(new TransactionImpl(accessMode));
+	}
+
+	final static String profile="ddl";
+	static {
+		if (System.getProperty("datastax-java-driver.basic.request.timeout")==null) {
+			System.setProperty("datastax-java-driver.basic.request.timeout", "10 seconds");
+		}
+		if (System.getProperty("datastax-java-driver.profiles."+profile+".basic.request.timeout")==null) {
+			System.setProperty("datastax-java-driver.profiles."+profile+".basic.request.timeout", "30 seconds");
+		}
+	}
+	private final class TransactionImpl implements ReadableTransaction,WriteableTransaction {
+		
+		final AccessMode accessMode;
+		public TransactionImpl(AccessMode accessMode) {
+			super();
+			this.accessMode=accessMode;
+		}
+
+		@Override
+		public void openTree(TreeName name, boolean createOnDemand) {
+			if (createOnDemand) {
+				execute(prepared.getUnchecked("CREATE TABLE IF NOT EXISTS "+getTableName()+" (baseDN text,indexId text,key blob,value blob,PRIMARY KEY ((baseDN,indexId),key));").bind().setExecutionProfileName(profile));
+			}
+		}
+		
+		public void clearTree(TreeName treeName) {
+			checkReadOnly();
+			deleteTree(treeName);
+		}
+		
+		@Override
+		public ByteString read(TreeName treeName, ByteSequence key) {
+			final Row row=execute(
+					prepared.getUnchecked("SELECT value FROM "+getTableName()+" WHERE baseDN=:baseDN and indexId=:indexId and key=:key").bind()
+						.setString("baseDN", treeName.getBaseDN()).setString("indexId", treeName.getIndexId()) 
+						.setByteBuffer("key", ByteBuffer.wrap(key.toByteArray())) 
+					).one();
+			return row==null?null:ByteString.wrap(row.getByteBuffer("value").array());
+		}
+
+		@Override
+		public Cursor<ByteString, ByteString> openCursor(TreeName treeName) {
+			return new CursorImpl(this,treeName);
+		}
+
+		@Override
+		public long getRecordCount(TreeName treeName) {
+			return execute(
+					prepared.getUnchecked("SELECT count(*) FROM "+getTableName()+" WHERE baseDN=:baseDN and indexId=:indexId").bind()
+						.setString("baseDN", treeName.getBaseDN()).setString("indexId", treeName.getIndexId()) 
+					).one().getLong(0);
+		}
+
+		@Override
+		public void deleteTree(TreeName treeName) {
+			checkReadOnly();
+			openTree(treeName,true);
+			execute(
+					prepared.getUnchecked("DELETE FROM "+getTableName()+" WHERE baseDN=:baseDN and indexId=:indexId").bind()
+						.setString("baseDN", treeName.getBaseDN()).setString("indexId", treeName.getIndexId()) 
+					);
+		}
+
+		@Override
+		public void put(TreeName treeName, ByteSequence key, ByteSequence value) {
+			checkReadOnly();
+			execute(
+				prepared.getUnchecked("INSERT INTO "+getTableName()+" (baseDN,indexId,key,value) VALUES (:baseDN,:indexId,:key,:value)").bind()
+					.setString("baseDN", treeName.getBaseDN()).setString("indexId", treeName.getIndexId()) 
+					.setByteBuffer("key", ByteBuffer.wrap(key.toByteArray()))
+					.setByteBuffer("value",ByteBuffer.wrap(value.toByteArray()))
+				);
+		}
+
+		@Override
+		public boolean update(TreeName treeName, ByteSequence key, UpdateFunction f) {
+			checkReadOnly();
+			final ByteString oldValue=read(treeName,key);
+			final ByteSequence newValue=f.computeNewValue(oldValue);
+			if (Objects.equals(newValue, oldValue))
+	        {
+				return false;
+	        }
+	        if (newValue == null)
+	        {
+	        	delete(treeName, key);
+	        	return true;
+	        }
+	        put(treeName,key,newValue);
+			return true;
+		}
+
+		@Override
+		public boolean delete(TreeName treeName, ByteSequence key) {
+			checkReadOnly();
+			execute(
+					prepared.getUnchecked("DELETE FROM "+getTableName()+" WHERE baseDN=:baseDN and indexId=:indexId and key=:key").bind()
+						.setString("baseDN", treeName.getBaseDN()).setString("indexId", treeName.getIndexId()) 
+						.setByteBuffer("key", ByteBuffer.wrap(key.toByteArray()))
+					);
+			return true;
+		}
+		
+		void checkReadOnly() {
+			if (AccessMode.READ_ONLY.equals(accessMode)) {
+				throw new ReadOnlyStorageException();
+			}
+		}
+	}
+	
+	private final class CursorImpl implements Cursor<ByteString, ByteString> {
+		final TreeName treeName;
+		final TransactionImpl tx;
+
+		ResultSet rc;
+		Iterator<Row> iterator;
+		Row current=null;
+		
+		public CursorImpl(TransactionImpl tx,TreeName treeName) {
+			this.treeName=treeName;
+			this.tx=tx;
+		}
+
+		ResultSet full(){
+			return execute(
+						prepared.getUnchecked("SELECT key,value FROM "+getTableName()+" WHERE baseDN=:baseDN and indexId=:indexId ORDER BY key").bind()
+							.setString("baseDN", treeName.getBaseDN()).setString("indexId", treeName.getIndexId()) 
+						);
+		}
+		
+		@Override
+		public boolean next() {
+			if (iterator==null) {
+				rc=full();
+				iterator=rc.iterator();
+			}
+			try {
+				current=iterator.next();
+				return true;
+			}catch (NoSuchElementException e) {
+				current=null;
+			}
+			return false;
+		}
+
+		@Override
+		public boolean isDefined() {
+			return current!=null;
+		}
+
+		@Override
+		public ByteString getKey() throws NoSuchElementException {
+			if (!isDefined()) {
+				throw new NoSuchElementException();
+			}
+			return ByteString.wrap(current.getByteBuffer("key").array());
+		}
+
+		@Override
+		public ByteString getValue() throws NoSuchElementException {
+			if (!isDefined()) {
+				throw new NoSuchElementException();
+			}
+			return ByteString.wrap(current.getByteBuffer("value").array());
+		}
+
+		@Override
+		public void delete() throws NoSuchElementException, UnsupportedOperationException {
+			if (!isDefined()) {
+				throw new NoSuchElementException();
+			}
+			tx.delete(treeName, getKey());
+		}
+
+		@Override
+		public void close() {
+			iterator=null;
+			current=null;
+			rc=null;
+		}
+
+		ResultSet full(ByteSequence key){
+			return execute(
+						prepared.getUnchecked("SELECT key,value FROM "+getTableName()+" WHERE baseDN=:baseDN and indexId=:indexId and key>=:key ORDER BY key").bind()
+							.setString("baseDN", treeName.getBaseDN()).setString("indexId", treeName.getIndexId()) 
+							.setByteBuffer("key", ByteBuffer.wrap(key.toByteArray()))
+						);
+		}
+		@Override
+		public boolean positionToKeyOrNext(ByteSequence key) {
+			rc=full(key); // start iterator from key key>=:key 
+			iterator=rc.iterator();
+			if (iterator.hasNext()) {
+				current=iterator.next();
+				return true;
+			}
+			current=null;
+			return false;
+		}
+		
+		@Override
+		public boolean positionToKey(ByteSequence key) {
+			if (positionToKeyOrNext(key) && key.equals(getKey())){
+				return true;
+			}
+			current=null;
+			return false;
+		}
+
+		ResultSet last(){
+			return execute(
+						prepared.getUnchecked("SELECT key,value FROM "+getTableName()+" WHERE baseDN=:baseDN and indexId=:indexId ORDER BY key DESC LIMIT 1").bind()
+							.setString("baseDN", treeName.getBaseDN()).setString("indexId", treeName.getIndexId()) 
+						);
+		}
+		
+		@Override
+		public boolean positionToLastKey() {
+			rc=last(); 
+			iterator=rc.iterator();
+			if (iterator.hasNext()) {
+				current=iterator.next();
+				return true;
+			}
+			current=null;
+			return false;
+		}
+
+		@Override
+		public boolean positionToIndex(int index) {
+			iterator=rc.iterator(); //reset position
+			int ct=0;
+			while(iterator.hasNext()){
+				current=iterator.next();
+				if (ct==index) {
+					return true;
+				}
+				ct++;
+		    }
+			current=null;
+			return false;
+		}
+	}
+	
+	@Override
+	public Set<TreeName> listTrees() {
+		// TODO Auto-generated method stub
+		return Collections.emptySet();
+	}
+	
+
+	private final class ImporterImpl implements Importer {
+		final TransactionImpl tx;
+		
+		final Boolean isOpen;
+		
+		public ImporterImpl() {
+			isOpen=getStorageStatus().isWorking();
+			if (!isOpen) {
+				try {
+					open(AccessMode.READ_WRITE);
+				}catch (Exception e) {
+					throw new StorageRuntimeException(e);
+				}
+			}
+			tx=new TransactionImpl(accessMode);
+		}
+		
+		@Override
+		public void close() {
+			if (!isOpen) {
+				Storage.this.close();
+			}
+		}
+		
+		@Override
+		public void clearTree(TreeName name) {
+			tx.clearTree(name);
+		}
+		
+		@Override
+		public void put(TreeName treeName, ByteSequence key, ByteSequence value) {
+			tx.put(treeName, key, value);
+		}
+		
+		@Override
+		public ByteString read(TreeName treeName, ByteSequence key) {
+			return tx.read(treeName, key);
+		}
+		
+		@Override
+		public SequentialCursor<ByteString, ByteString> openCursor(TreeName treeName) {
+			return tx.openCursor(treeName);
+		}
+	}
+	
+	//import
+	@Override
+	public Importer startImport() throws ConfigException, StorageRuntimeException {
+		return new ImporterImpl();
+	}
+	
+	//backup
+	@Override
+	public boolean supportsBackupAndRestore() {
+		return true;
+	}
+
+	@Override
+	public void createBackup(BackupConfig backupConfig) throws DirectoryException
+	{
+		// TODO backup over snapshot or cassandra export
+		//new BackupManager(config.getBackendId()).createBackup(this, backupConfig);
+	}
+
+	@Override
+	public void removeBackup(BackupDirectory backupDirectory, String backupID) throws DirectoryException
+	{
+		new BackupManager(config.getBackendId()).removeBackup(backupDirectory, backupID);
+	}
+
+	@Override
+	public void restoreBackup(RestoreConfig restoreConfig) throws DirectoryException
+	{
+		// TODO restore over snapshot or cassandra export
+		//new BackupManager(config.getBackendId()).restoreBackup(this, restoreConfig);
+	}
+
+}
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/cassandra/package-info.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/cassandra/package-info.java
new file mode 100644
index 0000000..6ce885b
--- /dev/null
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/cassandra/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * The contents of this file are subject to the terms of the Common Development and
+ * Distribution License (the License). You may not use this file except in compliance with the
+ * License.
+ *
+ * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ * specific language governing permission and limitations under the License.
+ *
+ * When distributing Covered Software, include this CDDL Header Notice in each file and include
+ * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ * Header, with the fields enclosed by brackets [] replaced by your own identifying
+ * information: "Portions Copyright [year] [name of copyright owner]".
+ *
+ * Copyright 2023 3A Systems LLC.
+ */
+@org.opends.server.types.PublicAPI(
+     stability=org.opends.server.types.StabilityLevel.PRIVATE)
+package org.opends.server.backends.cassandra;
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/OnDiskMergeImporter.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/OnDiskMergeImporter.java
index df4ba93..cb50717 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/OnDiskMergeImporter.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/OnDiskMergeImporter.java
@@ -389,6 +389,7 @@
       final int initialThreadCount = maxThreadCount;
       final Long offheapMemorySize = backendCfg.getImportOffheapMemorySize();
       boolean useOffHeap = (offheapMemorySize != null && offheapMemorySize > 0);
+      
       long memoryAvailable =
           useOffHeap ? offheapMemorySize.longValue() : calculateAvailableHeapMemoryForBuffersAfterGC();
       int threadCount = initialThreadCount;
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/tools/BackendTypeHelper.java b/opendj-server-legacy/src/main/java/org/opends/server/tools/BackendTypeHelper.java
index 516c320..26dd222 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/tools/BackendTypeHelper.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/tools/BackendTypeHelper.java
@@ -140,7 +140,11 @@
     try
     {
       Class.forName(backendClassName);
-      backends.add(backendToAdd);
+      if (backendClassName.equals("org.opends.server.backends.jeb.JEBackend")) { //default
+    	  backends.add(0,backendToAdd);
+      }else {
+    	  backends.add(backendToAdd);
+      }
     }
     catch (ClassNotFoundException ignored)
     {
diff --git a/opendj-server-legacy/src/test/java/org/opends/server/TestCaseUtils.java b/opendj-server-legacy/src/test/java/org/opends/server/TestCaseUtils.java
index 3a42c32..5bfc227 100644
--- a/opendj-server-legacy/src/test/java/org/opends/server/TestCaseUtils.java
+++ b/opendj-server-legacy/src/test/java/org/opends/server/TestCaseUtils.java
@@ -1673,13 +1673,15 @@
     appendStreamContent(logsContents, TestCaseUtils.getSystemOutContents(), "System.out");
     appendStreamContent(logsContents, TestCaseUtils.getSystemErrContents(), "System.err");
     
-    for (final File logFile : Arrays.asList(new File(paths.testInstanceRoot, "logs").listFiles())) {
-    	 try {
-			appendStreamContent(logsContents, readFile(logFile.getPath()), logFile.getPath());
-		} catch (IOException e) {
-			e.printStackTrace();
-		}
-	}   
+    if (new File(paths.testInstanceRoot, "logs").listFiles()!=null) {
+	    for (final File logFile : Arrays.asList(new File(paths.testInstanceRoot, "logs").listFiles())) {
+	    	 try {
+				appendStreamContent(logsContents, readFile(logFile.getPath()), logFile.getPath());
+			} catch (IOException e) {
+				e.printStackTrace();
+			}
+		}   
+    }
   }
 
   private static void appendStreamContent(StringBuilder out, String content, String name)
diff --git a/opendj-server-legacy/src/test/java/org/opends/server/TestListener.java b/opendj-server-legacy/src/test/java/org/opends/server/TestListener.java
index 5b92eab..461d615 100644
--- a/opendj-server-legacy/src/test/java/org/opends/server/TestListener.java
+++ b/opendj-server-legacy/src/test/java/org/opends/server/TestListener.java
@@ -13,6 +13,7 @@
  *
  * Copyright 2008 Sun Microsystems, Inc.
  * Portions Copyright 2013-2016 ForgeRock AS.
+ * Portions Copyright 2023 3A Systems, LLC.
  */
 package org.opends.server;
 
@@ -246,7 +247,6 @@
         && countTestsWithStatus(ITestResult.SKIP) != 0) {
       originalSystemErr.println("There were no explicit test failures,"
           + " but some tests were skipped (possibly due to errors in @Before* or @After* methods).");
-      System.exit(-1);
     }
   }
 
diff --git a/opendj-server-legacy/src/test/java/org/opends/server/backends/cassandra/EncryptedTestCase.java b/opendj-server-legacy/src/test/java/org/opends/server/backends/cassandra/EncryptedTestCase.java
new file mode 100644
index 0000000..03bfd8b
--- /dev/null
+++ b/opendj-server-legacy/src/test/java/org/opends/server/backends/cassandra/EncryptedTestCase.java
@@ -0,0 +1,62 @@
+/*
+ * The contents of this file are subject to the terms of the Common Development and
+ * Distribution License (the License). You may not use this file except in compliance with the
+ * License.
+ *
+ * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ * specific language governing permission and limitations under the License.
+ *
+ * When distributing Covered Software, include this CDDL Header Notice in each file and include
+ * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ * Header, with the fields enclosed by brackets [] replaced by your own identifying
+ * information: "Portions Copyright [year] [name of copyright owner]".
+ *
+ * Copyright 2023 3A Systems, LLC.
+ */
+package org.opends.server.backends.cassandra;
+
+import static org.mockito.Mockito.when;
+import static org.forgerock.opendj.config.ConfigurationMock.mockCfg;
+
+import org.forgerock.opendj.server.config.server.CASBackendCfg;
+import org.opends.server.backends.pluggable.PluggableBackendImplTestCase;
+import org.testng.SkipException;
+import org.testng.annotations.Test;
+
+import com.datastax.oss.driver.api.core.AllNodesFailedException;
+import com.datastax.oss.driver.api.core.CqlSession;
+import com.datastax.oss.driver.api.core.config.DriverConfigLoader;
+
+//docker run --rm -it -p 9042:9042 --name cassandra cassandra
+
+@Test
+public class EncryptedTestCase extends PluggableBackendImplTestCase<CASBackendCfg>
+{
+  @Override
+  protected Backend createBackend()
+  {
+	  System.setProperty("datastax-java-driver.basic.request.timeout", "30 seconds"); //for docker slow start
+	  //test allow cassandra
+	  try(CqlSession session=CqlSession.builder()
+			.withConfigLoader(DriverConfigLoader.fromDefaults(Storage.class.getClassLoader()))
+			.build()){
+		session.close();
+	  }catch (AllNodesFailedException e) {
+		  throw new SkipException("run before test: docker run --rm -it -p 9042:9042 --name cassandra cassandra");
+	  }
+	  return new Backend();
+  }
+
+  @Override
+  protected CASBackendCfg createBackendCfg()
+  {
+	  CASBackendCfg backendCfg = mockCfg(CASBackendCfg.class);
+	  when(backendCfg.getBackendId()).thenReturn("EncCASTestCase"+System.currentTimeMillis());
+	  when(backendCfg.getDBDirectory()).thenReturn("EncCASTestCase");
+	  
+	  when(backendCfg.isConfidentialityEnabled()).thenReturn(true);
+	  when(backendCfg.getCipherKeyLength()).thenReturn(128);
+	  when(backendCfg.getCipherTransformation()).thenReturn("AES/CBC/PKCS5Padding");
+	  return backendCfg;
+  }
+}
diff --git a/opendj-server-legacy/src/test/java/org/opends/server/backends/cassandra/TestCase.java b/opendj-server-legacy/src/test/java/org/opends/server/backends/cassandra/TestCase.java
new file mode 100644
index 0000000..18b0656
--- /dev/null
+++ b/opendj-server-legacy/src/test/java/org/opends/server/backends/cassandra/TestCase.java
@@ -0,0 +1,56 @@
+/*
+ * The contents of this file are subject to the terms of the Common Development and
+ * Distribution License (the License). You may not use this file except in compliance with the
+ * License.
+ *
+ * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ * specific language governing permission and limitations under the License.
+ *
+ * When distributing Covered Software, include this CDDL Header Notice in each file and include
+ * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ * Header, with the fields enclosed by brackets [] replaced by your own identifying
+ * information: "Portions Copyright [year] [name of copyright owner]".
+ *
+ * Copyright 2023 3A Systems, LLC.
+ */
+package org.opends.server.backends.cassandra;
+
+import static org.mockito.Mockito.when;
+import static org.forgerock.opendj.config.ConfigurationMock.mockCfg;
+
+import org.forgerock.opendj.server.config.server.CASBackendCfg;
+import org.opends.server.backends.pluggable.PluggableBackendImplTestCase;
+import org.testng.SkipException;
+import org.testng.annotations.Test;
+
+import com.datastax.oss.driver.api.core.AllNodesFailedException;
+import com.datastax.oss.driver.api.core.CqlSession;
+import com.datastax.oss.driver.api.core.config.DriverConfigLoader;
+
+//docker run --rm -it -p 9042:9042 --name cassandra cassandra
+
+@Test
+public class TestCase extends PluggableBackendImplTestCase<CASBackendCfg> {
+
+	@Override
+	protected Backend createBackend() {
+		System.setProperty("datastax-java-driver.basic.request.timeout", "30 seconds"); //for docker slow start
+		//test allow cassandra
+		try(CqlSession session=CqlSession.builder()
+				.withConfigLoader(DriverConfigLoader.fromDefaults(Storage.class.getClassLoader()))
+				.build()){
+			session.close();
+		}catch (AllNodesFailedException e) {
+			throw new SkipException("run before test: docker run --rm -it -p 9042:9042 --name cassandra cassandra");
+		}
+		return new Backend();
+	}
+
+	@Override
+	protected CASBackendCfg createBackendCfg() {
+		CASBackendCfg backendCfg = mockCfg(CASBackendCfg.class);
+		when(backendCfg.getBackendId()).thenReturn("CASTestCase");
+		when(backendCfg.getDBDirectory()).thenReturn("CASTestCase");
+		return backendCfg;
+	}
+}
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 9a9f4c0..a5d3e26 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
@@ -12,6 +12,7 @@
  * information: "Portions Copyright [year] [name of copyright owner]".
  *
  * Copyright 2015-2016 ForgeRock AS.
+ * Copyright 2023 	   3A Systems, LLC.
  */
 package org.opends.server.backends.pluggable;
 
@@ -66,6 +67,7 @@
 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.controls.SubtreeDeleteControl;
 import org.opends.server.core.AddOperation;
 import org.opends.server.core.DeleteOperation;
 import org.opends.server.core.ModifyDNOperation;
@@ -186,6 +188,12 @@
     backend.configureBackend(backendCfg, TestCaseUtils.getServerContext());
     backend.openBackend();
 
+    if (backend.entryExists(testBaseDN)) {
+    	DeleteOperation op = mock(DeleteOperation.class);
+    	when(op.getRequestControl(SubtreeDeleteControl.DECODER)).thenReturn(new SubtreeDeleteControl(true));
+        backend.deleteEntry(testBaseDN, op);
+    }
+    
     topEntries = TestCaseUtils.makeEntries(
                 "dn: " + testBaseDN,
                 "objectclass: top",
@@ -550,7 +558,7 @@
     searchDN = entries.get(1).getName();
     badEntryDN = testBaseDN.child(DN.valueOf("ou=bogus")).child(DN.valueOf("ou=dummy"));
     backupID = "backupID1";
-
+    
     addEntriesToBackend(topEntries);
     addEntriesToBackend(entries);
     addEntriesToBackend(workEntries);
@@ -1171,6 +1179,8 @@
     backend.finalizeBackend();
     try
     {
+      readOnlyContainer.open(AccessMode.READ_WRITE); //init storage before reading
+      readOnlyContainer.close();
       readOnlyContainer.open(AccessMode.READ_ONLY);
       readOnlyContainer.getStorage().write(new WriteOperation()
       {
diff --git a/pom.xml b/pom.xml
index 24c7acc..6d80543 100644
--- a/pom.xml
+++ b/pom.xml
@@ -400,7 +400,7 @@
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
                     <artifactId>maven-failsafe-plugin</artifactId>
-                    <version>3.1.0</version>
+                    <version>3.1.2</version>
                 </plugin>
                 
                 <plugin>

--
Gitblit v1.10.0