From 060581d0784596bdbf14a89612c2cf12fb4de06e Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Wed, 17 May 2023 14:19:18 -0700 Subject: [PATCH] Remove pluggable docker compose ReadinessCheck Remove pluggable `ReadinessCheck` interface and only use the `TcpConnectServiceReadinessCheck` implementation for now. We may re-introduce pluggable checks in a future version. See gh-35544 --- .../readiness/ServiceNotReadyException.java | 10 +--- .../readiness/ServiceReadinessCheck.java | 47 ------------------- .../readiness/ServiceReadinessChecks.java | 32 +++++-------- .../TcpConnectServiceReadinessCheck.java | 8 ++-- .../ServiceReadinessChecksTests.java | 44 ++++------------- .../asciidoc/features/docker-compose.adoc | 2 - 6 files changed, 26 insertions(+), 117 deletions(-) delete mode 100644 spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/readiness/ServiceReadinessCheck.java diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/readiness/ServiceNotReadyException.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/readiness/ServiceNotReadyException.java index af7cc4f22b..9bfaca006d 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/readiness/ServiceNotReadyException.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/readiness/ServiceNotReadyException.java @@ -24,10 +24,8 @@ import org.springframework.boot.docker.compose.core.RunningService; * @author Moritz Halbritter * @author Andy Wilkinson * @author Phillip Webb - * @since 3.1.0 - * @see ServiceReadinessCheck */ -public class ServiceNotReadyException extends RuntimeException { +class ServiceNotReadyException extends RuntimeException { private final RunningService service; @@ -40,11 +38,7 @@ public class ServiceNotReadyException extends RuntimeException { this.service = service; } - /** - * Return the service that was not ready. - * @return the non-ready service - */ - public RunningService getService() { + RunningService getService() { return this.service; } diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/readiness/ServiceReadinessCheck.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/readiness/ServiceReadinessCheck.java deleted file mode 100644 index 0f4c9d2f62..0000000000 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/readiness/ServiceReadinessCheck.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.docker.compose.readiness; - -import org.springframework.boot.context.properties.bind.Binder; -import org.springframework.boot.docker.compose.core.RunningService; -import org.springframework.core.env.Environment; - -/** - * Strategy used to check if a {@link RunningService} is ready. Implementations may be - * registered in {@code spring.factories}. The following constructor arguments types are - * supported: - * - * - * @author Moritz Halbritter - * @author Andy Wilkinson - * @author Phillip Webb - * @since 3.1.0 - */ -public interface ServiceReadinessCheck { - - /** - * Checks whether the given {@code service} is ready. - * @param service service to check - * @throws ServiceNotReadyException if the service is not ready - */ - void check(RunningService service) throws ServiceNotReadyException; - -} diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/readiness/ServiceReadinessChecks.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/readiness/ServiceReadinessChecks.java index 4d1ea22526..c75605c1df 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/readiness/ServiceReadinessChecks.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/readiness/ServiceReadinessChecks.java @@ -32,12 +32,10 @@ import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.docker.compose.core.RunningService; import org.springframework.core.env.Environment; import org.springframework.core.io.support.SpringFactoriesLoader; -import org.springframework.core.io.support.SpringFactoriesLoader.ArgumentResolver; import org.springframework.core.log.LogMessage; /** - * A collection of {@link ServiceReadinessCheck} instances that can be used to - * {@link #wait() wait} for {@link RunningService services} to be ready. + * Utility used to {@link #wait() wait} for {@link RunningService services} to be ready. * * @author Moritz Halbritter * @author Andy Wilkinson @@ -58,7 +56,7 @@ public class ServiceReadinessChecks { private final ReadinessProperties properties; - private final List checks; + private final TcpConnectServiceReadinessCheck check; public ServiceReadinessChecks(ClassLoader classLoader, Environment environment, Binder binder) { this(Clock.systemUTC(), ServiceReadinessChecks::sleep, @@ -68,15 +66,11 @@ public class ServiceReadinessChecks { ServiceReadinessChecks(Clock clock, Consumer sleep, SpringFactoriesLoader loader, ClassLoader classLoader, Environment environment, Binder binder, - Function tcpCheckFactory) { - ArgumentResolver argumentResolver = ArgumentResolver.of(ClassLoader.class, classLoader) - .and(Environment.class, environment) - .and(Binder.class, binder); + Function tcpCheckFactory) { this.clock = clock; this.sleep = sleep; this.properties = ReadinessProperties.get(binder); - this.checks = new ArrayList<>(loader.load(ServiceReadinessCheck.class, argumentResolver)); - this.checks.add(tcpCheckFactory.apply(this.properties.getTcp())); + this.check = tcpCheckFactory.apply(this.properties.getTcp()); } /** @@ -106,16 +100,14 @@ public class ServiceReadinessChecks { continue; } logger.trace(LogMessage.format("Checking readiness of service '%s'", service)); - for (ServiceReadinessCheck check : this.checks) { - try { - check.check(service); - logger.trace(LogMessage.format("Service '%s' is ready", service)); - } - catch (ServiceNotReadyException ex) { - logger.trace(LogMessage.format("Service '%s' is not ready", service), ex); - exceptions = (exceptions != null) ? exceptions : new ArrayList<>(); - exceptions.add(ex); - } + try { + this.check.check(service); + logger.trace(LogMessage.format("Service '%s' is ready", service)); + } + catch (ServiceNotReadyException ex) { + logger.trace(LogMessage.format("Service '%s' is not ready", service), ex); + exceptions = (exceptions != null) ? exceptions : new ArrayList<>(); + exceptions.add(ex); } } return (exceptions != null) ? exceptions : Collections.emptyList(); diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/readiness/TcpConnectServiceReadinessCheck.java b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/readiness/TcpConnectServiceReadinessCheck.java index c68e088a2a..38da8e0608 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/readiness/TcpConnectServiceReadinessCheck.java +++ b/spring-boot-project/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/readiness/TcpConnectServiceReadinessCheck.java @@ -24,14 +24,13 @@ import java.net.SocketTimeoutException; import org.springframework.boot.docker.compose.core.RunningService; /** - * Default {@link ServiceReadinessCheck} that checks readiness by connecting to the - * exposed TCP ports. + * Checks readiness by connecting to the exposed TCP ports. * * @author Moritz Halbritter * @author Andy Wilkinson * @author Phillip Webb */ -class TcpConnectServiceReadinessCheck implements ServiceReadinessCheck { +class TcpConnectServiceReadinessCheck { private static final String DISABLE_LABEL = "org.springframework.boot.readiness-check.tcp.disable"; @@ -41,8 +40,7 @@ class TcpConnectServiceReadinessCheck implements ServiceReadinessCheck { this.properties = properties; } - @Override - public void check(RunningService service) { + void check(RunningService service) { if (service.labels().containsKey(DISABLE_LABEL)) { return; } diff --git a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/readiness/ServiceReadinessChecksTests.java b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/readiness/ServiceReadinessChecksTests.java index 237aefbf82..e3d04f42f2 100644 --- a/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/readiness/ServiceReadinessChecksTests.java +++ b/spring-boot-project/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/readiness/ServiceReadinessChecksTests.java @@ -25,22 +25,16 @@ import java.util.Map; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.ArgumentMatchers; import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.docker.compose.core.RunningService; -import org.springframework.core.env.Environment; -import org.springframework.core.io.support.SpringFactoriesLoader.ArgumentResolver; import org.springframework.core.test.io.support.MockSpringFactoriesLoader; import org.springframework.mock.env.MockEnvironment; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.then; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; /** * Tests for {@link ServiceReadinessChecks}. @@ -67,8 +61,6 @@ class ServiceReadinessChecksTests { private List runningServices; - private final MockServiceReadinessCheck mockTcpCheck = new MockServiceReadinessCheck(); - @BeforeEach void setup() { this.clock = mock(Clock.class); @@ -81,42 +73,25 @@ class ServiceReadinessChecksTests { this.runningServices = List.of(this.runningService); } - @Test - void loadCanResolveArguments() { - this.loader = spy(MockSpringFactoriesLoader.class); - createChecks(); - then(this.loader).should() - .load(eq(ServiceReadinessCheck.class), ArgumentMatchers.assertArg((argumentResolver) -> { - assertThat(argumentResolver.resolve(ClassLoader.class)).isEqualTo(this.classLoader); - assertThat(argumentResolver.resolve(Environment.class)).isEqualTo(this.environment); - assertThat(argumentResolver.resolve(Binder.class)).isEqualTo(this.binder); - })); - } - @Test void waitUntilReadyWhenImmediatelyReady() { MockServiceReadinessCheck check = new MockServiceReadinessCheck(); - this.loader.addInstance(ServiceReadinessCheck.class, check); - createChecks().waitUntilReady(this.runningServices); + createChecks(check).waitUntilReady(this.runningServices); assertThat(check.getChecked()).contains(this.runningService); - assertThat(this.mockTcpCheck.getChecked()).contains(this.runningService); } @Test void waitUntilReadyWhenTakesTimeToBeReady() { MockServiceReadinessCheck check = new MockServiceReadinessCheck(2); - this.loader.addInstance(ServiceReadinessCheck.class, check); - createChecks().waitUntilReady(this.runningServices); + createChecks(check).waitUntilReady(this.runningServices); assertThat(check.getChecked()).hasSize(2).contains(this.runningService); - assertThat(this.mockTcpCheck.getChecked()).contains(this.runningService); } @Test void waitUntilReadyWhenTimeout() { MockServiceReadinessCheck check = new MockServiceReadinessCheck(Integer.MAX_VALUE); - this.loader.addInstance(ServiceReadinessCheck.class, check); assertThatExceptionOfType(ReadinessTimeoutException.class) - .isThrownBy(() -> createChecks().waitUntilReady(this.runningServices)) + .isThrownBy(() -> createChecks(check).waitUntilReady(this.runningServices)) .satisfies((ex) -> assertThat(ex.getSuppressed()).hasSize(1)); assertThat(check.getChecked()).hasSizeGreaterThan(10); } @@ -125,25 +100,23 @@ class ServiceReadinessChecksTests { void waitForWhenServiceHasDisableLabelDoesNotCheck() { given(this.runningService.labels()).willReturn(Map.of("org.springframework.boot.readiness-check.disable", "")); MockServiceReadinessCheck check = new MockServiceReadinessCheck(); - this.loader.addInstance(ServiceReadinessCheck.class, check); - createChecks().waitUntilReady(this.runningServices); + createChecks(check).waitUntilReady(this.runningServices); assertThat(check.getChecked()).isEmpty(); - assertThat(this.mockTcpCheck.getChecked()).isEmpty(); } void sleep(Duration duration) { this.now = this.now.plus(duration); } - private ServiceReadinessChecks createChecks() { + private ServiceReadinessChecks createChecks(TcpConnectServiceReadinessCheck check) { return new ServiceReadinessChecks(this.clock, this::sleep, this.loader, this.classLoader, this.environment, - this.binder, (properties) -> this.mockTcpCheck); + this.binder, (properties) -> check); } /** - * Mock {@link ServiceReadinessCheck}. + * Mock {@link TcpConnectServiceReadinessCheck}. */ - static class MockServiceReadinessCheck implements ServiceReadinessCheck { + static class MockServiceReadinessCheck extends TcpConnectServiceReadinessCheck { private final Integer failUntil; @@ -154,6 +127,7 @@ class ServiceReadinessChecksTests { } MockServiceReadinessCheck(Integer failUntil) { + super(null); this.failUntil = failUntil; } diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/docker-compose.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/docker-compose.adoc index db2b531899..6621b51576 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/docker-compose.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/docker-compose.adoc @@ -184,8 +184,6 @@ You can also change timeout values in your `application.properties` or `applicat The overall timeout can be configured using configprop:spring.docker.compose.readiness.timeout[]. -TIP: You can also provide your own `ServiceReadinessCheck` implementations and register them in the `spring.factories` file. - [[features.docker-compose.lifecycle]]