Browse Source
This commit moves the core Liveness and Readiness support to its own `availability` package. We've made this a core concept independent of Kubernetes. Spring Boot now produces `LivenessStateChanged` and `ReadinessStateChanged` events as part of the typical application lifecycle. Liveness and Readiness Probes (`HealthIndicator` components and health groups) are still configured only when deployed on Kubernetes. This commit also improves the documentation around Probes best practices and container lifecycle considerations. See gh-19593pull/20617/head
30 changed files with 521 additions and 354 deletions
@ -0,0 +1,58 @@
@@ -0,0 +1,58 @@
|
||||
/* |
||||
* Copyright 2012-2020 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.actuate.autoconfigure.kubernetes; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.boot.actuate.availability.LivenessProbeHealthIndicator; |
||||
import org.springframework.boot.actuate.availability.ReadinessProbeHealthIndicator; |
||||
import org.springframework.boot.actuate.health.HealthEndpointGroupsRegistryCustomizer; |
||||
import org.springframework.boot.autoconfigure.AutoConfigurations; |
||||
import org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration; |
||||
import org.springframework.boot.availability.ApplicationAvailabilityProvider; |
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
||||
/** |
||||
* Tests fos {@link ProbesHealthContributorAutoConfiguration}. |
||||
* |
||||
* @author Brian Clozel |
||||
*/ |
||||
class ProbesHealthContributorAutoConfigurationTests { |
||||
|
||||
private ApplicationContextRunner contextRunner = new ApplicationContextRunner().withConfiguration(AutoConfigurations |
||||
.of(ApplicationAvailabilityAutoConfiguration.class, ProbesHealthContributorAutoConfiguration.class)); |
||||
|
||||
@Test |
||||
void probesNotConfiguredIfNotKubernetes() { |
||||
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(ApplicationAvailabilityProvider.class) |
||||
.doesNotHaveBean(LivenessProbeHealthIndicator.class) |
||||
.doesNotHaveBean(ReadinessProbeHealthIndicator.class) |
||||
.doesNotHaveBean(HealthEndpointGroupsRegistryCustomizer.class)); |
||||
} |
||||
|
||||
@Test |
||||
void probesConfiguredIfKubernetes() { |
||||
this.contextRunner.withPropertyValues("spring.main.cloud-platform=kubernetes") |
||||
.run((context) -> assertThat(context).hasSingleBean(ApplicationAvailabilityProvider.class) |
||||
.hasSingleBean(LivenessProbeHealthIndicator.class) |
||||
.hasSingleBean(ReadinessProbeHealthIndicator.class) |
||||
.hasSingleBean(HealthEndpointGroupsRegistryCustomizer.class)); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,20 @@
@@ -0,0 +1,20 @@
|
||||
/* |
||||
* Copyright 2012-2019 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. |
||||
*/ |
||||
|
||||
/** |
||||
* Actuator support for application availability concerns. |
||||
*/ |
||||
package org.springframework.boot.actuate.availability; |
||||
21
spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/kubernetes/ApplicationStateAutoConfiguration.java → spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/availability/ApplicationAvailabilityAutoConfiguration.java
21
spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/kubernetes/ApplicationStateAutoConfiguration.java → spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/availability/ApplicationAvailabilityAutoConfiguration.java
22
spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/kubernetes/ApplicationStateAutoConfigurationTests.java → spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/kubernetes/ApplicationAvailabilityAutoConfigurationTests.java
22
spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/kubernetes/ApplicationStateAutoConfigurationTests.java → spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/kubernetes/ApplicationAvailabilityAutoConfigurationTests.java
@ -1,61 +0,0 @@
@@ -1,61 +0,0 @@
|
||||
/* |
||||
* Copyright 2012-2020 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.kubernetes; |
||||
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent; |
||||
import org.springframework.boot.context.event.ApplicationStartedEvent; |
||||
import org.springframework.context.ApplicationEvent; |
||||
import org.springframework.context.ApplicationEventPublisher; |
||||
import org.springframework.context.ApplicationEventPublisherAware; |
||||
import org.springframework.context.ApplicationListener; |
||||
import org.springframework.context.event.ContextClosedEvent; |
||||
|
||||
/** |
||||
* {@link ApplicationListener} that listens for application lifecycle events such as |
||||
* {@link ApplicationStartedEvent}, {@link ApplicationReadyEvent}, |
||||
* {@link ContextClosedEvent}. Those events are then translated and published into events |
||||
* consumed by {@link ApplicationStateProvider} to update the application state. |
||||
* |
||||
* @author Brian Clozel |
||||
* @since 2.3.0 |
||||
*/ |
||||
public class SpringApplicationEventListener |
||||
implements ApplicationListener<ApplicationEvent>, ApplicationEventPublisherAware { |
||||
|
||||
private ApplicationEventPublisher applicationEventPublisher; |
||||
|
||||
@Override |
||||
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { |
||||
this.applicationEventPublisher = applicationEventPublisher; |
||||
} |
||||
|
||||
@Override |
||||
public void onApplicationEvent(ApplicationEvent event) { |
||||
if (event instanceof ApplicationStartedEvent) { |
||||
LivenessState livenessState = LivenessState.live(); |
||||
this.applicationEventPublisher |
||||
.publishEvent(new LivenessStateChangedEvent(livenessState, "Application has started")); |
||||
} |
||||
else if (event instanceof ApplicationReadyEvent) { |
||||
this.applicationEventPublisher.publishEvent(ReadinessStateChangedEvent.ready()); |
||||
} |
||||
else if (event instanceof ContextClosedEvent) { |
||||
this.applicationEventPublisher.publishEvent(ReadinessStateChangedEvent.busy()); |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,102 @@
@@ -0,0 +1,102 @@
|
||||
/* |
||||
* Copyright 2012-2020 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.context.event; |
||||
|
||||
import java.util.ArrayDeque; |
||||
import java.util.ArrayList; |
||||
import java.util.Collections; |
||||
import java.util.Deque; |
||||
import java.util.List; |
||||
|
||||
import org.junit.jupiter.api.BeforeEach; |
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.boot.SpringApplication; |
||||
import org.springframework.boot.availability.LivenessStateChangedEvent; |
||||
import org.springframework.boot.availability.ReadinessStateChangedEvent; |
||||
import org.springframework.context.ApplicationEvent; |
||||
import org.springframework.context.ApplicationListener; |
||||
import org.springframework.context.support.StaticApplicationContext; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.mockito.BDDMockito.given; |
||||
import static org.mockito.Mockito.mock; |
||||
|
||||
/** |
||||
* Tests for {@link EventPublishingRunListener} |
||||
* |
||||
* @author Brian Clozel |
||||
*/ |
||||
class EventPublishingRunListenerTests { |
||||
|
||||
private SpringApplication application; |
||||
|
||||
private EventPublishingRunListener runListener; |
||||
|
||||
private TestApplicationListener eventListener; |
||||
|
||||
@BeforeEach |
||||
void setup() { |
||||
this.eventListener = new TestApplicationListener(); |
||||
this.application = mock(SpringApplication.class); |
||||
given(this.application.getListeners()).willReturn(Collections.singleton(this.eventListener)); |
||||
this.runListener = new EventPublishingRunListener(this.application, null); |
||||
} |
||||
|
||||
@Test |
||||
void shouldPublishLifecyleEvents() { |
||||
StaticApplicationContext context = new StaticApplicationContext(); |
||||
assertThat(this.eventListener.receivedEvents()).isEmpty(); |
||||
this.runListener.starting(); |
||||
checkApplicationEvents(ApplicationStartingEvent.class); |
||||
this.runListener.environmentPrepared(null); |
||||
checkApplicationEvents(ApplicationEnvironmentPreparedEvent.class); |
||||
this.runListener.contextPrepared(context); |
||||
checkApplicationEvents(ApplicationContextInitializedEvent.class); |
||||
this.runListener.contextLoaded(context); |
||||
checkApplicationEvents(ApplicationPreparedEvent.class); |
||||
context.refresh(); |
||||
this.runListener.started(context); |
||||
checkApplicationEvents(ApplicationStartedEvent.class, LivenessStateChangedEvent.class); |
||||
this.runListener.running(context); |
||||
checkApplicationEvents(ApplicationReadyEvent.class, ReadinessStateChangedEvent.class); |
||||
} |
||||
|
||||
void checkApplicationEvents(Class<?>... eventClasses) { |
||||
assertThat(this.eventListener.receivedEvents()).extracting("class").contains((Object[]) eventClasses); |
||||
} |
||||
|
||||
static class TestApplicationListener implements ApplicationListener<ApplicationEvent> { |
||||
|
||||
private Deque<ApplicationEvent> events = new ArrayDeque<>(); |
||||
|
||||
@Override |
||||
public void onApplicationEvent(ApplicationEvent event) { |
||||
this.events.add(event); |
||||
} |
||||
|
||||
List<ApplicationEvent> receivedEvents() { |
||||
List<ApplicationEvent> receivedEvents = new ArrayList<>(); |
||||
while (!this.events.isEmpty()) { |
||||
receivedEvents.add(this.events.pollFirst()); |
||||
} |
||||
return receivedEvents; |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -1,83 +0,0 @@
@@ -1,83 +0,0 @@
|
||||
/* |
||||
* Copyright 2012-2020 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.kubernetes; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
import org.mockito.ArgumentCaptor; |
||||
|
||||
import org.springframework.boot.SpringApplication; |
||||
import org.springframework.boot.context.event.ApplicationReadyEvent; |
||||
import org.springframework.boot.context.event.ApplicationStartedEvent; |
||||
import org.springframework.context.ApplicationEvent; |
||||
import org.springframework.context.ApplicationEventPublisher; |
||||
import org.springframework.context.event.ContextClosedEvent; |
||||
import org.springframework.context.support.StaticApplicationContext; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.mockito.Mockito.mock; |
||||
import static org.mockito.Mockito.verify; |
||||
|
||||
/** |
||||
* Tests for {@link SpringApplicationEventListener} |
||||
* |
||||
* @author Brian Clozel |
||||
*/ |
||||
class SpringApplicationEventListenerTests { |
||||
|
||||
@Test |
||||
void shouldReactToApplicationStartedEvent() { |
||||
ApplicationEvent event = publishAndReceiveApplicationEvent( |
||||
new ApplicationStartedEvent(new SpringApplication(), null, null)); |
||||
|
||||
assertThat(event).isInstanceOf(LivenessStateChangedEvent.class); |
||||
LivenessStateChangedEvent livenessEvent = (LivenessStateChangedEvent) event; |
||||
assertThat(livenessEvent.getLivenessState()).isEqualTo(LivenessState.live()); |
||||
} |
||||
|
||||
@Test |
||||
void shouldReactToApplicationReadyEvent() { |
||||
ApplicationEvent event = publishAndReceiveApplicationEvent( |
||||
new ApplicationReadyEvent(new SpringApplication(), null, null)); |
||||
|
||||
assertThat(event).isInstanceOf(ReadinessStateChangedEvent.class); |
||||
ReadinessStateChangedEvent readinessEvent = (ReadinessStateChangedEvent) event; |
||||
assertThat(readinessEvent.getReadinessState()).isEqualTo(ReadinessState.ready()); |
||||
} |
||||
|
||||
@Test |
||||
void shouldReactToContextClosedEvent() { |
||||
ApplicationEvent event = publishAndReceiveApplicationEvent( |
||||
new ContextClosedEvent(new StaticApplicationContext())); |
||||
|
||||
assertThat(event).isInstanceOf(ReadinessStateChangedEvent.class); |
||||
ReadinessStateChangedEvent readinessEvent = (ReadinessStateChangedEvent) event; |
||||
assertThat(readinessEvent.getReadinessState()).isEqualTo(ReadinessState.busy()); |
||||
} |
||||
|
||||
private ApplicationEvent publishAndReceiveApplicationEvent(ApplicationEvent eventToSend) { |
||||
ApplicationEventPublisher eventPublisher = mock(ApplicationEventPublisher.class); |
||||
SpringApplicationEventListener eventListener = new SpringApplicationEventListener(); |
||||
eventListener.setApplicationEventPublisher(eventPublisher); |
||||
|
||||
eventListener.onApplicationEvent(eventToSend); |
||||
ArgumentCaptor<ApplicationEvent> event = ArgumentCaptor.forClass(ApplicationEvent.class); |
||||
verify(eventPublisher).publishEvent(event.capture()); |
||||
|
||||
return event.getValue(); |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue