Skip to content

Commit

Permalink
Allow docker compose service readiness checks to be bypassed
Browse files Browse the repository at this point in the history
Add `spring.docker.compose.readiness.wait` property that can be used to
determine how Spring Boot should wait for docker compose services to
become ready.

Fixes gh-35545
  • Loading branch information
philwebb committed May 17, 2023
1 parent d018aa8 commit a8602a1
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.springframework.boot.docker.compose.core.DockerCompose;
import org.springframework.boot.docker.compose.core.DockerComposeFile;
import org.springframework.boot.docker.compose.core.RunningService;
import org.springframework.boot.docker.compose.lifecycle.DockerComposeProperties.Readiness.Wait;
import org.springframework.boot.docker.compose.lifecycle.DockerComposeProperties.Start;
import org.springframework.boot.docker.compose.lifecycle.DockerComposeProperties.Stop;
import org.springframework.context.ApplicationContext;
Expand Down Expand Up @@ -110,15 +111,19 @@ void start() {
LifecycleManagement lifecycleManagement = this.properties.getLifecycleManagement();
Start start = this.properties.getStart();
Stop stop = this.properties.getStop();
Wait wait = this.properties.getReadiness().getWait();
if (lifecycleManagement.shouldStart() && !dockerCompose.hasRunningServices()) {
start.getCommand().applyTo(dockerCompose, start.getLogLevel());
wait = (wait != Wait.ONLY_IF_STARTED) ? wait : Wait.ALWAYS;
if (lifecycleManagement.shouldStop()) {
this.shutdownHandlers.add(() -> stop.getCommand().applyTo(dockerCompose, stop.getTimeout()));
}
}
List<RunningService> runningServices = new ArrayList<>(dockerCompose.getRunningServices());
runningServices.removeIf(this::isIgnored);
this.serviceReadinessChecks.waitUntilReady(runningServices);
if (wait == Wait.ALWAYS || wait == null) {
this.serviceReadinessChecks.waitUntilReady(runningServices);
}
publishEvent(new DockerComposeServicesReadyEvent(this.applicationContext, runningServices));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,11 @@ public void setInTests(boolean inTests) {
*/
public static class Readiness {

/**
* Wait strategy to use.
*/
private Wait wait = Wait.ALWAYS;

/**
* Timeout of the readiness checks.
*/
Expand All @@ -254,6 +259,14 @@ public static class Readiness {
*/
private final Tcp tcp = new Tcp();

public Wait getWait() {
return this.wait;
}

public void setWait(Wait wait) {
this.wait = wait;
}

public Duration getTimeout() {
return this.timeout;
}
Expand All @@ -266,6 +279,29 @@ public Tcp getTcp() {
return this.tcp;
}

/**
* Readiness wait strategies.
*/
public enum Wait {

/**
* Always perform readiness checks.
*/
ALWAYS,

/**
* Always perform readiness checks.

This comment has been minimized.

Copy link
@ChristianCiach

ChristianCiach May 19, 2023

Copy-paste error. Should be "Never perform...".

This comment has been minimized.

Copy link
@wilkinsona

wilkinsona May 23, 2023

Member

Thanks. We've fixed this in 0ca3ab7.

*/
NEVER,

/**
* Only perform readiness checks if docker was started with lifecycle
* management.
*/
ONLY_IF_STARTED

}

/**
* TCP properties.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import org.springframework.boot.docker.compose.core.DockerCompose;
import org.springframework.boot.docker.compose.core.DockerComposeFile;
import org.springframework.boot.docker.compose.core.RunningService;
import org.springframework.boot.docker.compose.lifecycle.DockerComposeProperties.Readiness.Wait;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.support.GenericApplicationContext;
Expand Down Expand Up @@ -268,7 +269,7 @@ void startWhenHasStopTimeoutUsesDuration() {
void startWhenHasIgnoreLabelIgnoresService() {
EventCapturingListener listener = new EventCapturingListener();
this.eventListeners.add(listener);
setUpRunningServices(Map.of("org.springframework.boot.ignore", "true"));
setUpRunningServices(true, Map.of("org.springframework.boot.ignore", "true"));
this.lifecycleManager.start();
this.shutdownHandlers.run();
assertThat(listener.getEvent()).isNotNull();
Expand All @@ -285,6 +286,40 @@ void startWaitsUntilReady() {
then(this.serviceReadinessChecks).should().waitUntilReady(this.runningServices);
}

@Test
void startWhenWaitNeverDoesNotWaitUntilReady() {
this.properties.getReadiness().setWait(Wait.NEVER);
EventCapturingListener listener = new EventCapturingListener();
this.eventListeners.add(listener);
setUpRunningServices();
this.lifecycleManager.start();
this.shutdownHandlers.run();
then(this.serviceReadinessChecks).should(never()).waitUntilReady(this.runningServices);
}

@Test
void startWhenWaitOnlyIfStartedAndNotStartedDoesNotWaitUntilReady() {
this.properties.getReadiness().setWait(Wait.ONLY_IF_STARTED);
this.properties.setLifecycleManagement(LifecycleManagement.NONE);
EventCapturingListener listener = new EventCapturingListener();
this.eventListeners.add(listener);
setUpRunningServices();
this.lifecycleManager.start();
this.shutdownHandlers.run();
then(this.serviceReadinessChecks).should(never()).waitUntilReady(this.runningServices);
}

@Test
void startWhenWaitOnlyIfStartedAndStartedWaitsUntilReady() {
this.properties.getReadiness().setWait(Wait.ONLY_IF_STARTED);
EventCapturingListener listener = new EventCapturingListener();
this.eventListeners.add(listener);
setUpRunningServices(false);
this.lifecycleManager.start();
this.shutdownHandlers.run();
then(this.serviceReadinessChecks).should().waitUntilReady(this.runningServices);
}

@Test
void startGetsDockerComposeWithActiveProfiles() {
this.properties.getProfiles().setActive(Set.of("my-profile"));
Expand All @@ -306,16 +341,26 @@ void startPublishesEvent() {
}

private void setUpRunningServices() {
setUpRunningServices(Collections.emptyMap());
setUpRunningServices(true);
}

private void setUpRunningServices(Map<String, String> labels) {
private void setUpRunningServices(boolean started) {
setUpRunningServices(started, Collections.emptyMap());
}

@SuppressWarnings("unchecked")
private void setUpRunningServices(boolean started, Map<String, String> labels) {
given(this.dockerCompose.hasDefinedServices()).willReturn(true);
given(this.dockerCompose.hasRunningServices()).willReturn(true);
RunningService runningService = mock(RunningService.class);
given(runningService.labels()).willReturn(labels);
this.runningServices = List.of(runningService);
given(this.dockerCompose.getRunningServices()).willReturn(this.runningServices);
if (started) {
given(this.dockerCompose.getRunningServices()).willReturn(this.runningServices);
}
else {
given(this.dockerCompose.getRunningServices()).willReturn(Collections.emptyList(), this.runningServices);
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
import org.springframework.boot.docker.compose.lifecycle.DockerComposeProperties.Readiness.Wait;

import static org.assertj.core.api.Assertions.assertThat;

Expand All @@ -48,6 +49,7 @@ void getWhenNoPropertiesReturnsNew() {
assertThat(properties.getStop().getCommand()).isEqualTo(StopCommand.STOP);
assertThat(properties.getStop().getTimeout()).isEqualTo(Duration.ofSeconds(10));
assertThat(properties.getProfiles().getActive()).isEmpty();
assertThat(properties.getReadiness().getWait()).isEqualTo(Wait.ALWAYS);
assertThat(properties.getReadiness().getTimeout()).isEqualTo(Duration.ofMinutes(2));
assertThat(properties.getReadiness().getTcp().getConnectTimeout()).isEqualTo(Duration.ofMillis(200));
assertThat(properties.getReadiness().getTcp().getReadTimeout()).isEqualTo(Duration.ofMillis(200));
Expand All @@ -63,6 +65,7 @@ void getWhenPropertiesReturnsBound() {
source.put("spring.docker.compose.stop.command", "down");
source.put("spring.docker.compose.stop.timeout", "5s");
source.put("spring.docker.compose.profiles.active", "myprofile");
source.put("spring.docker.compose.readiness.wait", "only-if-started");
source.put("spring.docker.compose.readiness.timeout", "10s");
source.put("spring.docker.compose.readiness.tcp.connect-timeout", "400ms");
source.put("spring.docker.compose.readiness.tcp.read-timeout", "500ms");
Expand All @@ -75,6 +78,7 @@ void getWhenPropertiesReturnsBound() {
assertThat(properties.getStop().getCommand()).isEqualTo(StopCommand.DOWN);
assertThat(properties.getStop().getTimeout()).isEqualTo(Duration.ofSeconds(5));
assertThat(properties.getProfiles().getActive()).containsExactly("myprofile");
assertThat(properties.getReadiness().getWait()).isEqualTo(Wait.ONLY_IF_STARTED);
assertThat(properties.getReadiness().getTimeout()).isEqualTo(Duration.ofSeconds(10));
assertThat(properties.getReadiness().getTcp().getConnectTimeout()).isEqualTo(Duration.ofMillis(400));
assertThat(properties.getReadiness().getTcp().getReadTimeout()).isEqualTo(Duration.ofMillis(500));
Expand Down

0 comments on commit a8602a1

Please sign in to comment.