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-19593pull/20577/head
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;
|
@ -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;
|
@ -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)));
|
||||
}
|
||||
|
||||
}
|
@ -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…
Reference in New Issue