diff --git a/extensions/devservices/common/src/main/java/io/quarkus/devservices/common/Labels.java b/extensions/devservices/common/src/main/java/io/quarkus/devservices/common/Labels.java index dccf5f1b7d181..0a011741df7df 100644 --- a/extensions/devservices/common/src/main/java/io/quarkus/devservices/common/Labels.java +++ b/extensions/devservices/common/src/main/java/io/quarkus/devservices/common/Labels.java @@ -22,6 +22,7 @@ public final class Labels { public static final String COMPOSE_WAIT_FOR = QUARKUS_COMPOSE_PREFIX + ".wait_for"; public static final String COMPOSE_WAIT_FOR_LOGS = COMPOSE_WAIT_FOR + ".logs"; + public static final String COMPOSE_WAIT_FOR_LOGS_TIMEOUT = COMPOSE_WAIT_FOR_LOGS + ".timeout"; public static final String COMPOSE_WAIT_FOR_PORTS = COMPOSE_WAIT_FOR + ".ports"; public static final String COMPOSE_WAIT_FOR_PORTS_DISABLE = COMPOSE_WAIT_FOR_PORTS + ".disable"; public static final String COMPOSE_WAIT_FOR_PORTS_TIMEOUT = COMPOSE_WAIT_FOR_PORTS + ".timeout"; diff --git a/extensions/devservices/deployment/src/main/java/io/quarkus/devservices/deployment/compose/ComposeProject.java b/extensions/devservices/deployment/src/main/java/io/quarkus/devservices/deployment/compose/ComposeProject.java index c28eadc0e621a..7e935feca45c4 100644 --- a/extensions/devservices/deployment/src/main/java/io/quarkus/devservices/deployment/compose/ComposeProject.java +++ b/extensions/devservices/deployment/src/main/java/io/quarkus/devservices/deployment/compose/ComposeProject.java @@ -139,6 +139,9 @@ private void registerWaitStrategies(ComposeFiles composeFiles, // Add wait for log message if (e.getKey().startsWith(COMPOSE_WAIT_FOR_LOGS)) { int times = 1; + if (COMPOSE_WAIT_FOR_LOGS_TIMEOUT.equals(e.getKey())) { + continue; + } if (e.getKey().length() > COMPOSE_WAIT_FOR_LOGS.length()) { try { times = Integer.parseInt(e.getKey().replace(COMPOSE_WAIT_FOR_LOGS + ".", "")); @@ -146,7 +149,10 @@ private void registerWaitStrategies(ComposeFiles composeFiles, LOG.warnv("Cannot parse label `{}`", e.getKey()); } } - addWaitStrategy(waitStrategies, serviceName, Wait.forLogMessage((String) e.getValue(), times)); + String waitForTimeout = (String) labels.get(COMPOSE_WAIT_FOR_LOGS_TIMEOUT); + Duration timeout = waitForTimeout != null ? Duration.parse("PT" + waitForTimeout) : startupTimeout; + addWaitStrategy(waitStrategies, serviceName, Wait.forLogMessage((String) e.getValue(), times) + .withStartupTimeout(timeout)); } } // Add wait for port availability diff --git a/extensions/devservices/deployment/src/test/java/io/quarkus/devservices/deployment/compose/ComposeFileTest.java b/extensions/devservices/deployment/src/test/java/io/quarkus/devservices/deployment/compose/ComposeFileTest.java index 81c0b9da4e4e2..fce94ea10d8f4 100644 --- a/extensions/devservices/deployment/src/test/java/io/quarkus/devservices/deployment/compose/ComposeFileTest.java +++ b/extensions/devservices/deployment/src/test/java/io/quarkus/devservices/deployment/compose/ComposeFileTest.java @@ -42,7 +42,7 @@ void testValidComposeFileParsing() { assertNotNull(db); assertEquals("db", db.getServiceName()); assertEquals("database system is ready to accept connections", - db.getLabels().get("io.quarkus.devservices.compose.wait_for.logs")); + db.getLabels().get("io.quarkus.devservices.compose.wait_for.logs.2")); assertFalse(db.hasHealthCheck()); // Test Redis service @@ -91,7 +91,7 @@ void testServiceDefinitionExtraction() { // Test labels Map labels = db.getLabels(); assertEquals("database system is ready to accept connections", - labels.get("io.quarkus.devservices.compose.wait_for.logs")); + labels.get("io.quarkus.devservices.compose.wait_for.logs.2")); // Test container name (should be null for valid compose) assertNull(db.getContainerName()); diff --git a/extensions/devservices/deployment/src/test/java/io/quarkus/devservices/deployment/compose/ComposeProjectTest.java b/extensions/devservices/deployment/src/test/java/io/quarkus/devservices/deployment/compose/ComposeProjectTest.java index f76f839890083..b133d1451e297 100644 --- a/extensions/devservices/deployment/src/test/java/io/quarkus/devservices/deployment/compose/ComposeProjectTest.java +++ b/extensions/devservices/deployment/src/test/java/io/quarkus/devservices/deployment/compose/ComposeProjectTest.java @@ -6,12 +6,16 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.File; +import java.lang.reflect.Field; import java.time.Duration; +import java.time.temporal.ChronoUnit; import java.util.List; import java.util.Map; import org.junit.jupiter.api.Test; +import org.testcontainers.containers.wait.strategy.AbstractWaitStrategy; import org.testcontainers.containers.wait.strategy.WaitAllStrategy; +import org.testcontainers.containers.wait.strategy.WaitStrategy; public class ComposeProjectTest { @@ -36,11 +40,45 @@ void testBasicProject() { assertTrue(waitStrategies.containsKey("db")); assertTrue(waitStrategies.containsKey("redis")); + // check wait strategy startup times + WaitAllStrategy db = waitStrategies.get("db"); + List strategies = getChildStrategies(db); + assertEquals(1, strategies.size()); + Duration startupTimeout = getStartupTimeout(strategies.get(0)); + assertEquals(Duration.of(10, ChronoUnit.SECONDS), startupTimeout); + + WaitAllStrategy redis = waitStrategies.get("redis"); + strategies = getChildStrategies(redis); + assertEquals(2, strategies.size()); + for (WaitStrategy strategy : strategies) { + assertEquals(Duration.of(2, ChronoUnit.MINUTES), getStartupTimeout(strategy)); + } + // Verify project name String project = composeProject.getProject(); assertEquals("test", project); } + private static Duration getStartupTimeout(WaitStrategy waitStrategy) { + try { + Field startupTimeout = AbstractWaitStrategy.class.getDeclaredField("startupTimeout"); + startupTimeout.setAccessible(true); + return (Duration) startupTimeout.get(waitStrategy); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + private static List getChildStrategies(WaitAllStrategy db) { + try { + Field strategies = WaitAllStrategy.class.getDeclaredField("strategies"); + strategies.setAccessible(true); + return (List) strategies.get(db); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } + @Test void testProjectWithWaitStrategies() { ComposeFiles files = new ComposeFiles(List.of(composeFileWithProfiles)); diff --git a/extensions/devservices/deployment/src/test/resources/valid-compose.yml b/extensions/devservices/deployment/src/test/resources/valid-compose.yml index 4baa0656ee158..27aa74366443c 100644 --- a/extensions/devservices/deployment/src/test/resources/valid-compose.yml +++ b/extensions/devservices/deployment/src/test/resources/valid-compose.yml @@ -7,12 +7,16 @@ services: POSTGRES_PASSWORD: postgres POSTGRES_USER: postgres labels: - io.quarkus.devservices.compose.wait_for.logs: "database system is ready to accept connections" + io.quarkus.devservices.compose.wait_for.logs.2: "database system is ready to accept connections" + io.quarkus.devservices.compose.wait_for.logs.timeout: 10s + io.quarkus.devservices.compose.wait_for.ports.disable: true redis: image: redis:6 ports: - "6379:6379" + labels: + io.quarkus.devservices.compose.wait_for.logs: "The server is now ready to accept connections" kafka: profiles: