Skip to content

Commit

Permalink
Allow to start daemon database container (#360)
Browse files Browse the repository at this point in the history
* Allow to start daemon database container
* Add documentation for TC_DAEMON parameter
* Add tests for TC_DAEMON parameter
* Update changelog
  • Loading branch information
inikolaev authored and rnorth committed Jun 11, 2017
1 parent 86070d3 commit 05192e6
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 1 deletion.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Change Log
All notable changes to this project will be documented in this file.

## UNRELEASED
### Changed
- Added `TC_DAEMON` JDBC URL flag to prevent `ContainerDatabaseDriver` from shutting down containers at the time all connections are closed. (#359, #360)

## [1.3.0] - 2017-06-05
### Fixed
- Improved container cleanup if startup failed (#336, #335)
Expand Down
8 changes: 8 additions & 0 deletions docs/usage/database_containers.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,14 @@ public class JDBCDriverTest {
...
```

#### Running container in daemon mode

By default database container is being stopped as soon as last connection is closed. There are cases when you might need to start container and keep it running till you stop it explicitly or JVM is shutdown. To do this, add `TC_DAEMON` parameter to the URL as follows:

`jdbc:tc:mysql://hostname/databasename?TC_DAEMON=true`

With this parameter database container will keep running even when there're no open connections.
#### Overriding MySQL my.cnf settings
For MySQL databases, it is possible to override configuration settings using resources on the classpath. Assuming `somepath/mysql_conf_override`
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package org.testcontainers.jdbc;

import org.junit.AfterClass;
import org.junit.Test;
import org.testcontainers.containers.JdbcDatabaseContainer;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

import static org.rnorth.visibleassertions.VisibleAssertions.assertNotNull;
import static org.rnorth.visibleassertions.VisibleAssertions.assertNull;
import static org.rnorth.visibleassertions.VisibleAssertions.assertTrue;

/**
* Created by inikolaev on 08/06/2017.
*/
public class DatabaseDriverShutdownTest {
@AfterClass
public static void testCleanup() {
ContainerDatabaseDriver.killContainers();
}

@Test
public void shouldStopContainerWhenAllConnectionsClosed() throws SQLException {
final String jdbcUrl = "jdbc:tc:postgresql://hostname/databasename";

getConnectionAndClose(jdbcUrl);

JdbcDatabaseContainer<?> container = ContainerDatabaseDriver.getContainer(jdbcUrl);
assertNull("Database container instance is null as expected", container);
}

@Test
public void shouldNotStopDaemonContainerWhenAllConnectionsClosed() throws SQLException {
final String jdbcUrl = "jdbc:tc:postgresql://hostname/databasename?TC_DAEMON=true";

getConnectionAndClose(jdbcUrl);

JdbcDatabaseContainer<?> container = ContainerDatabaseDriver.getContainer(jdbcUrl);
assertNotNull("Database container instance is not null as expected", container);
assertTrue("Database container is running as expected", container.isRunning());
}

private void getConnectionAndClose(String jdbcUrl) throws SQLException {
try (Connection connection = DriverManager.getConnection(jdbcUrl)) {
assertNotNull("Obtained connection as expected", connection);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.testcontainers.jdbc;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Charsets;
import com.google.common.io.Resources;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -37,6 +38,7 @@
public class ContainerDatabaseDriver implements Driver {

private static final Pattern URL_MATCHING_PATTERN = Pattern.compile("jdbc:tc:([a-z]+)(:([^:]+))?://[^\\?]+(\\?.*)?");
private static final Pattern DAEMON_MATCHING_PATTERN = Pattern.compile(".*([\\?&]?)TC_DAEMON=([^\\?&]+).*");
private static final Pattern INITSCRIPT_MATCHING_PATTERN = Pattern.compile(".*([\\?&]?)TC_INITSCRIPT=([^\\?&]+).*");
private static final Pattern INITFUNCTION_MATCHING_PATTERN = Pattern.compile(".*([\\?&]?)TC_INITFUNCTION=" +
"((\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*\\.)*\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*)" +
Expand Down Expand Up @@ -186,6 +188,9 @@ private Map<String, String> getContainerParameters(String url) {
* @return the connection, wrapped
*/
private Connection wrapConnection(final Connection connection, final JdbcDatabaseContainer container, final String url) {
final Matcher matcher = DAEMON_MATCHING_PATTERN.matcher(url);
final boolean isDaemon = matcher.matches() ? Boolean.parseBoolean(matcher.group(2)) : false;

Set<Connection> connections = containerConnections.get(container.getContainerId());

if (connections == null) {
Expand All @@ -199,7 +204,7 @@ private Connection wrapConnection(final Connection connection, final JdbcDatabas

return new ConnectionWrapper(connection, () -> {
finalConnections.remove(connection);
if (finalConnections.isEmpty()) {
if (!isDaemon && finalConnections.isEmpty()) {
container.stop();
jdbcUrlContainerCache.remove(url);
}
Expand Down Expand Up @@ -313,4 +318,16 @@ public static void killContainer(String jdbcUrl) {
}
}
}

/**
* Utility method to get an instance of a database container given its JDBC URL.
* @param jdbcUrl the JDBC URL of the container instance to get
* @return an instance of database container or <code>null</code> if no container associated with JDBC URL
*/
@VisibleForTesting
static JdbcDatabaseContainer getContainer(String jdbcUrl) {
synchronized (jdbcUrlContainerCache) {
return jdbcUrlContainerCache.get(jdbcUrl);
}
}
}

0 comments on commit 05192e6

Please sign in to comment.