Add test dependencies for FoundationDB

This commit is contained in:
Jon Chambers 2025-06-24 22:03:34 -04:00
parent d6f14d02dd
commit a99f7bb87d
7 changed files with 217 additions and 0 deletions

View File

@ -332,6 +332,12 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>earth.adi</groupId>
<artifactId>testcontainers-foundationdb</artifactId>
<version>1.1.0</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>

View File

@ -502,6 +502,12 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>earth.adi</groupId>
<artifactId>testcontainers-foundationdb</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.auth</groupId>
<artifactId>google-auth-library-oauth2-http</artifactId>
@ -733,6 +739,7 @@
<!-- add-opens: work around PATCH not being a supported method on HttpUrlConnection -->
<argLine>-javaagent:${org.mockito:mockito-core:jar} --add-opens=java.base/java.net=ALL-UNNAMED</argLine>
<systemPropertyVariables>
<foundationdb.version>${foundationdb.version}</foundationdb.version>
<localstackImage>${localstack.image}</localstackImage>
</systemPropertyVariables>
</configuration>

View File

@ -0,0 +1,19 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.storage;
import com.apple.foundationdb.Database;
import com.apple.foundationdb.FDB;
import java.io.IOException;
interface FoundationDbDatabaseLifecycleManager {
void initializeDatabase(final FDB fdb) throws IOException;
Database getDatabase();
void closeDatabase();
}

View File

@ -0,0 +1,43 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.storage;
import com.apple.foundationdb.Database;
import com.apple.foundationdb.FDB;
import java.io.IOException;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
class FoundationDbExtension implements BeforeAllCallback, ExtensionContext.Store.CloseableResource {
private static FoundationDbDatabaseLifecycleManager databaseLifecycleManager;
@Override
public void beforeAll(final ExtensionContext context) throws IOException {
if (databaseLifecycleManager == null) {
final String serviceContainerName = System.getProperty("foundationDb.serviceContainerName");
databaseLifecycleManager = serviceContainerName != null
? new ServiceContainerFoundationDbDatabaseLifecycleManager(serviceContainerName)
: new TestcontainersFoundationDbDatabaseLifecycleManager();
databaseLifecycleManager.initializeDatabase(FDB.selectAPIVersion(730));
context.getRoot().getStore(ExtensionContext.Namespace.GLOBAL).put(getClass().getName(), this);
}
}
public Database getDatabase() {
return databaseLifecycleManager.getDatabase();
}
@Override
public void close() throws Throwable {
if (databaseLifecycleManager != null) {
databaseLifecycleManager.closeDatabase();
}
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.storage;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.whispersystems.textsecuregcm.util.TestRandomUtil;
import java.nio.charset.StandardCharsets;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
public class FoundationDbTest {
@RegisterExtension
static FoundationDbExtension FOUNDATION_DB_EXTENSION = new FoundationDbExtension();
@Test
void setGetValue() {
final byte[] key = "test".getBytes(StandardCharsets.UTF_8);
final byte[] value = TestRandomUtil.nextBytes(16);
FOUNDATION_DB_EXTENSION.getDatabase().run(transaction -> {
transaction.set(key, value);
return null;
});
final byte[] retrievedValue = FOUNDATION_DB_EXTENSION.getDatabase().run(transaction -> transaction.get(key).join());
assertArrayEquals(value, retrievedValue);
}
}

View File

@ -0,0 +1,63 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.storage;
import com.apple.foundationdb.Database;
import com.apple.foundationdb.FDB;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Manages the lifecycle of a database connected to a FoundationDB instance running as an external service container.
*/
class ServiceContainerFoundationDbDatabaseLifecycleManager implements FoundationDbDatabaseLifecycleManager {
private final String foundationDbServiceContainerName;
private Database database;
private static final Logger log = LoggerFactory.getLogger(ServiceContainerFoundationDbDatabaseLifecycleManager.class);
ServiceContainerFoundationDbDatabaseLifecycleManager(final String foundationDbServiceContainerName) {
log.info("Using FoundationDB service container: {}", foundationDbServiceContainerName);
this.foundationDbServiceContainerName = foundationDbServiceContainerName;
}
@Override
public void initializeDatabase(final FDB fdb) throws IOException {
final File clusterFile = File.createTempFile("fdb.cluster", "");
clusterFile.deleteOnExit();
try (final FileWriter fileWriter = new FileWriter(clusterFile)) {
fileWriter.write(String.format("docker:docker@%s:4500", foundationDbServiceContainerName));
}
// If we don't initialize the database before trying to use it, things will just hang in a mysterious, message-free
// way. Note that the `new` keyword in `configure new single memory` means that we can't accidentally clobber an
// existing database (though initialization may fail if there's already a database present).
new ProcessBuilder("/usr/bin/fdbcli",
"-C", clusterFile.getAbsolutePath(),
"--exec", "configure new single memory")
.start()
.onExit()
.join();
database = fdb.open(clusterFile.getAbsolutePath());
}
@Override
public Database getDatabase() {
return database;
}
@Override
public void closeDatabase() {
database.close();
}
}

View File

@ -0,0 +1,45 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.storage;
import com.apple.foundationdb.Database;
import com.apple.foundationdb.FDB;
import earth.adi.testcontainers.containers.FoundationDBContainer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testcontainers.utility.DockerImageName;
class TestcontainersFoundationDbDatabaseLifecycleManager implements FoundationDbDatabaseLifecycleManager {
private FoundationDBContainer foundationDBContainer;
private Database database;
private static final String FOUNDATIONDB_IMAGE_NAME = "foundationdb/foundationdb:" +
System.getProperty("foundationdb.version", "7.3.62");
private static final Logger log = LoggerFactory.getLogger(TestcontainersFoundationDbDatabaseLifecycleManager.class);
@Override
public void initializeDatabase(final FDB fdb) {
log.info("Using Testcontainers FoundationDB container: {}", FOUNDATIONDB_IMAGE_NAME);
foundationDBContainer = new FoundationDBContainer(DockerImageName.parse(FOUNDATIONDB_IMAGE_NAME));
foundationDBContainer.start();
database = fdb.open(foundationDBContainer.getClusterFilePath());
}
@Override
public Database getDatabase() {
return database;
}
@Override
public void closeDatabase() {
database.close();
foundationDBContainer.close();
}
}