From f8f561a435c8cc46cbb0b84d692e73428650c049 Mon Sep 17 00:00:00 2001 From: Ryan Emerson Date: Fri, 4 Jul 2025 10:12:51 +0100 Subject: [PATCH] Check cluster is correctly formed in ClusteredKeycloakServer Closes #40858 Signed-off-by: Ryan Emerson Co-authored-by: Pedro Ruivo --- .../it/utils/DockerKeycloakDistribution.java | 4 ++- .../server/ClusteredKeycloakServer.java | 35 ++++++++++++++----- .../AbstractContainerTestDatabase.java | 1 + .../JBossLogConsumer.java | 2 +- 4 files changed, 32 insertions(+), 10 deletions(-) rename test-framework/core/src/main/java/org/keycloak/testframework/{database => logging}/JBossLogConsumer.java (94%) diff --git a/quarkus/tests/junit5/src/main/java/org/keycloak/it/utils/DockerKeycloakDistribution.java b/quarkus/tests/junit5/src/main/java/org/keycloak/it/utils/DockerKeycloakDistribution.java index 1c41c1ac2a0..c5793a7d6c6 100644 --- a/quarkus/tests/junit5/src/main/java/org/keycloak/it/utils/DockerKeycloakDistribution.java +++ b/quarkus/tests/junit5/src/main/java/org/keycloak/it/utils/DockerKeycloakDistribution.java @@ -56,6 +56,8 @@ public final class DockerKeycloakDistribution implements KeycloakDistribution { private static final Logger LOGGER = Logger.getLogger(DockerKeycloakDistribution.class); + public static final int STARTUP_TIMEOUT_SECONDS = 120; + private final boolean debug; private final boolean manualStop; private final int requestPort; @@ -102,7 +104,7 @@ public final class DockerKeycloakDistribution implements KeycloakDistribution { .withEnv(envVars) .withExposedPorts(exposedPorts) .withStartupAttempts(1) - .withStartupTimeout(Duration.ofSeconds(120)) + .withStartupTimeout(Duration.ofSeconds(STARTUP_TIMEOUT_SECONDS)) .waitingFor(Wait.forListeningPorts(8080)); } diff --git a/test-framework/clustering/src/main/java/org/keycloak/testframework/server/ClusteredKeycloakServer.java b/test-framework/clustering/src/main/java/org/keycloak/testframework/server/ClusteredKeycloakServer.java index 47c8cffc960..dbca21e6fa6 100644 --- a/test-framework/clustering/src/main/java/org/keycloak/testframework/server/ClusteredKeycloakServer.java +++ b/test-framework/clustering/src/main/java/org/keycloak/testframework/server/ClusteredKeycloakServer.java @@ -19,17 +19,21 @@ package org.keycloak.testframework.server; import java.util.Arrays; import java.util.Objects; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import org.infinispan.server.test.core.CountdownLatchLoggingConsumer; import org.jboss.logging.Logger; import org.keycloak.it.utils.DockerKeycloakDistribution; import org.keycloak.testframework.clustering.LoadBalancer; -import org.keycloak.testframework.database.JBossLogConsumer; +import org.keycloak.testframework.logging.JBossLogConsumer; import org.testcontainers.images.RemoteDockerImage; import org.testcontainers.utility.DockerImageName; import org.testcontainers.utility.LazyFuture; public class ClusteredKeycloakServer implements KeycloakServer { + private static final String CLUSTER_VIEW_REGEX = ".*ISPN000093.*(?<=\\()(%1$d)(?=\\)).*|.*ISPN000094.*(?<=\\()(%1$d)(?=\\)).*"; private static final boolean MANUAL_STOP = true; private static final int REQUEST_PORT = 8080; private static final int MANAGEMENT_PORT = 9000; @@ -49,15 +53,26 @@ public class ClusteredKeycloakServer implements KeycloakServer { @Override public void start(KeycloakServerConfigBuilder configBuilder) { + int numServers = containers.length; + CountdownLatchLoggingConsumer clusterLatch = new CountdownLatchLoggingConsumer(numServers, String.format(CLUSTER_VIEW_REGEX, numServers)); String[] imagePeServer = null; if (images == null || images.isEmpty() || (imagePeServer = images.split(",")).length == 1) { - startContainersWithSameImage(configBuilder, imagePeServer == null ? SNAPSHOT_IMAGE : imagePeServer[0]); + startContainersWithSameImage(configBuilder, imagePeServer == null ? SNAPSHOT_IMAGE : imagePeServer[0], clusterLatch); } else { - startContainersWithMixedImage(configBuilder, imagePeServer); + startContainersWithMixedImage(configBuilder, imagePeServer, clusterLatch); + } + + try { + clusterLatch.await((long) numServers * DockerKeycloakDistribution.STARTUP_TIMEOUT_SECONDS, TimeUnit.SECONDS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException(e); + } catch (TimeoutException e) { + throw new RuntimeException("Expected %d cluster members".formatted(numServers), e); } } - private void startContainersWithMixedImage(KeycloakServerConfigBuilder configBuilder, String[] imagePeServer) { + private void startContainersWithMixedImage(KeycloakServerConfigBuilder configBuilder, String[] imagePeServer, CountdownLatchLoggingConsumer clusterLatch) { assert imagePeServer != null; if (containers.length != imagePeServer.length) { throw new IllegalArgumentException("The number of containers and the number of images must match"); @@ -79,12 +94,12 @@ public class ClusteredKeycloakServer implements KeycloakServer { copyProvidersAndConfigs(container, configBuilder); - container.setCustomLogConsumer(new JBossLogConsumer(Logger.getLogger("managed.keycloak." + i))); + configureLogConsumers(container, i, clusterLatch); container.run(configBuilder.toArgs()); } } - private void startContainersWithSameImage(KeycloakServerConfigBuilder configBuilder, String image) { + private void startContainersWithSameImage(KeycloakServerConfigBuilder configBuilder, String image, CountdownLatchLoggingConsumer clusterLatch) { int[] exposedPorts = new int[]{REQUEST_PORT, MANAGEMENT_PORT}; LazyFuture imageFuture = image == null || SNAPSHOT_IMAGE.equals(image) ? defaultImage() : @@ -94,12 +109,16 @@ public class ClusteredKeycloakServer implements KeycloakServer { containers[i] = container; copyProvidersAndConfigs(container, configBuilder); - - container.setCustomLogConsumer(new JBossLogConsumer(Logger.getLogger("managed.keycloak." + i))); + configureLogConsumers(container, i, clusterLatch); container.run(configBuilder.toArgs()); } } + private static void configureLogConsumers(DockerKeycloakDistribution container, int index, CountdownLatchLoggingConsumer clusterLatch) { + var logger = new JBossLogConsumer(Logger.getLogger("managed.keycloak." + index)); + container.setCustomLogConsumer(logger.andThen(clusterLatch)); + } + private void copyProvidersAndConfigs(DockerKeycloakDistribution container, KeycloakServerConfigBuilder configBuilder) { for (var dependency : configBuilder.toDependencies()) { container.copyProvider(dependency.getGroupId(), dependency.getArtifactId()); diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/database/AbstractContainerTestDatabase.java b/test-framework/core/src/main/java/org/keycloak/testframework/database/AbstractContainerTestDatabase.java index 37024022e6f..c8bc957566d 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/database/AbstractContainerTestDatabase.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/database/AbstractContainerTestDatabase.java @@ -2,6 +2,7 @@ package org.keycloak.testframework.database; import org.jboss.logging.Logger; import org.keycloak.testframework.config.Config; +import org.keycloak.testframework.logging.JBossLogConsumer; import org.testcontainers.containers.JdbcDatabaseContainer; import java.io.IOException; diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/database/JBossLogConsumer.java b/test-framework/core/src/main/java/org/keycloak/testframework/logging/JBossLogConsumer.java similarity index 94% rename from test-framework/core/src/main/java/org/keycloak/testframework/database/JBossLogConsumer.java rename to test-framework/core/src/main/java/org/keycloak/testframework/logging/JBossLogConsumer.java index 9d35ad99709..12ea46ebfa3 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/database/JBossLogConsumer.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/logging/JBossLogConsumer.java @@ -1,4 +1,4 @@ -package org.keycloak.testframework.database; +package org.keycloak.testframework.logging; import org.jboss.logging.Logger; import org.testcontainers.containers.output.OutputFrame;