.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: 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(); 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> opendj-maven-plugin/src/main/resources/config/xml/org/forgerock/opendj/server/config/CASBackendConfiguration.xml
New file @@ -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> 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> 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 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()); } } opendj-server-legacy/src/main/java/org/opends/server/backends/cassandra/Backend.java
New file @@ -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); } } opendj-server-legacy/src/main/java/org/opends/server/backends/cassandra/Storage.java
New file @@ -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); } } opendj-server-legacy/src/main/java/org/opends/server/backends/cassandra/package-info.java
New file @@ -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; 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; 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) { 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) 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); } } opendj-server-legacy/src/test/java/org/opends/server/backends/cassandra/EncryptedTestCase.java
New file @@ -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; } } opendj-server-legacy/src/test/java/org/opends/server/backends/cassandra/TestCase.java
New file @@ -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; } } 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() { 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>