Skip to content

Commit 05192e6

Browse files
inikolaevrnorth
authored andcommitted
Allow to start daemon database container (#360)
* Allow to start daemon database container * Add documentation for TC_DAEMON parameter * Add tests for TC_DAEMON parameter * Update changelog
1 parent 86070d3 commit 05192e6

File tree

4 files changed

+80
-1
lines changed

4 files changed

+80
-1
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# Change Log
22
All notable changes to this project will be documented in this file.
33

4+
## UNRELEASED
5+
### Changed
6+
- Added `TC_DAEMON` JDBC URL flag to prevent `ContainerDatabaseDriver` from shutting down containers at the time all connections are closed. (#359, #360)
7+
48
## [1.3.0] - 2017-06-05
59
### Fixed
610
- Improved container cleanup if startup failed (#336, #335)

docs/usage/database_containers.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,14 @@ public class JDBCDriverTest {
9797
...
9898
```
9999

100+
#### Running container in daemon mode
101+
102+
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:
103+
104+
`jdbc:tc:mysql://hostname/databasename?TC_DAEMON=true`
105+
106+
With this parameter database container will keep running even when there're no open connections.
107+
100108
#### Overriding MySQL my.cnf settings
101109
102110
For MySQL databases, it is possible to override configuration settings using resources on the classpath. Assuming `somepath/mysql_conf_override`
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package org.testcontainers.jdbc;
2+
3+
import org.junit.AfterClass;
4+
import org.junit.Test;
5+
import org.testcontainers.containers.JdbcDatabaseContainer;
6+
7+
import java.sql.Connection;
8+
import java.sql.DriverManager;
9+
import java.sql.SQLException;
10+
11+
import static org.rnorth.visibleassertions.VisibleAssertions.assertNotNull;
12+
import static org.rnorth.visibleassertions.VisibleAssertions.assertNull;
13+
import static org.rnorth.visibleassertions.VisibleAssertions.assertTrue;
14+
15+
/**
16+
* Created by inikolaev on 08/06/2017.
17+
*/
18+
public class DatabaseDriverShutdownTest {
19+
@AfterClass
20+
public static void testCleanup() {
21+
ContainerDatabaseDriver.killContainers();
22+
}
23+
24+
@Test
25+
public void shouldStopContainerWhenAllConnectionsClosed() throws SQLException {
26+
final String jdbcUrl = "jdbc:tc:postgresql://hostname/databasename";
27+
28+
getConnectionAndClose(jdbcUrl);
29+
30+
JdbcDatabaseContainer<?> container = ContainerDatabaseDriver.getContainer(jdbcUrl);
31+
assertNull("Database container instance is null as expected", container);
32+
}
33+
34+
@Test
35+
public void shouldNotStopDaemonContainerWhenAllConnectionsClosed() throws SQLException {
36+
final String jdbcUrl = "jdbc:tc:postgresql://hostname/databasename?TC_DAEMON=true";
37+
38+
getConnectionAndClose(jdbcUrl);
39+
40+
JdbcDatabaseContainer<?> container = ContainerDatabaseDriver.getContainer(jdbcUrl);
41+
assertNotNull("Database container instance is not null as expected", container);
42+
assertTrue("Database container is running as expected", container.isRunning());
43+
}
44+
45+
private void getConnectionAndClose(String jdbcUrl) throws SQLException {
46+
try (Connection connection = DriverManager.getConnection(jdbcUrl)) {
47+
assertNotNull("Obtained connection as expected", connection);
48+
}
49+
}
50+
}

modules/jdbc/src/main/java/org/testcontainers/jdbc/ContainerDatabaseDriver.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.testcontainers.jdbc;
22

3+
import com.google.common.annotations.VisibleForTesting;
34
import com.google.common.base.Charsets;
45
import com.google.common.io.Resources;
56
import org.slf4j.LoggerFactory;
@@ -37,6 +38,7 @@
3738
public class ContainerDatabaseDriver implements Driver {
3839

3940
private static final Pattern URL_MATCHING_PATTERN = Pattern.compile("jdbc:tc:([a-z]+)(:([^:]+))?://[^\\?]+(\\?.*)?");
41+
private static final Pattern DAEMON_MATCHING_PATTERN = Pattern.compile(".*([\\?&]?)TC_DAEMON=([^\\?&]+).*");
4042
private static final Pattern INITSCRIPT_MATCHING_PATTERN = Pattern.compile(".*([\\?&]?)TC_INITSCRIPT=([^\\?&]+).*");
4143
private static final Pattern INITFUNCTION_MATCHING_PATTERN = Pattern.compile(".*([\\?&]?)TC_INITFUNCTION=" +
4244
"((\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*\\.)*\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*)" +
@@ -186,6 +188,9 @@ private Map<String, String> getContainerParameters(String url) {
186188
* @return the connection, wrapped
187189
*/
188190
private Connection wrapConnection(final Connection connection, final JdbcDatabaseContainer container, final String url) {
191+
final Matcher matcher = DAEMON_MATCHING_PATTERN.matcher(url);
192+
final boolean isDaemon = matcher.matches() ? Boolean.parseBoolean(matcher.group(2)) : false;
193+
189194
Set<Connection> connections = containerConnections.get(container.getContainerId());
190195

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

200205
return new ConnectionWrapper(connection, () -> {
201206
finalConnections.remove(connection);
202-
if (finalConnections.isEmpty()) {
207+
if (!isDaemon && finalConnections.isEmpty()) {
203208
container.stop();
204209
jdbcUrlContainerCache.remove(url);
205210
}
@@ -313,4 +318,16 @@ public static void killContainer(String jdbcUrl) {
313318
}
314319
}
315320
}
321+
322+
/**
323+
* Utility method to get an instance of a database container given its JDBC URL.
324+
* @param jdbcUrl the JDBC URL of the container instance to get
325+
* @return an instance of database container or <code>null</code> if no container associated with JDBC URL
326+
*/
327+
@VisibleForTesting
328+
static JdbcDatabaseContainer getContainer(String jdbcUrl) {
329+
synchronized (jdbcUrlContainerCache) {
330+
return jdbcUrlContainerCache.get(jdbcUrl);
331+
}
332+
}
316333
}

0 commit comments

Comments
 (0)