With this setup:
spring:
r2dbc:
url: r2dbc:tc:postgresql:///test?TC_IMAGE_TAG=12-alpine
flyway:
url: jdbc:tc:postgresql:12-alpine:///test
a first container (created for migrations) gets down right after successful migration and a new one gets up for testing. One can see this from this log (when one constantly runs docker ps
):
~ 17:50:33
❯ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
953fc83fc678 testcontainers/ryuk:0.3.1 "/app" 1 second ago Up Less than a second 0.0.0.0:61792->8080/tcp testcontainers-ryuk-93520c73-9205-4b22-8ff2-83a1edbf980a
~ 17:50:37
❯ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
818232ec554d postgres:12-alpine "docker-entrypoint.s…" 1 second ago Up Less than a second 0.0.0.0:61802->5432/tcp intelligent_mirzakhani
953fc83fc678 testcontainers/ryuk:0.3.1 "/app" 2 seconds ago Up 1 second 0.0.0.0:61792->8080/tcp testcontainers-ryuk-93520c73-9205-4b22-8ff2-83a1edbf980a
~ 17:50:38
❯ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1fff5f400c40 postgres:12-alpine "docker-entrypoint.s…" Less than a second ago Up Less than a second 0.0.0.0:61812->5432/tcp festive_mcclintock
953fc83fc678 testcontainers/ryuk:0.3.1 "/app" 3 seconds ago Up 2 seconds 0.0.0.0:61792->8080/tcp testcontainers-ryuk-93520c73-9205-4b22-8ff2-83a1edbf980a
~ 17:50:39
❯ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1fff5f400c40 postgres:12-alpine "docker-entrypoint.s…" 5 seconds ago Up 5 seconds 0.0.0.0:61812->5432/tcp festive_mcclintock
953fc83fc678 testcontainers/ryuk:0.3.1 "/app" 8 seconds ago Up 7 seconds 0.0.0.0:61792->8080/tcp testcontainers-ryuk-93520c73-9205-4b22-8ff2-83a1edbf980a
i.e. 818232ec554d -> 1fff5f400c40.
The same thing happens with in-mem H2 db. But after adding DB_CLOSE_DELAY=-1 parameter the issue gets resolved:
spring:
r2dbc:
url: r2dbc:h2:mem:///test;MODE=PostgreSQL
flyway:
url: jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;MODE=PostgreSQL
Sorry if this should had been ticketed in flyway's repo.
build.gradle.kts:
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
buildscript {
dependencies {
classpath("com.google.cloud.tools:jib-spring-boot-extension-gradle:0.1.0")
}
}
plugins {
id("org.springframework.boot") version "2.5.4"
id("io.spring.dependency-management") version "1.0.11.RELEASE"
id("com.google.cloud.tools.jib") version "3.1.2"
id("org.flywaydb.flyway") version "7.5.4"
kotlin("jvm") version "1.5.21"
kotlin("plugin.spring") version "1.5.21"
}
group = "kz.qbi"
version = System.getenv("CELLS_VERSION")
java.sourceCompatibility = JavaVersion.VERSION_11
repositories {
mavenCentral()
}
extra["testcontainersVersion"] = "1.16.0"
dependencies {
implementation("org.springframework.boot:spring-boot-starter-webflux")
implementation("org.springframework.boot:spring-boot-starter-data-r2dbc")
implementation("org.springframework.boot:spring-boot-starter-validation")
implementation("org.springframework:spring-jdbc")
implementation("io.projectreactor.kotlin:reactor-kotlin-extensions")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")
implementation("org.flywaydb:flyway-core")
runtimeOnly("org.postgresql:postgresql")
runtimeOnly("io.r2dbc:r2dbc-postgresql")
developmentOnly("org.springframework.boot:spring-boot-devtools")
testImplementation("org.springframework.boot:spring-boot-starter-test") {
exclude(module = "mockito-core")
}
testImplementation("io.projectreactor:reactor-test")
testImplementation("org.testcontainers:junit-jupiter")
testImplementation("org.testcontainers:postgresql")
testImplementation("org.testcontainers:r2dbc")
testImplementation("com.ninja-squad:springmockk:3.0.1")
testRuntimeOnly("com.h2database:h2")
testRuntimeOnly("io.r2dbc:r2dbc-h2")
}
dependencyManagement {
imports {
mavenBom("org.testcontainers:testcontainers-bom:${property("testcontainersVersion")}")
}
}
jib {
from {
image = "***"
}
to {
image = "***/cells"
tags = setOf("$version")
}
container {
creationTime = "USE_CURRENT_TIMESTAMP"
user = "***"
}
pluginExtensions {
pluginExtension {
implementation = "com.google.cloud.tools.jib.gradle.extension.springboot.JibSpringBootExtension"
}
}
}
tasks.withType<KotlinCompile> {
kotlinOptions {
@Suppress("SpellCheckingInspection")
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "11"
}
}
tasks.withType<Test> {
useJUnitPlatform()
failFast = true
}
tasks {
build {
dependsOn(jib)
}
}
Comment From: wilkinsona
Thanks for the report but I don't think there's anything we can do about this in Spring Boot.
Flyway closes the connection when it has finished the migration. This is the right thing for it to do to avoid leaking a connection. When using Testcontainers this causes its JDBC driver to stop the container. You can control this behaviour by adding TC_DAEMON=true
to the jdbc URL:
jdbc:tc:postgresql:12-alpine:///test?TC_DAEMON=true
This will leave the container running. Unfortunately, this isn't sufficient to cause the container to be reused when connecting using R2DBC.
Unless I've missed something, Testcontainers doesn't support container reuse across its R2DBC and JDBC integration. In other words, it doesn't see r2dbc:tc:postgresql:///test?TC_IMAGE_TAG=12-alpine
and jdbc:tc:postgresql:12-alpine:///test
as referring to the same database. This means that setting TC_DAEMON
only results in two containers running at the same time rather than the two running one after the other.
If you'd like to pursue this, I'd recommend raising it with the Testcontainers team to see if they could enable container reuse across r2dbc:tc
and jdbc:tc
URLs.
/cc @bsideup
Comment From: yerzhant
Thank you for clarification.