diff --git a/quarkus/tests/integration/src/main/java/org/keycloak/it/utils/DockerKeycloakDistribution.java b/quarkus/tests/integration/src/main/java/org/keycloak/it/utils/DockerKeycloakDistribution.java index 8809adb26f..5f44a71b5e 100644 --- a/quarkus/tests/integration/src/main/java/org/keycloak/it/utils/DockerKeycloakDistribution.java +++ b/quarkus/tests/integration/src/main/java/org/keycloak/it/utils/DockerKeycloakDistribution.java @@ -6,12 +6,18 @@ import org.keycloak.common.Version; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.output.OutputFrame; import org.testcontainers.containers.output.ToStringConsumer; +import org.testcontainers.containers.wait.strategy.Wait; import org.testcontainers.images.builder.ImageFromDockerfile; +import org.testcontainers.shaded.com.google.common.util.concurrent.RateLimiter; +import org.testcontainers.utility.ResourceReaper; import java.io.File; import java.io.IOException; import java.net.URL; +import java.time.Duration; import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; public final class DockerKeycloakDistribution implements KeycloakDistribution { @@ -30,6 +36,14 @@ public final class DockerKeycloakDistribution implements KeycloakDistribution { private boolean dockerfileFetched = false; private GenericContainer keycloakContainer = null; + private String containerId = null; + + private Executor parallelReaperExecutor = Executors.newSingleThreadExecutor(); + + public DockerKeycloakDistribution(boolean debug, boolean manualStop, boolean reCreate) { + this.debug = debug; + this.manualStop = manualStop; + } private File createDockerCacheFile() { try { @@ -58,34 +72,36 @@ public final class DockerKeycloakDistribution implements KeycloakDistribution { } fetchDockerfile(); return new GenericContainer( - new ImageFromDockerfile() + new ImageFromDockerfile("keycloak.x-under-test", false) .withFileFromFile("keycloakx.tar.gz", distributionFile) .withFileFromFile("Dockerfile", cachedDockerfile) .withBuildArg("KEYCLOAK_DIST", "keycloakx.tar.gz") ) - .withExposedPorts(8080); - } - - public DockerKeycloakDistribution(boolean debug, boolean manualStop, boolean reCreate) { - this.debug = debug; - this.manualStop = manualStop; + .withExposedPorts(8080) + .withStartupAttempts(1) + .withStartupTimeout(Duration.ofSeconds(120)) + .waitingFor(Wait.forListeningPort()); } @Override public void start(List arguments) { + stop(); try { this.exitCode = -1; this.stdout = ""; this.stderr = ""; + this.containerId = null; this.backupConsumer = new ToStringConsumer(); - keycloakContainer = getKeycloakContainer(); keycloakContainer .withLogConsumer(backupConsumer) .withCommand(arguments.toArray(new String[0])) .start(); + containerId = keycloakContainer.getContainerId(); + + waitForStableOutput(); // TODO: this is based on a lot of assumptions io.restassured.RestAssured.port = keycloakContainer.getMappedPort(8080); @@ -93,15 +109,38 @@ public final class DockerKeycloakDistribution implements KeycloakDistribution { this.exitCode = -1; this.stdout = backupConsumer.toUtf8String(); this.stderr = backupConsumer.toUtf8String(); + cleanupContainer(); keycloakContainer = null; LOGGER.warn("Failed to start Keycloak container", cause); } } + // After the web server is responding we are still producing some logs that got checked in the tests + private void waitForStableOutput() { + String lastLine = ""; + boolean stableOutput = false; + while (!stableOutput) { + if (keycloakContainer.isRunning()) { + try { + Thread.sleep(200); + } catch (InterruptedException e) { + } + String[] splitted = keycloakContainer.getLogs().split("\n"); + String newLastLine = splitted[splitted.length - 1]; + + stableOutput = lastLine.equals(newLastLine); + lastLine = newLastLine; + } else { + stableOutput = true; + } + } + } + @Override public void stop() { try { if (keycloakContainer != null) { + containerId = keycloakContainer.getContainerId(); this.stdout = fetchOutputStream(); this.stderr = fetchErrorStream(); @@ -112,9 +151,34 @@ public final class DockerKeycloakDistribution implements KeycloakDistribution { this.exitCode = -1; throw new RuntimeException("Failed to stop the server", cause); } finally { + cleanupContainer(); keycloakContainer = null; } } + + private void cleanupContainer() { + if (containerId != null) { + try { + final String finalContainerId = containerId; + Runnable reaper = new Runnable() { + @Override + public void run() { + try { + ResourceReaper + .instance() + .stopAndRemoveContainer(finalContainerId); + } catch (Exception cause) { + throw new RuntimeException("Failed to stop and remove container", cause); + } + } + }; + parallelReaperExecutor.execute(reaper); + } catch (Exception cause) { + throw new RuntimeException("Failed to schecdule the removal of the container", cause); + } + } + } + private String fetchOutputStream() { if (keycloakContainer != null && keycloakContainer.isRunning()) { return keycloakContainer.getLogs(OutputFrame.OutputType.STDOUT); @@ -159,4 +223,5 @@ public final class DockerKeycloakDistribution implements KeycloakDistribution { public boolean isManualStop() { return this.manualStop; } + }