-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Add scylladb module #8002
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add scylladb module #8002
Changes from 1 commit
86ed2cb
bc7eb8b
35ea1c2
8590397
f10e620
1805c16
b5fa599
af00b41
7badf3d
60f7dd4
ccd7e41
8ac1c15
7a356f2
181472e
19ae30a
3b95f64
6ab9c7e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -47,6 +47,7 @@ body: | |
| - QuestDB | ||
| - RabbitMQ | ||
| - Redpanda | ||
| - ScyllaDB | ||
| - Selenium | ||
| - Solace | ||
| - Solr | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -47,6 +47,7 @@ body: | |
| - QuestDB | ||
| - RabbitMQ | ||
| - Redpanda | ||
| - ScyllaDB | ||
| - Selenium | ||
| - Solace | ||
| - Solr | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -47,6 +47,7 @@ body: | |
| - Pulsar | ||
| - RabbitMQ | ||
| - Redpanda | ||
| - ScyllaDB | ||
| - Selenium | ||
| - Solace | ||
| - Solr | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| # ScyllaDB Module | ||
|
|
||
| ## Usage example | ||
|
|
||
| This example connects to the ScyllaDB Cluster, creates a keyspaces and asserts that is has been created. | ||
|
|
||
| <!--codeinclude--> | ||
| [Building CqlSession](../../../modules/scylladb/src/test/java/org/testcontainers/containers/ScyllaDBDriver4Test.java) inside_block:scylladb | ||
| <!--/codeinclude--> | ||
|
|
||
| ## Adding this module to your project dependencies | ||
|
|
||
| Add the following dependency to your `pom.xml`/`build.gradle` file: | ||
|
|
||
| === "Gradle" | ||
| ```groovy | ||
| testImplementation "org.testcontainers:scylladb:{{latest_version}}" | ||
| ``` | ||
|
|
||
| === "Maven" | ||
| ```xml | ||
| <dependency> | ||
| <groupId>org.testcontainers</groupId> | ||
| <artifactId>scylladb</artifactId> | ||
| <version>{{latest_version}}</version> | ||
| <scope>test</scope> | ||
| </dependency> | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| description = "Testcontainers :: ScyllaDB" | ||
|
|
||
| configurations.all { | ||
| resolutionStrategy { | ||
| force 'io.dropwizard.metrics:metrics-core:3.2.6' | ||
| } | ||
| } | ||
|
|
||
| dependencies { | ||
| api project(":database-commons") | ||
| api "com.scylladb:java-driver-core:4.15.0.0" | ||
| api "com.datastax.cassandra:cassandra-driver-core:3.10.0" | ||
mkorolyov marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| testImplementation 'org.assertj:assertj-core:3.24.2' | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,194 @@ | ||
| package org.testcontainers.containers; | ||
|
|
||
| import com.github.dockerjava.api.command.InspectContainerResponse; | ||
| import org.apache.commons.io.IOUtils; | ||
| import org.testcontainers.containers.delegate.ScyllaDBDatabaseDelegate; | ||
| import org.testcontainers.delegate.DatabaseDelegate; | ||
| import org.testcontainers.ext.ScriptUtils; | ||
| import org.testcontainers.ext.ScriptUtils.ScriptLoadException; | ||
| import org.testcontainers.utility.DockerImageName; | ||
| import org.testcontainers.utility.MountableFile; | ||
|
|
||
| import java.io.IOException; | ||
| import java.net.InetSocketAddress; | ||
| import java.net.URL; | ||
| import java.nio.charset.StandardCharsets; | ||
| import java.util.Optional; | ||
|
|
||
| import javax.script.ScriptException; | ||
|
|
||
| /** | ||
| * Testcontainers implementation for ScyllaDB. | ||
| * <p> | ||
| * Supported image: {@code scylladb} | ||
| * <p> | ||
| * Exposed ports: 9042 | ||
| */ | ||
| public class ScyllaDBContainer<SELF extends ScyllaDBContainer<SELF>> extends GenericContainer<SELF> { | ||
|
|
||
| private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("scylladb/scylla:5.2.9"); | ||
|
|
||
| public static final Integer CQL_PORT = 9042; | ||
|
|
||
| private static final String DEFAULT_LOCAL_DATACENTER = "datacenter1"; | ||
|
|
||
| private static final String CONTAINER_CONFIG_LOCATION = "/etc/scylla"; | ||
|
|
||
| private static final String USERNAME = "scylladb"; | ||
|
|
||
| private static final String PASSWORD = "scylladb"; | ||
|
|
||
| private String configLocation; | ||
|
|
||
| private String initScriptPath; | ||
|
|
||
| private boolean enableJmxReporting; | ||
|
|
||
| public ScyllaDBContainer(String dockerImageName) { | ||
| this(DockerImageName.parse(dockerImageName)); | ||
| } | ||
|
|
||
| public ScyllaDBContainer(DockerImageName dockerImageName) { | ||
| super(dockerImageName); | ||
| dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME); | ||
|
|
||
| addExposedPort(CQL_PORT); | ||
| this.enableJmxReporting = false; | ||
|
|
||
| withEnv("CASSANDRA_SNITCH", "GossipingPropertyFileSnitch"); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this needs to be checked, scylla docker doesn't use this variable |
||
| withEnv("JVM_OPTS", "-Dcassandra.skip_wait_for_gossip_to_settle=0 -Dcassandra.initial_token=0"); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. those are not relevant to scylla, you need commandline option for |
||
| withEnv("HEAP_NEWSIZE", "128M"); | ||
| withEnv("MAX_HEAP_SIZE", "1024M"); | ||
| withEnv("SCYLLADB_ENDPOINT_SNITCH", "GossipingPropertyFileSnitch"); | ||
| withEnv("SCYLLADB_DC", DEFAULT_LOCAL_DATACENTER); | ||
| } | ||
|
|
||
| @Override | ||
| protected void configure() { | ||
| optionallyMapResourceParameterAsVolume(CONTAINER_CONFIG_LOCATION, configLocation); | ||
| } | ||
|
|
||
| @Override | ||
| protected void containerIsStarted(InspectContainerResponse containerInfo) { | ||
| runInitScriptIfRequired(); | ||
| } | ||
|
|
||
| /** | ||
| * Load init script content and apply it to the database if initScriptPath is set | ||
| */ | ||
| private void runInitScriptIfRequired() { | ||
| if (initScriptPath != null) { | ||
| try { | ||
| URL resource = Thread.currentThread().getContextClassLoader().getResource(initScriptPath); | ||
| if (resource == null) { | ||
| logger().warn("Could not load classpath init script: {}", initScriptPath); | ||
| throw new ScriptLoadException( | ||
| "Could not load classpath init script: " + initScriptPath + ". Resource not found." | ||
| ); | ||
| } | ||
| String cql = IOUtils.toString(resource, StandardCharsets.UTF_8); | ||
| DatabaseDelegate databaseDelegate = new ScyllaDBDatabaseDelegate(this); | ||
| ScriptUtils.executeDatabaseScript(databaseDelegate, initScriptPath, cql); | ||
| } catch (IOException e) { | ||
| logger().warn("Could not load classpath init script: {}", initScriptPath); | ||
| throw new ScriptLoadException("Could not load classpath init script: " + initScriptPath, e); | ||
| } catch (ScriptException e) { | ||
| logger().error("Error while executing init script: {}", initScriptPath, e); | ||
| throw new ScriptUtils.UncategorizedScriptException( | ||
| "Error while executing init script: " + initScriptPath, | ||
| e | ||
| ); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Map (effectively replace) directory in Docker with the content of resourceLocation if resource location is not null | ||
| * | ||
| * Protected to allow for changing implementation by extending the class | ||
| * | ||
| * @param pathNameInContainer path in docker | ||
| * @param resourceLocation relative classpath to resource | ||
| */ | ||
| protected void optionallyMapResourceParameterAsVolume(String pathNameInContainer, String resourceLocation) { | ||
| Optional | ||
| .ofNullable(resourceLocation) | ||
| .map(MountableFile::forClasspathResource) | ||
| .ifPresent(mountableFile -> withCopyFileToContainer(mountableFile, pathNameInContainer)); | ||
| } | ||
|
|
||
| /** | ||
| * Initialize ScyllaDB with the custom overridden ScyllaDB configuration | ||
| * <p> | ||
| * Be aware, that Docker effectively replaces all /etc/sylladb content with the content of config location, so if | ||
| * scylladb.yaml in configLocation is absent or corrupted, then ScyllaDB just won't launch | ||
| * | ||
| * @param configLocation relative classpath with the directory that contains cassandra.yaml and other configuration files | ||
| */ | ||
| public SELF withConfigurationOverride(String configLocation) { | ||
| this.configLocation = configLocation; | ||
| return self(); | ||
| } | ||
|
|
||
| /** | ||
| * Initialize ScyllaDB with init CQL script | ||
| * <p> | ||
| * CQL script will be applied after container is started (see using WaitStrategy) | ||
| * | ||
| * @param initScriptPath relative classpath resource | ||
| */ | ||
| public SELF withInitScript(String initScriptPath) { | ||
| this.initScriptPath = initScriptPath; | ||
| return self(); | ||
| } | ||
|
|
||
| /** | ||
| * Initialize ScyllaDB client with JMX reporting enabled or disabled | ||
| */ | ||
| public SELF withJmxReporting(boolean enableJmxReporting) { | ||
| this.enableJmxReporting = enableJmxReporting; | ||
| return self(); | ||
| } | ||
|
|
||
| /** | ||
| * Get username | ||
| * | ||
| * By default ScyllaDB has authenticator: AllowAllAuthenticator in scylladb.yaml | ||
| * If username and password need to be used, then authenticator should be set as PasswordAuthenticator | ||
| * (through custom ScyllaDB configuration) and through CQL with default scylladb-scylladb credentials | ||
| * user management should be modified | ||
| */ | ||
| public String getUsername() { | ||
| return USERNAME; | ||
| } | ||
|
|
||
| /** | ||
| * Get password | ||
| * | ||
| * By default ScyllaDB has authenticator: AllowAllAuthenticator in scylladb.yaml | ||
| * If username and password need to be used, then authenticator should be set as PasswordAuthenticator | ||
| * (through custom Cassandra configuration) and through CQL with default scylladb-scylladb credentials | ||
| * user management should be modified | ||
| */ | ||
| public String getPassword() { | ||
| return PASSWORD; | ||
| } | ||
|
|
||
| /** | ||
| * Retrieve an {@link InetSocketAddress} for connecting to the ScyllaDB container via the driver. | ||
| * | ||
| * @return A InetSocketAddrss representation of this ScyllaDB container's host and port. | ||
| */ | ||
| public InetSocketAddress getContactPoint() { | ||
| return new InetSocketAddress(getHost(), getMappedPort(CQL_PORT)); | ||
| } | ||
|
|
||
| /** | ||
| * Retrieve the Local Datacenter for connecting to the ScyllaDB container via the driver. | ||
| * | ||
| * @return The configured local Datacenter name. | ||
| */ | ||
| public String getLocalDatacenter() { | ||
| return getEnvMap().getOrDefault("SCYLLADB_DC", DEFAULT_LOCAL_DATACENTER); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| package org.testcontainers.containers.delegate; | ||
|
|
||
| import com.datastax.oss.driver.api.core.CqlSession; | ||
| import com.datastax.oss.driver.api.core.DriverException; | ||
| import com.datastax.oss.driver.api.core.cql.ResultSet; | ||
| import org.slf4j.Logger; | ||
| import org.testcontainers.containers.ContainerState; | ||
| import org.testcontainers.containers.ScyllaDBContainer; | ||
| import org.testcontainers.delegate.AbstractDatabaseDelegate; | ||
| import org.testcontainers.exception.ConnectionCreationException; | ||
| import org.testcontainers.ext.ScriptUtils.ScriptStatementFailedException; | ||
| import org.testcontainers.utility.DockerLoggerFactory; | ||
|
|
||
| import java.net.InetSocketAddress; | ||
|
|
||
| public class ScyllaDBDatabaseDelegate extends AbstractDatabaseDelegate<CqlSession> { | ||
|
|
||
| public ScyllaDBDatabaseDelegate(ContainerState container) { | ||
| this.container = container; | ||
| } | ||
|
|
||
| protected Logger logger() { | ||
| return DockerLoggerFactory.getLogger(container.getCurrentContainerInfo().getName()); | ||
| } | ||
|
|
||
| private final ContainerState container; | ||
|
|
||
| @Override | ||
| protected CqlSession createNewConnection() { | ||
| try { | ||
| return CqlSession | ||
| .builder() | ||
| .addContactPoint( | ||
| new InetSocketAddress(container.getHost(), container.getMappedPort(ScyllaDBContainer.CQL_PORT)) | ||
| ) | ||
| .withLocalDatacenter("datacenter1") | ||
| .build(); | ||
| } catch (DriverException e) { | ||
| throw new ConnectionCreationException("Could not obtain cassandra connection", e); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public void execute( | ||
| String statement, | ||
| String scriptPath, | ||
| int lineNumber, | ||
| boolean continueOnError, | ||
| boolean ignoreFailedDrops | ||
| ) { | ||
| try { | ||
| ResultSet result = getConnection().execute(statement); | ||
| if (!result.wasApplied()) { | ||
| throw new ScriptStatementFailedException(statement, lineNumber, scriptPath); | ||
| } | ||
| } catch (DriverException e) { | ||
| throw new ScriptStatementFailedException(statement, lineNumber, scriptPath, e); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| protected void closeConnectionQuietly(CqlSession session) { | ||
| try { | ||
| session.close(); | ||
| } catch (Exception e) { | ||
| logger().error("Could not close cassandra connection", e); | ||
| } | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.