Add Kubernetes Liveness and Readiness Probes support

Prior to this commit and as of Spring Boot 2.2.0, we would advise
developers to use the Actuator health groups to define custom "liveness"
and "readiness" groups and configure them with subsets of existing
health indicators.

This commit addresses several limitations with that approach.

First, `LivenessState` and `ReadinessState` are promoted to first class
concepts in Spring Boot applications. These states should not only based
on periodic health checks. Applications should be able to track changes
(and adapt their behavior) or update states (when an error happens).

The `ApplicationStateProvider` can be injected and used by applications
components to get the current application state. Components can also
track specific `ApplicationEvent` to be notified of changes, like
`ReadinessStateChangedEvent` and `LivenessStateChangedEvent`.
Components can also publish such events with an
`ApplicationEventPublisher`. Spring Boot will track startup event and
application context state to update the liveness and readiness state of
the application. This infrastructure is available in the
main spring-boot module.

If Spring Boot Actuator is on the classpath, additional
`HealthIndicator` will be contributed to the application:
`"LivenessProveHealthIndicator"` and `"ReadinessProbeHealthIndicator"`.
Also, "liveness" and "readiness" Health groups will be defined if
they're not configured already.

Closes gh-19593
pull/20577/head
Brian Clozel 5 years ago
parent b680db6cd8
commit fd0b2f6695

@ -0,0 +1,64 @@
/*
* 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.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator;
import org.springframework.boot.actuate.health.HealthEndpointGroupsRegistryCustomizer;
import org.springframework.boot.actuate.kubernetes.LivenessProbeHealthIndicator;
import org.springframework.boot.actuate.kubernetes.ProbesHealthEndpointGroupsRegistrar;
import org.springframework.boot.actuate.kubernetes.ReadinessProbeHealthIndicator;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnCloudPlatform;
import org.springframework.boot.autoconfigure.kubernetes.ApplicationStateAutoConfiguration;
import org.springframework.boot.cloud.CloudPlatform;
import org.springframework.boot.kubernetes.ApplicationStateProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* {@link EnableAutoConfiguration Auto-configuration} for
* {@link LivenessProbeHealthIndicator} and {@link ReadinessProbeHealthIndicator}.
*
* @author Brian Clozel
* @since 2.3.0
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES)
@AutoConfigureAfter(ApplicationStateAutoConfiguration.class)
public class ProbesHealthContributorAutoConfiguration {
@Bean
@ConditionalOnEnabledHealthIndicator("livenessProbe")
public LivenessProbeHealthIndicator livenessProbeHealthIndicator(
ApplicationStateProvider applicationStateProvider) {
return new LivenessProbeHealthIndicator(applicationStateProvider);
}
@Bean
@ConditionalOnEnabledHealthIndicator("readinessProbe")
public ReadinessProbeHealthIndicator readinessProbeHealthIndicator(
ApplicationStateProvider applicationStateProvider) {
return new ReadinessProbeHealthIndicator(applicationStateProvider);
}
@Bean
public HealthEndpointGroupsRegistryCustomizer probesRegistryCustomizer() {
return new ProbesHealthEndpointGroupsRegistrar();
}
}

@ -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.
*/
/**
* Auto-configuration for actuator kubernetes concerns.
*/
package org.springframework.boot.actuate.autoconfigure.kubernetes;

@ -29,6 +29,7 @@ org.springframework.boot.actuate.autoconfigure.integration.IntegrationGraphEndpo
org.springframework.boot.actuate.autoconfigure.jdbc.DataSourceHealthContributorAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.jms.JmsHealthContributorAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.jolokia.JolokiaEndpointAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.kubernetes.ProbesHealthContributorAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.ldap.LdapHealthContributorAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.liquibase.LiquibaseEndpointAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.logging.LogFileWebEndpointAutoConfiguration,\

@ -0,0 +1,49 @@
/*
* 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.kubernetes;
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.kubernetes.ApplicationStateProvider;
import org.springframework.boot.kubernetes.LivenessState;
/**
* A {@link HealthIndicator} that checks the {@link LivenessState} of the application.
*
* @author Brian Clozel
* @since 2.3.0
*/
public class LivenessProbeHealthIndicator extends AbstractHealthIndicator {
private final ApplicationStateProvider applicationStateProvider;
public LivenessProbeHealthIndicator(ApplicationStateProvider applicationStateProvider) {
this.applicationStateProvider = applicationStateProvider;
}
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
if (LivenessState.live().equals(this.applicationStateProvider.getLivenessState())) {
builder.up();
}
else {
builder.down();
}
}
}

@ -0,0 +1,50 @@
/*
* 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.kubernetes;
import org.springframework.boot.actuate.health.HealthEndpointGroup;
import org.springframework.boot.actuate.health.HealthEndpointGroupsRegistry;
import org.springframework.boot.actuate.health.HealthEndpointGroupsRegistryCustomizer;
/**
* {@link HealthEndpointGroupsRegistryCustomizer} that registers {@code "liveness"} and
* {@code "readiness"} {@link HealthEndpointGroup groups} if they don't exist already.
*
* @author Brian Clozel
* @since 2.3.0
*/
public class ProbesHealthEndpointGroupsRegistrar implements HealthEndpointGroupsRegistryCustomizer {
private static final String LIVENESS_GROUP_NAME = "liveness";
private static final String READINESS_GROUP_NAME = "readiness";
private static final String LIVENESS_PROBE_INDICATOR = "livenessProbe";
private static final String READINESS_PROBE_INDICATOR = "readinessProbe";
@Override
public void customize(HealthEndpointGroupsRegistry registry) {
if (registry.get(LIVENESS_GROUP_NAME) == null) {
registry.add(LIVENESS_GROUP_NAME, (configurer) -> configurer.include(LIVENESS_PROBE_INDICATOR));
}
if (registry.get(READINESS_GROUP_NAME) == null) {
registry.add(READINESS_GROUP_NAME, (configurer) -> configurer.include(READINESS_PROBE_INDICATOR));
}
}
}

@ -0,0 +1,49 @@
/*
* 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.kubernetes;
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.kubernetes.ApplicationStateProvider;
import org.springframework.boot.kubernetes.ReadinessState;
/**
* A {@link HealthIndicator} that checks the {@link ReadinessState} of the application.
*
* @author Brian Clozel
* @since 2.3.0
*/
public class ReadinessProbeHealthIndicator extends AbstractHealthIndicator {
private final ApplicationStateProvider applicationStateProvider;
public ReadinessProbeHealthIndicator(ApplicationStateProvider applicationStateProvider) {
this.applicationStateProvider = applicationStateProvider;
}
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
if (ReadinessState.ready().equals(this.applicationStateProvider.getReadinessState())) {
builder.up();
}
else {
builder.outOfService();
}
}
}

@ -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 kubernetes-related concerns.
*/
package org.springframework.boot.actuate.kubernetes;

@ -0,0 +1,124 @@
/*
* 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.health;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* Test implementation for {@link HealthEndpointGroupsRegistry}
*
* @author Brian Clozel
*/
public class TestHealthEndpointGroupsRegistry implements HealthEndpointGroupsRegistry {
private TestHealthEndpointGroup primary = new TestHealthEndpointGroup();
private Map<String, TestHealthEndpointGroup> groups = new HashMap<>();
@Override
public HealthEndpointGroupsRegistry add(String groupName, Consumer<HealthEndpointGroupConfigurer> builder) {
TestHealthEndpointGroupConfigurer configurer = new TestHealthEndpointGroupConfigurer();
builder.accept(configurer);
this.groups.put(groupName, configurer.toHealthEndpointGroup());
return this;
}
@Override
public HealthEndpointGroupsRegistry remove(String groupName) {
this.groups.remove(groupName);
return this;
}
@Override
public HealthEndpointGroups toGroups() {
return this;
}
@Override
public HealthEndpointGroup getPrimary() {
return this.primary;
}
@Override
public Set<String> getNames() {
return this.groups.keySet();
}
@Override
public HealthEndpointGroup get(String name) {
return this.groups.get(name);
}
class TestHealthEndpointGroupConfigurer implements HealthEndpointGroupConfigurer {
private Predicate<String> predicate = (name) -> true;
@Override
public HealthEndpointGroupConfigurer include(String... indicators) {
Predicate<String> included = Arrays.asList(indicators).stream()
.map((group) -> (Predicate<String>) (s) -> s.equals(group)).reduce(Predicate::or)
.orElse((s) -> true);
this.predicate = this.predicate.and(included);
return this;
}
@Override
public HealthEndpointGroupConfigurer exclude(String... indicators) {
Predicate<String> excluded = Arrays.asList(indicators).stream()
.map((group) -> (Predicate<String>) (s) -> !s.equals(group)).reduce(Predicate::or)
.orElse((s) -> true);
this.predicate = this.predicate.and(excluded);
return this;
}
@Override
public HealthEndpointGroupConfigurer statusAggregator(StatusAggregator statusAggregator) {
throw new UnsupportedOperationException();
}
@Override
public HealthEndpointGroupConfigurer httpCodeStatusMapper(HttpCodeStatusMapper httpCodeStatusMapper) {
throw new UnsupportedOperationException();
}
@Override
public HealthEndpointGroupConfigurer showComponents(HealthEndpointGroup.Show showComponents) {
throw new UnsupportedOperationException();
}
@Override
public HealthEndpointGroupConfigurer showDetails(HealthEndpointGroup.Show showDetails) {
throw new UnsupportedOperationException();
}
@Override
public HealthEndpointGroupConfigurer roles(String... roles) {
throw new UnsupportedOperationException();
}
TestHealthEndpointGroup toHealthEndpointGroup() {
return new TestHealthEndpointGroup(this.predicate);
}
}
}

@ -0,0 +1,59 @@
/*
* 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.kubernetes;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.health.Status;
import org.springframework.boot.kubernetes.ApplicationStateProvider;
import org.springframework.boot.kubernetes.LivenessState;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.when;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link LivenessProbeHealthIndicator}
*
* @author Brian Clozel
*/
class LivenessProbeHealthIndicatorTests {
private ApplicationStateProvider stateProvider;
private LivenessProbeHealthIndicator healthIndicator;
@BeforeEach
void setUp() {
this.stateProvider = mock(ApplicationStateProvider.class);
this.healthIndicator = new LivenessProbeHealthIndicator(this.stateProvider);
}
@Test
void livenessIsLive() {
when(this.stateProvider.getLivenessState()).thenReturn(LivenessState.live());
assertThat(this.healthIndicator.health().getStatus()).isEqualTo(Status.UP);
}
@Test
void livenessIsBroken() {
when(this.stateProvider.getLivenessState()).thenReturn(LivenessState.broken());
assertThat(this.healthIndicator.health().getStatus()).isEqualTo(Status.DOWN);
}
}

@ -0,0 +1,64 @@
/*
* 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.kubernetes;
import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.health.HealthEndpointGroup;
import org.springframework.boot.actuate.health.HealthEndpointGroupsRegistry;
import org.springframework.boot.actuate.health.TestHealthEndpointGroupsRegistry;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@code ProbesHealthEndpointGroupsRegistrar }
*
* @author Brian Clozel
*/
class ProbesHealthEndpointGroupsRegistrarTests {
private ProbesHealthEndpointGroupsRegistrar registrar = new ProbesHealthEndpointGroupsRegistrar();
@Test
void shouldAddKubernetesProbes() {
HealthEndpointGroupsRegistry registry = new TestHealthEndpointGroupsRegistry();
this.registrar.customize(registry);
HealthEndpointGroup liveness = registry.get("liveness");
assertThat(liveness).isNotNull();
assertThat(liveness.isMember("livenessProbe")).isTrue();
HealthEndpointGroup readiness = registry.get("readiness");
assertThat(readiness).isNotNull();
assertThat(readiness.isMember("readinessProbe")).isTrue();
}
@Test
void shouldNotAddProbeIfGroupExists() {
HealthEndpointGroupsRegistry registry = new TestHealthEndpointGroupsRegistry();
registry.add("readiness", (configurer) -> configurer.include("test"));
this.registrar.customize(registry);
HealthEndpointGroup liveness = registry.get("liveness");
assertThat(liveness).isNotNull();
assertThat(liveness.isMember("livenessProbe")).isTrue();
HealthEndpointGroup readiness = registry.get("readiness");
assertThat(readiness).isNotNull();
assertThat(readiness.isMember("readinessProbe")).isFalse();
assertThat(readiness.isMember("test")).isTrue();
}
}

@ -0,0 +1,59 @@
/*
* 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.kubernetes;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.health.Status;
import org.springframework.boot.kubernetes.ApplicationStateProvider;
import org.springframework.boot.kubernetes.ReadinessState;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.when;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link ReadinessProbeHealthIndicator}
*
* @author Brian Clozel
*/
class ReadinessProbeHealthIndicatorTests {
private ApplicationStateProvider stateProvider;
private ReadinessProbeHealthIndicator healthIndicator;
@BeforeEach
void setUp() {
this.stateProvider = mock(ApplicationStateProvider.class);
this.healthIndicator = new ReadinessProbeHealthIndicator(this.stateProvider);
}
@Test
void readinessIsReady() {
when(this.stateProvider.getReadinessState()).thenReturn(ReadinessState.ready());
assertThat(this.healthIndicator.health().getStatus()).isEqualTo(Status.UP);
}
@Test
void readinessIsBusy() {
when(this.stateProvider.getReadinessState()).thenReturn(ReadinessState.busy());
assertThat(this.healthIndicator.health().getStatus()).isEqualTo(Status.OUT_OF_SERVICE);
}
}

@ -0,0 +1,47 @@
/*
* 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.autoconfigure.kubernetes;
import org.springframework.boot.autoconfigure.condition.ConditionalOnCloudPlatform;
import org.springframework.boot.cloud.CloudPlatform;
import org.springframework.boot.kubernetes.ApplicationStateProvider;
import org.springframework.boot.kubernetes.SpringApplicationEventListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration} for
* {@link ApplicationStateProvider}.
*
* @author Brian Clozel
* @since 2.3.0
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnCloudPlatform(CloudPlatform.KUBERNETES)
public class ApplicationStateAutoConfiguration {
@Bean
public ApplicationStateProvider applicationStateProvider() {
return new ApplicationStateProvider();
}
@Bean
public SpringApplicationEventListener springApplicationEventListener() {
return new SpringApplicationEventListener();
}
}

@ -0,0 +1,20 @@
/*
* 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.
*/
/**
* Auto-configuration for Kubernetes application features.
*/
package org.springframework.boot.autoconfigure.kubernetes;

@ -63,6 +63,7 @@ org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.rest.RestClientAutoConfiguration,\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\
org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
@ -84,11 +85,11 @@ org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\
org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\
org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration,\
org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\
org.springframework.boot.autoconfigure.kubernetes.ApplicationStateAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\

@ -0,0 +1,51 @@
/*
* 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.autoconfigure.kubernetes;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.kubernetes.ApplicationStateProvider;
import org.springframework.boot.kubernetes.SpringApplicationEventListener;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ApplicationStateAutoConfiguration}
*
* @author Brian Clozel
*/
class ApplicationStateAutoConfigurationTests {
private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(ApplicationStateAutoConfiguration.class));
@Test
void disabledWhenNotDeployedOnKubernetes() {
this.contextRunner.run(((context) -> assertThat(context).doesNotHaveBean(ApplicationStateProvider.class)
.doesNotHaveBean(SpringApplicationEventListener.class)));
}
@Test
void enabledWhenDeployedOnKubernetes() {
this.contextRunner.withPropertyValues("spring.main.cloud-platform:kubernetes")
.run(((context) -> assertThat(context).hasSingleBean(ApplicationStateProvider.class)
.hasSingleBean(SpringApplicationEventListener.class)));
}
}

@ -181,6 +181,11 @@ See the {spring-boot-module-api}/cloud/CloudFoundryVcapEnvironmentPostProcessor.
TIP: The https://github.com/pivotal-cf/java-cfenv/[Java CFEnv] project is a better fit for tasks such as configuring a DataSource.
[[cloud-deployment-kubernetes]]
=== Kubernetes
Spring Boot auto-detects Kubernetes deployment environments by checking the environment for `"*_SERVICE_HOST"` and `"*_SERVICE_PORT"` variables.
Spring Boot helps you to <<spring-boot-features.adoc#boot-features-kubernetes-application-state,manage the state of your application>> and export it with <<production-ready-features.adoc#production-ready-kubernetes-probes, HTTP Kubernetes Probes using Actuator>>.
[[cloud-deployment-heroku]]
@ -254,10 +259,7 @@ For more details, refer to https://devcenter.heroku.com/articles/deploying-sprin
[[cloud-deployment-openshift]]
=== OpenShift
https://www.openshift.com/[OpenShift] is the Red Hat public (and enterprise) extension of the Kubernetes container orchestration platform.
Similarly to Kubernetes, OpenShift has many options for installing Spring Boot based applications.
OpenShift has many resources describing how to deploy Spring Boot applications, including:
https://www.openshift.com/[OpenShift] has many resources describing how to deploy Spring Boot applications, including:
* https://blog.openshift.com/using-openshift-enterprise-grade-spring-boot-deployments/[Using the S2I builder]
* https://access.redhat.com/documentation/en-us/reference_architectures/2017/html-single/spring_boot_microservices_on_red_hat_openshift_container_platform_3/[Architecture guide]

@ -647,7 +647,7 @@ If no `HealthIndicator` returns a status that is known to the `StatusAggregator`
TIP: The `HealthContributorRegistry` can be used to register and unregister health indicators at runtime.
[[production-ready-health-indicators]]
==== Auto-configured HealthIndicators
The following `HealthIndicators` are auto-configured by Spring Boot when appropriate:
@ -679,6 +679,12 @@ The following `HealthIndicators` are auto-configured by Spring Boot when appropr
| {spring-boot-actuator-module-code}/jms/JmsHealthIndicator.java[`JmsHealthIndicator`]
| Checks that a JMS broker is up.
| {spring-boot-actuator-module-code}/kubernetes/LivenessProbeHealthIndicator.java[`LivenessProbeHealthIndicator`]
| Exposes the "Liveness" application state.
| {spring-boot-actuator-module-code}/kubernetes/ReadinessProbeHealthIndicator.java[`ReadinessProbeHealthIndicator`]
| Exposes the "Readiness" application state.
| {spring-boot-actuator-module-code}/ldap/LdapHealthIndicator.java[`LdapHealthIndicator`]
| Checks that an LDAP server is up.
@ -835,10 +841,9 @@ TIP: If necessary, reactive indicators replace the regular ones.
Also, any `HealthIndicator` that is not handled explicitly is wrapped automatically.
[[production-ready-health-groups]]
==== Health Groups
It's sometimes useful to organize health indicators into groups that can be used for different purposes.
For example, if you deploy your application to Kubernetes, you may want one different sets of health indicators for your "`liveness`" and "`readiness`" probes.
To create a health indicator group you can use the `management.endpoint.health.group.<name>` property and specify a list of health indicator IDs to `include` or `exclude`.
For example, to create a group that includes only database indicators you can define the following:
@ -865,6 +870,35 @@ It's also possible to override the `show-details` and `roles` properties if requ
TIP: You can use `@Qualifier("groupname")` if you need to register custom `StatusAggregator` or `HttpCodeStatusMapper` beans for use with the group.
[[production-ready-kubernetes-probes]]
=== Kubernetes Probes
Applications deployed on Kubernetes can provide information about their internal state with https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#container-probes[Container Probes].
Depending on https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/[your Kubernetes configuration], the kubelet will call those probes and react to the result.
When deployed in a Kubernetes environment, Spring Boot will manage your <<spring-boot-features.adoc#boot-features-kubernetes-application-state,Kubernetes Application State>>.
Spring Boot Actuator will gather the "Liveness" and "Readiness" information using <<production-ready-health-indicators,Health Indicators>>
and expose them as HTTP Probes using <<production-ready-health-groups, Health Groups>>.
You can then point your Kubernetes `"livenessProbe"` to `"/actuator/health/liveness"` and `"readinessProbe"` to `"/actuator/health/readiness"`.
If your application context startup can be longer than the configured `"livenessProbe"` timeout, configuring a `"startupProbe"` can solve startup issues.
TIP: Application should perform long-running startup tasks with `ApplicationRunners`; in this case, you don't necessarily need a `"startupProbe"` as such tasks are performed after the "Liveness" state is marked as successful.
The application won't receive traffic until tasks are finished - only then the `"readinessProbe"` will be successful.
As explained in the <<spring-boot-features.adoc#boot-features-kubernetes-application-state,Kubernetes Application State section>>, depending on external systems for defining the "Liveness" state might result in cascading failures.
In case your application needs to rely on a specific subset of health checks for "Liveness" or "Readiness", you should consider configuring <<production-ready-health-groups, Health Groups>> manually:
[source,properties,indent=0,configprops]
----
management.endpoint.health.group.readiness.include=readinessProbe,customCheck
----
WARNING: If your Actuator endpoints are deployed on a separate management context, be aware that endpoints are then not using the same web infrastructure (port, connection pools, framework components) as the main application.
In this case, a Probe check could be successful even if the main application does not work properly (for example, it cannot accept new connections).
You'll find more information in the <<deployment.adoc#cloud-deployment-kubernetes, deploying on Kubernetes>> section.
[[production-ready-application-info]]
=== Application Information

@ -6013,6 +6013,90 @@ For instance, it is possible to customize the name of the table for the JDBC sto
For setting the timeout of the session you can use the configprop:spring.session.timeout[] property.
If that property is not set, the auto-configuration falls back to the value of configprop:server.servlet.session.timeout[].
[[boot-features-kubernetes-application-state]]
== Kubernetes Application State
When deployed on Kubernetes, applications can provide information about their state to the platform using https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/[Kubernetes Probes].
Spring Boot manages this application state with the `ApplicationStateProvider` and makes it available to application components and the cloud platform itself.
This feature is only enabled when the application is running on Kubernetes.
TIP: The cloud platform detection relies on platform-specific environment variables, but you can override this detection with the configprop:spring.main.cloud-platform[] configuration property.
[[boot-features-kubernetes-liveness-state]]
=== Liveness State
The "Liveness" state of an application tells whether its internal state allows it to work correctly, or recover by itself if it's currently failing.
An invalid "Liveness" state means that the application is in a broken state and cannot recover from it; the infrastructure should then restart the application to mitigate that problem.
NOTE: In general, the "Liveness" state should not be based external checks, such as <<production-ready-features.adoc#production-ready-health, Health checks>>.
If it did, a failing external system (a database, a Web API, an external cache) would trigger massive restarts and cascading failures across the platform.
The internal state of Spring Boot applications is mostly represented by the Spring application context.
If the application context has started successfully, Spring Boot assumes that the application is in a valid state.
An application is considered live as soon as the `ApplicationStartedEvent` has been published, see <<boot-features-application-events-and-listeners, Spring Boot application lifecycle and related Application Events>>.
[[boot-features-kubernetes-readiness-state]]
=== Readiness State
The "Readiness" state of an application tells whether the application is ready to handle traffic.
A failing "Readiness" state tells Kubernetes that it should not route traffic to the application for now.
This typically happens during startup, while `CommandLineRunner` and `ApplicationRunner` components are being processed, or at any time if the application decides that it's too busy for additional traffic.
An application is considered live as soon as the `ApplicationReadyEvent` has been published, see <<boot-features-application-events-and-listeners, Spring Boot application lifecycle and related Application Events>>.
TIP: Tasks expected to run during startup should be executed by `CommandLineRunner` and `ApplicationRunner` components instead of using Spring component lifecycle callbacks such as `@PostConstruct`.
[[boot-features-kubernetes-managing-state]]
=== Managing Kubernetes Application State
Application components can retrieve the current application state at any time, by injecting the `ApplicationStateProvider` and calling methods on it.
More often, applications will want to listen to state updates or update the state of the application.
For example, we can export the "Readiness" state of the application to a file so that a Kubernetes "exec Probe" can look at this file:
[source,java,indent=0]
----
@Component
public class LivenessStateExporter implements ApplicationListener<LivenessStateChangedEvent> {
@Override
public void onApplicationEvent(LivenessStateChangedEvent event) {
switch (event.getLivenessState().getStatus()) {
case LIVE:
// create file /tmp/healthy
break;
case BROKEN:
// remove file /tmp/healthy
break;
}
}
}
----
We can also update the state of the application, when the application breaks and cannot recover:
[source,java,indent=0]
----
@Component
public class LocalCacheVerifier {
private final ApplicationEventPublisher eventPublisher;
public LocalCacheVerifier(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
public void checkLocalCache() {
try {
//...
}
catch (CacheCompletelyBroken ex) {
this.eventPublisher.publishEvent(LivenessStateChangedEvent.broken(ex));
}
}
}
----
Spring Boot provides <<production-ready-features.adoc#production-ready-kubernetes-probes,Kubernetes HTTP probes for "Liveness" and "Readiness" with Actuator Health Endpoints>>.
You can get more guidance about <<deployment.adoc#cloud-deployment-kubernetes,deploying Spring Boot applications on Kubernetes in the dedicated section>>.
[[boot-features-jmx]]

@ -0,0 +1,75 @@
/*
* 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.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
/**
* Holds the application state, when deployed in a Kubernetes environment.
* <p>
* Other application components can get the current state information from the
* {@code ApplicationStateProvider}, or publish application evens such as
* {@link ReadinessStateChangedEvent} and {@link LivenessStateChangedEvent} to update the
* state of the application.
*
* @author Brian Clozel
* @since 2.3.0
*/
public class ApplicationStateProvider implements ApplicationListener<ApplicationEvent> {
private static final Log logger = LogFactory.getLog(ApplicationStateProvider.class);
private LivenessState livenessState;
private ReadinessState readinessState;
public ApplicationStateProvider() {
this(LivenessState.broken(), ReadinessState.busy());
}
public ApplicationStateProvider(LivenessState livenessState, ReadinessState readinessState) {
this.livenessState = livenessState;
this.readinessState = readinessState;
}
public LivenessState getLivenessState() {
return this.livenessState;
}
public ReadinessState getReadinessState() {
return this.readinessState;
}
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof LivenessStateChangedEvent) {
LivenessStateChangedEvent livenessEvent = (LivenessStateChangedEvent) event;
this.livenessState = livenessEvent.getLivenessState();
logger.info("Application State is now " + this.livenessState.toString());
}
else if (event instanceof ReadinessStateChangedEvent) {
ReadinessStateChangedEvent readinessEvent = (ReadinessStateChangedEvent) event;
this.readinessState = readinessEvent.getReadinessState();
logger.info("Application State is now " + this.readinessState.toString());
}
}
}

@ -0,0 +1,86 @@
/*
* 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 java.util.Objects;
/**
* "Liveness" state of the application, when deployed on Kubernetes.
* <p>
* An application is considered live when it's running with a correct internal state.
* "Liveness" failure means that the internal state of the application is broken and we
* cannot recover from it. As a result, the platform should restart the application.
*
* @author Brian Clozel
* @since 2.3.0
*/
public final class LivenessState {
private final Status status;
LivenessState(Status status) {
this.status = status;
}
public Status getStatus() {
return this.status;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
LivenessState that = (LivenessState) o;
return this.status == that.status;
}
@Override
public int hashCode() {
return Objects.hash(this.status);
}
@Override
public String toString() {
return "LivenessState{" + "status=" + this.status + '}';
}
public static LivenessState broken() {
return new LivenessState(Status.BROKEN);
}
public static LivenessState live() {
return new LivenessState(Status.LIVE);
}
public enum Status {
/**
* The application is running and its internal state is correct.
*/
LIVE,
/**
* The internal state of the application is broken.
*/
BROKEN
}
}

@ -0,0 +1,64 @@
/*
* 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.context.ApplicationEvent;
/**
* {@link ApplicationEvent} sent when the {@link LivenessState} of the application
* changes.
* <p>
* Any application component can send such events to update the state of the application.
*
* @author Brian Clozel
* @since 2.3.0
*/
public class LivenessStateChangedEvent extends ApplicationEvent {
private final String cause;
LivenessStateChangedEvent(LivenessState state, String cause) {
super(state);
this.cause = cause;
}
public LivenessState getLivenessState() {
return (LivenessState) getSource();
}
/**
* Create a new {@code ApplicationEvent} signaling that the {@link LivenessState} is
* broken.
* @param cause the cause of the broken internal state of the application
* @return the application event
*/
public static LivenessStateChangedEvent broken(String cause) {
return new LivenessStateChangedEvent(LivenessState.broken(), cause);
}
/**
* Create a new {@code ApplicationEvent} signaling that the {@link LivenessState} is
* broken.
* @param throwable the exception that caused the broken internal state of the
* application
* @return the application event
*/
public static LivenessStateChangedEvent broken(Throwable throwable) {
return new LivenessStateChangedEvent(LivenessState.broken(), throwable.getMessage());
}
}

@ -0,0 +1,86 @@
/*
* 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 java.util.Objects;
/**
* "Readiness" state of the application, when deployed on Kubernetes.
* <p>
* An application is considered ready when it's {@link LivenessState live} and willing to
* accept traffic. "Readiness" failure means that the application is not able to accept
* traffic and that the infrastructure should stop routing requests to it.
*
* @author Brian Clozel
* @since 2.3.0
*/
public final class ReadinessState {
private final Availability availability;
private ReadinessState(Availability availability) {
this.availability = availability;
}
public Availability getAvailability() {
return this.availability;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ReadinessState that = (ReadinessState) o;
return this.availability == that.availability;
}
@Override
public int hashCode() {
return Objects.hash(this.availability);
}
@Override
public String toString() {
return "ReadinessState{" + "availability=" + this.availability + '}';
}
public static ReadinessState ready() {
return new ReadinessState(Availability.READY);
}
public static ReadinessState busy() {
return new ReadinessState(Availability.BUSY);
}
public enum Availability {
/**
* The application is not willing to receive traffic.
*/
BUSY,
/**
* The application is ready to receive traffic.
*/
READY
}
}

@ -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.kubernetes;
import org.springframework.context.ApplicationEvent;
/**
* {@link ApplicationEvent} sent when the {@link ReadinessState} of the application
* changes.
* <p>
* Any application component can send such events to update the state of the application.
*
* @author Brian Clozel
* @since 2.3.0
*/
public class ReadinessStateChangedEvent extends ApplicationEvent {
ReadinessStateChangedEvent(ReadinessState state) {
super(state);
}
public ReadinessState getReadinessState() {
return (ReadinessState) getSource();
}
/**
* Create a new {@code ApplicationEvent} signaling that the {@link ReadinessState} is
* ready.
* @return the application event
*/
public static ReadinessStateChangedEvent ready() {
return new ReadinessStateChangedEvent(ReadinessState.ready());
}
/**
* Create a new {@code ApplicationEvent} signaling that the {@link ReadinessState} is
* busy.
* @return the application event
*/
public static ReadinessStateChangedEvent busy() {
return new ReadinessStateChangedEvent(ReadinessState.busy());
}
}

@ -0,0 +1,61 @@
/*
* 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,20 @@
/*
* 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.
*/
/**
* Support for Kubernetes deployment environments.
*/
package org.springframework.boot.kubernetes;

@ -0,0 +1,52 @@
/*
* 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 static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ApplicationStateProvider}
*
* @author Brian Clozel
*/
class ApplicationStateProviderTests {
@Test
void initialStateShouldBeFailures() {
ApplicationStateProvider stateProvider = new ApplicationStateProvider();
assertThat(stateProvider.getLivenessState()).isEqualTo(LivenessState.broken());
assertThat(stateProvider.getReadinessState()).isEqualTo(ReadinessState.busy());
}
@Test
void updateLivenessState() {
ApplicationStateProvider stateProvider = new ApplicationStateProvider();
LivenessState livenessState = LivenessState.live();
stateProvider.onApplicationEvent(new LivenessStateChangedEvent(livenessState, "Startup complete"));
assertThat(stateProvider.getLivenessState()).isEqualTo(livenessState);
}
@Test
void updateReadiessState() {
ApplicationStateProvider stateProvider = new ApplicationStateProvider();
stateProvider.onApplicationEvent(ReadinessStateChangedEvent.ready());
assertThat(stateProvider.getReadinessState()).isEqualTo(ReadinessState.ready());
}
}

@ -0,0 +1,83 @@
/*
* 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…
Cancel
Save