diff --git a/modules/cassandra/build.gradle b/modules/cassandra/build.gradle index 9704a05b69d..8284fec8b72 100644 --- a/modules/cassandra/build.gradle +++ b/modules/cassandra/build.gradle @@ -1,6 +1,6 @@ description = "TestContainers :: Cassandra" dependencies { - compile project(":database-commons") + compile project(":jdbc") compile "com.datastax.cassandra:cassandra-driver-core:3.7.1" } diff --git a/modules/cassandra/pom.xml b/modules/cassandra/pom.xml new file mode 100644 index 00000000000..2f939f2810b --- /dev/null +++ b/modules/cassandra/pom.xml @@ -0,0 +1,94 @@ + + + 4.0.0 + org.testcontainers + cassandra-2179 + pr-2179 + jar + + cassandra + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.6.0 + + true + 256m + 1024m + 1.8 + 1.8 + UTF-8 + + + + + + + 1.12.3 + 3.8.0 + + + + + + org.testcontainers + testcontainers-bom + ${testcontainers.version} + import + pom + + + + + + + + org.testcontainers + database-commons + + + org.testcontainers + jdbc + + + commons-io + commons-io + 2.6 + + + org.projectlombok + lombok + 1.18.8 + + + org.rnorth.duct-tape + duct-tape + 1.0.7 + + + com.datastax.cassandra + cassandra-driver-core + ${datastax.version} + + + com.datastax.cassandra + cassandra-driver-mapping + ${datastax.version} + + + com.datastax.cassandra + cassandra-driver-extras + ${datastax.version} + + + + com.github.adejanovski + cassandra-jdbc-wrapper + 3.1.0 + test + + + diff --git a/modules/cassandra/src/main/java/org/testcontainers/containers/CassandraContainer.java b/modules/cassandra/src/main/java/org/testcontainers/containers/CassandraContainer.java index a78d23e4b4b..29fbfaed143 100644 --- a/modules/cassandra/src/main/java/org/testcontainers/containers/CassandraContainer.java +++ b/modules/cassandra/src/main/java/org/testcontainers/containers/CassandraContainer.java @@ -22,32 +22,53 @@ * * @author Eugeny Karpov */ -public class CassandraContainer> extends GenericContainer { +public class CassandraContainer> extends JdbcDatabaseContainer { - public static final String IMAGE = "cassandra"; + public static final String NAME = "cassandra"; public static final Integer CQL_PORT = 9042; + public static final Integer THRIFT_PORT = 9160; + public static final Integer JMX_PORT = 7199; private static final String CONTAINER_CONFIG_LOCATION = "/etc/cassandra"; private static final String USERNAME = "cassandra"; private static final String PASSWORD = "cassandra"; + private static final String DEFAULT_DRIVER_CLASSNAME = "com.github.adejanovski.cassandra.jdbc.CassandraDriver"; + + public static final String IMAGE = "cassandra"; + public static final String DEFAULT_TAG = "3.11.2"; private String configLocation; private String initScriptPath; + private boolean enableNativeAPI; private boolean enableJmxReporting; + private String driverClassname = DEFAULT_DRIVER_CLASSNAME; public CassandraContainer() { - this(IMAGE + ":3.11.2"); + this(IMAGE + ":" + DEFAULT_TAG); } public CassandraContainer(String dockerImageName) { super(dockerImageName); addExposedPort(CQL_PORT); setStartupAttempts(3); + this.enableNativeAPI = false; this.enableJmxReporting = false; } + @Override + protected Integer getLivenessCheckPort() { + return getMappedPort(CQL_PORT); + } + @Override protected void configure() { optionallyMapResourceParameterAsVolume(CONTAINER_CONFIG_LOCATION, configLocation); + if (enableNativeAPI) { + addExposedPort(THRIFT_PORT); + addEnv("CASSANDRA_START_RPC", "true"); + } + if (enableJmxReporting) { + addExposedPort(JMX_PORT); + } } @Override @@ -58,7 +79,7 @@ protected void containerIsStarted(InspectContainerResponse containerInfo) { /** * Load init script content and apply it to the database if initScriptPath is set */ - private void runInitScriptIfRequired() { + protected void runInitScriptIfRequired() { if (initScriptPath != null) { try { URL resource = Thread.currentThread().getContextClassLoader().getResource(initScriptPath); @@ -118,6 +139,24 @@ public SELF withInitScript(String initScriptPath) { return self(); } + /** + * Initialize Cassandra with Native support (via thrift over RPC) with default driver classname. + * This is usually required for JDBC access. + */ + public SELF withNativeAPI(boolean enableNativeAPI) { + return withNativeAPI(enableNativeAPI, DEFAULT_DRIVER_CLASSNAME); + } + + /** + * Initialize Cassandra with Native support (via thrift over RPC) using specified driver classname. + * This is usually required for JDBC access. + */ + public SELF withNativeAPI(boolean enableNativeAPI, String driverClassname) { + this.enableNativeAPI = enableNativeAPI; + this.driverClassname = driverClassname; + return self(); + } + /** * Initialize Cassandra client with JMX reporting enabled or disabled */ @@ -150,6 +189,64 @@ public String getPassword() { return PASSWORD; } + /** + * Get recommended driver classname. + */ + @Override + public String getDriverClassName() { + return driverClassname; + } + + /** + * Get JDBC URL + * + * Returns appropriate JDBC URL if JDBC support is enabled. Otherwise throws UnsupportedOperationException. + * If a keyspace is used it should be appended to the URL. + */ + @Override + public String getJdbcUrl() { + if (enableNativeAPI) { + return "jdbc:cassandra://" + getContainerIpAddress() + ":" + getMappedPort(THRIFT_PORT); + } + throw new UnsupportedOperationException(); + } + + /** + * Gets SQL query that can be used to verify the server is up. This query returns + * the server release version. + */ + @Override + protected String getTestQueryString() { + return "SELECT release_version FROM system.local"; + } + + /** + * Get mapped JQL port. + * + * @return + */ + public int getCqlPort() { + return getMappedPort(CQL_PORT); + } + + /** + * Get mapped Native (thrift-over-RPC) port. This is used by most JDBC driver implementations. + * + * @return thrift port, or -1 if NativeAPI is not enabled + */ + public int getNativePort() { + return enableNativeAPI ? getMappedPort(THRIFT_PORT) : -1; + } + + /** + * Get mapped JMX port. + * + * @return jmx port, or -1 if JMX is not enabled + */ + public int getJmxPort() { + return enableJmxReporting ? getMappedPort(JMX_PORT) : -1; + } + /** * Get configured Cluster * @@ -173,7 +270,15 @@ public static Cluster getCluster(ContainerState containerState) { return getCluster(containerState, false); } - private DatabaseDelegate getDatabaseDelegate() { + /** + * Close down server. This has no effect. + */ + @Override + public void close() { + // no-op + } + + protected DatabaseDelegate getDatabaseDelegate() { return new CassandraDatabaseDelegate(this); } } diff --git a/modules/cassandra/src/main/java/org/testcontainers/containers/CassandraContainerProvider.java b/modules/cassandra/src/main/java/org/testcontainers/containers/CassandraContainerProvider.java new file mode 100644 index 00000000000..3d3653f7002 --- /dev/null +++ b/modules/cassandra/src/main/java/org/testcontainers/containers/CassandraContainerProvider.java @@ -0,0 +1,24 @@ +package org.testcontainers.containers; + +import org.testcontainers.containers.JdbcDatabaseContainer; +import org.testcontainers.containers.JdbcDatabaseContainerProvider; + +/** + * Implementation of JdbcDatabaseContainerProvider for Cassandra. + */ +public class CassandraContainerProvider extends JdbcDatabaseContainerProvider { + @Override + public boolean supports(String databaseType) { + return databaseType.equals(CassandraContainer.NAME); + } + + @Override + public JdbcDatabaseContainer newInstance() { + return newInstance(CassandraContainer.DEFAULT_TAG); + } + + @Override + public JdbcDatabaseContainer newInstance(String tag) { + return new CassandraContainer(CassandraContainer.IMAGE + ":" + tag); + } +} diff --git a/modules/cassandra/src/main/java/org/testcontainers/containers/delegate/CassandraDatabaseDelegate.java b/modules/cassandra/src/main/java/org/testcontainers/containers/delegate/CassandraDatabaseDelegate.java index e55dd31b0ff..324812eb8d2 100644 --- a/modules/cassandra/src/main/java/org/testcontainers/containers/delegate/CassandraDatabaseDelegate.java +++ b/modules/cassandra/src/main/java/org/testcontainers/containers/delegate/CassandraDatabaseDelegate.java @@ -5,6 +5,7 @@ import com.datastax.driver.core.exceptions.DriverException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; + import org.testcontainers.containers.CassandraContainer; import org.testcontainers.containers.ContainerState; import org.testcontainers.delegate.AbstractDatabaseDelegate; diff --git a/modules/cassandra/src/test/java/org/testcontainers/containers/CassandraContainerTest.java b/modules/cassandra/src/test/java/org/testcontainers/containers/CassandraContainerTest.java index 6105aa275c9..080ab1f79d5 100644 --- a/modules/cassandra/src/test/java/org/testcontainers/containers/CassandraContainerTest.java +++ b/modules/cassandra/src/test/java/org/testcontainers/containers/CassandraContainerTest.java @@ -6,10 +6,19 @@ import com.datastax.driver.core.Session; import lombok.extern.slf4j.Slf4j; import org.junit.Test; +import org.testcontainers.containers.output.OutputFrame; import org.testcontainers.containers.wait.CassandraQueryWaitStrategy; +import org.testcontainers.containers.wait.strategy.HostPortWaitStrategy; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import java.time.Duration; import static org.junit.Assert.*; +import static java.time.temporal.ChronoUnit.SECONDS; + /** * @author Eugeny Karpov */ @@ -106,6 +115,23 @@ public void testCassandraGetCluster() { } } + @Test + public void testCassandraJdbcQuery() throws SQLException { + try (CassandraContainer cassandraContainer = new CassandraContainer<>().withNativeAPI(true)) { + cassandraContainer.withLogConsumer(obj -> System.out.println(((OutputFrame) obj).getUtf8String().trim())); + // this doesn't seem to have any effect + //cassandraContainer.setWaitStrategy(new HostPortWaitStrategy().withStartupTimeout(Duration.of(90, SECONDS))); + cassandraContainer.setWaitStrategy(new CassandraQueryWaitStrategy()); + cassandraContainer.start(); + try (Connection conn = cassandraContainer.createConnection(""); + Statement stmt = conn.createStatement(); + java.sql.ResultSet rs = stmt.executeQuery("SELECT release_version FROM system.local")) { + assertTrue("no records found", rs.next()); + assertEquals("Result set has no release_version", cassandraContainer.DEFAULT_TAG, rs.getString("release_version")); + } + } + } + private void testInitScript(CassandraContainer cassandraContainer) { ResultSet resultSet = performQuery(cassandraContainer, "SELECT * FROM keySpaceTest.catalog_category"); assertTrue("Query was not applied", resultSet.wasApplied());