Configure existing probes at additional path when necessary
Fixes gh-30612
This commit is contained in:
parent
3a5b40645a
commit
cc15eb0b96
|
@ -43,6 +43,10 @@ class AvailabilityProbesHealthEndpointGroups implements HealthEndpointGroups {
|
||||||
|
|
||||||
private final Set<String> names;
|
private final Set<String> names;
|
||||||
|
|
||||||
|
private static final String LIVENESS = "liveness";
|
||||||
|
|
||||||
|
private static final String READINESS = "readiness";
|
||||||
|
|
||||||
AvailabilityProbesHealthEndpointGroups(HealthEndpointGroups groups, boolean addAdditionalPaths) {
|
AvailabilityProbesHealthEndpointGroups(HealthEndpointGroups groups, boolean addAdditionalPaths) {
|
||||||
Assert.notNull(groups, "Groups must not be null");
|
Assert.notNull(groups, "Groups must not be null");
|
||||||
this.groups = groups;
|
this.groups = groups;
|
||||||
|
@ -54,18 +58,32 @@ class AvailabilityProbesHealthEndpointGroups implements HealthEndpointGroups {
|
||||||
|
|
||||||
private Map<String, HealthEndpointGroup> createProbeGroups(boolean addAdditionalPaths) {
|
private Map<String, HealthEndpointGroup> createProbeGroups(boolean addAdditionalPaths) {
|
||||||
Map<String, HealthEndpointGroup> probeGroups = new LinkedHashMap<>();
|
Map<String, HealthEndpointGroup> probeGroups = new LinkedHashMap<>();
|
||||||
probeGroups.put("liveness", createProbeGroup(addAdditionalPaths, "/livez", "livenessState"));
|
probeGroups.put(LIVENESS, getOrCreateProbeGroup(addAdditionalPaths, LIVENESS, "/livez", "livenessState"));
|
||||||
probeGroups.put("readiness", createProbeGroup(addAdditionalPaths, "/readyz", "readinessState"));
|
probeGroups.put(READINESS, getOrCreateProbeGroup(addAdditionalPaths, READINESS, "/readyz", "readinessState"));
|
||||||
return Collections.unmodifiableMap(probeGroups);
|
return Collections.unmodifiableMap(probeGroups);
|
||||||
}
|
}
|
||||||
|
|
||||||
private AvailabilityProbesHealthEndpointGroup createProbeGroup(boolean addAdditionalPath, String path,
|
private HealthEndpointGroup getOrCreateProbeGroup(boolean addAdditionalPath, String name, String path,
|
||||||
String members) {
|
String members) {
|
||||||
|
HealthEndpointGroup group = this.groups.get(name);
|
||||||
|
if (group != null) {
|
||||||
|
return determineAdditionalPathForExistingGroup(addAdditionalPath, path, group);
|
||||||
|
}
|
||||||
AdditionalHealthEndpointPath additionalPath = (!addAdditionalPath) ? null
|
AdditionalHealthEndpointPath additionalPath = (!addAdditionalPath) ? null
|
||||||
: AdditionalHealthEndpointPath.of(WebServerNamespace.SERVER, path);
|
: AdditionalHealthEndpointPath.of(WebServerNamespace.SERVER, path);
|
||||||
return new AvailabilityProbesHealthEndpointGroup(additionalPath, members);
|
return new AvailabilityProbesHealthEndpointGroup(additionalPath, members);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private HealthEndpointGroup determineAdditionalPathForExistingGroup(boolean addAdditionalPath, String path,
|
||||||
|
HealthEndpointGroup group) {
|
||||||
|
if (addAdditionalPath && group.getAdditionalPath() == null) {
|
||||||
|
AdditionalHealthEndpointPath additionalPath = AdditionalHealthEndpointPath.of(WebServerNamespace.SERVER,
|
||||||
|
path);
|
||||||
|
return new DelegatingAvailabilityProbesHealthEndpointGroup(group, additionalPath);
|
||||||
|
}
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HealthEndpointGroup getPrimary() {
|
public HealthEndpointGroup getPrimary() {
|
||||||
return this.groups.getPrimary();
|
return this.groups.getPrimary();
|
||||||
|
@ -79,15 +97,14 @@ class AvailabilityProbesHealthEndpointGroups implements HealthEndpointGroups {
|
||||||
@Override
|
@Override
|
||||||
public HealthEndpointGroup get(String name) {
|
public HealthEndpointGroup get(String name) {
|
||||||
HealthEndpointGroup group = this.groups.get(name);
|
HealthEndpointGroup group = this.groups.get(name);
|
||||||
if (group == null) {
|
if (group == null || isProbeGroup(name)) {
|
||||||
group = this.probeGroups.get(name);
|
group = this.probeGroups.get(name);
|
||||||
}
|
}
|
||||||
return group;
|
return group;
|
||||||
}
|
}
|
||||||
|
|
||||||
static boolean containsAllProbeGroups(HealthEndpointGroups groups) {
|
private boolean isProbeGroup(String name) {
|
||||||
Set<String> names = groups.getNames();
|
return name.equals(LIVENESS) || name.equals(READINESS);
|
||||||
return names.contains("liveness") && names.contains("readiness");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,9 +41,6 @@ class AvailabilityProbesHealthEndpointGroupsPostProcessor implements HealthEndpo
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HealthEndpointGroups postProcessHealthEndpointGroups(HealthEndpointGroups groups) {
|
public HealthEndpointGroups postProcessHealthEndpointGroups(HealthEndpointGroups groups) {
|
||||||
if (AvailabilityProbesHealthEndpointGroups.containsAllProbeGroups(groups)) {
|
|
||||||
return groups;
|
|
||||||
}
|
|
||||||
return new AvailabilityProbesHealthEndpointGroups(groups, this.addAdditionalPaths);
|
return new AvailabilityProbesHealthEndpointGroups(groups, this.addAdditionalPaths);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2022 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.availability;
|
||||||
|
|
||||||
|
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||||
|
import org.springframework.boot.actuate.health.AdditionalHealthEndpointPath;
|
||||||
|
import org.springframework.boot.actuate.health.HealthEndpointGroup;
|
||||||
|
import org.springframework.boot.actuate.health.HttpCodeStatusMapper;
|
||||||
|
import org.springframework.boot.actuate.health.StatusAggregator;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link HealthEndpointGroup} used to support availability probes that delegates to an
|
||||||
|
* existing group.
|
||||||
|
*
|
||||||
|
* @author Madhura Bhave
|
||||||
|
*/
|
||||||
|
class DelegatingAvailabilityProbesHealthEndpointGroup implements HealthEndpointGroup {
|
||||||
|
|
||||||
|
private final HealthEndpointGroup delegate;
|
||||||
|
|
||||||
|
private final AdditionalHealthEndpointPath additionalPath;
|
||||||
|
|
||||||
|
DelegatingAvailabilityProbesHealthEndpointGroup(HealthEndpointGroup delegate,
|
||||||
|
AdditionalHealthEndpointPath additionalPath) {
|
||||||
|
Assert.notNull(delegate, "Delegate must not be null");
|
||||||
|
this.delegate = delegate;
|
||||||
|
this.additionalPath = additionalPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isMember(String name) {
|
||||||
|
return this.delegate.isMember(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean showComponents(SecurityContext securityContext) {
|
||||||
|
return this.delegate.showComponents(securityContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean showDetails(SecurityContext securityContext) {
|
||||||
|
return this.delegate.showDetails(securityContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StatusAggregator getStatusAggregator() {
|
||||||
|
return this.delegate.getStatusAggregator();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HttpCodeStatusMapper getHttpCodeStatusMapper() {
|
||||||
|
return this.delegate.getHttpCodeStatusMapper();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AdditionalHealthEndpointPath getAdditionalPath() {
|
||||||
|
return this.additionalPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -48,7 +48,8 @@ class AvailabilityProbesHealthEndpointGroupsPostProcessorTests {
|
||||||
names.add("readiness");
|
names.add("readiness");
|
||||||
names.add("liveness");
|
names.add("liveness");
|
||||||
given(groups.getNames()).willReturn(names);
|
given(groups.getNames()).willReturn(names);
|
||||||
assertThat(this.postProcessor.postProcessHealthEndpointGroups(groups)).isSameAs(groups);
|
assertThat(this.postProcessor.postProcessHealthEndpointGroups(groups))
|
||||||
|
.isInstanceOf(AvailabilityProbesHealthEndpointGroups.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -83,6 +84,25 @@ class AvailabilityProbesHealthEndpointGroupsPostProcessorTests {
|
||||||
assertThat(readiness.getAdditionalPath().toString()).isEqualTo("server:/readyz");
|
assertThat(readiness.getAdditionalPath().toString()).isEqualTo("server:/readyz");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void postProcessHealthEndpointGroupsWhenGroupsAlreadyContainedAndAdditionalPathPropertyIsTrue() {
|
||||||
|
HealthEndpointGroups groups = mock(HealthEndpointGroups.class);
|
||||||
|
Set<String> names = new LinkedHashSet<>();
|
||||||
|
names.add("test");
|
||||||
|
names.add("readiness");
|
||||||
|
names.add("liveness");
|
||||||
|
given(groups.getNames()).willReturn(names);
|
||||||
|
MockEnvironment environment = new MockEnvironment();
|
||||||
|
environment.setProperty("management.endpoint.health.probes.add-additional-paths", "true");
|
||||||
|
AvailabilityProbesHealthEndpointGroupsPostProcessor postProcessor = new AvailabilityProbesHealthEndpointGroupsPostProcessor(
|
||||||
|
environment);
|
||||||
|
HealthEndpointGroups postProcessed = postProcessor.postProcessHealthEndpointGroups(groups);
|
||||||
|
HealthEndpointGroup liveness = postProcessed.get("liveness");
|
||||||
|
HealthEndpointGroup readiness = postProcessed.get("readiness");
|
||||||
|
assertThat(liveness.getAdditionalPath().toString()).isEqualTo("server:/livez");
|
||||||
|
assertThat(readiness.getAdditionalPath().toString()).isEqualTo("server:/readyz");
|
||||||
|
}
|
||||||
|
|
||||||
private HealthEndpointGroups getPostProcessed(String value) {
|
private HealthEndpointGroups getPostProcessed(String value) {
|
||||||
MockEnvironment environment = new MockEnvironment();
|
MockEnvironment environment = new MockEnvironment();
|
||||||
environment.setProperty("management.endpoint.health.probes.add-additional-paths", value);
|
environment.setProperty("management.endpoint.health.probes.add-additional-paths", value);
|
||||||
|
|
|
@ -16,15 +16,15 @@
|
||||||
|
|
||||||
package org.springframework.boot.actuate.autoconfigure.availability;
|
package org.springframework.boot.actuate.autoconfigure.availability;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedHashSet;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import org.springframework.boot.actuate.health.AdditionalHealthEndpointPath;
|
||||||
import org.springframework.boot.actuate.health.HealthEndpointGroup;
|
import org.springframework.boot.actuate.health.HealthEndpointGroup;
|
||||||
import org.springframework.boot.actuate.health.HealthEndpointGroups;
|
import org.springframework.boot.actuate.health.HealthEndpointGroups;
|
||||||
|
import org.springframework.boot.actuate.health.HttpCodeStatusMapper;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||||
|
@ -35,6 +35,7 @@ import static org.mockito.Mockito.mock;
|
||||||
* Tests for {@link AvailabilityProbesHealthEndpointGroups}.
|
* Tests for {@link AvailabilityProbesHealthEndpointGroups}.
|
||||||
*
|
*
|
||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
|
* @author Madhura Bhave
|
||||||
*/
|
*/
|
||||||
class AvailabilityProbesHealthEndpointGroupsTests {
|
class AvailabilityProbesHealthEndpointGroupsTests {
|
||||||
|
|
||||||
|
@ -69,10 +70,32 @@ class AvailabilityProbesHealthEndpointGroupsTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void getWhenProbeInDelegateReturnsGroupFromDelegate() {
|
void getWhenProbeInDelegateReturnsOriginalGroup() {
|
||||||
given(this.delegate.get("liveness")).willReturn(this.group);
|
HealthEndpointGroup group = mock(HealthEndpointGroup.class);
|
||||||
|
HttpCodeStatusMapper mapper = mock(HttpCodeStatusMapper.class);
|
||||||
|
given(group.getHttpCodeStatusMapper()).willReturn(mapper);
|
||||||
|
given(this.delegate.get("liveness")).willReturn(group);
|
||||||
HealthEndpointGroups availabilityProbes = new AvailabilityProbesHealthEndpointGroups(this.delegate, false);
|
HealthEndpointGroups availabilityProbes = new AvailabilityProbesHealthEndpointGroups(this.delegate, false);
|
||||||
assertThat(availabilityProbes.get("liveness")).isEqualTo(this.group);
|
assertThat(availabilityProbes.get("liveness")).isEqualTo(group);
|
||||||
|
assertThat(group.getHttpCodeStatusMapper()).isEqualTo(mapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getWhenProbeInDelegateAndExistingAdditionalPathReturnsOriginalGroup() {
|
||||||
|
HealthEndpointGroup group = mock(HealthEndpointGroup.class);
|
||||||
|
given(group.getAdditionalPath()).willReturn(AdditionalHealthEndpointPath.from("server:test"));
|
||||||
|
given(this.delegate.get("liveness")).willReturn(group);
|
||||||
|
HealthEndpointGroups availabilityProbes = new AvailabilityProbesHealthEndpointGroups(this.delegate, true);
|
||||||
|
HealthEndpointGroup liveness = availabilityProbes.get("liveness");
|
||||||
|
assertThat(liveness).isEqualTo(group);
|
||||||
|
assertThat(liveness.getAdditionalPath().getValue()).isEqualTo("test");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getWhenProbeInDelegateAndAdditionalPathReturnsGroupWithAdditionalPath() {
|
||||||
|
given(this.delegate.get("liveness")).willReturn(this.group);
|
||||||
|
HealthEndpointGroups availabilityProbes = new AvailabilityProbesHealthEndpointGroups(this.delegate, true);
|
||||||
|
assertThat(availabilityProbes.get("liveness").getAdditionalPath().getValue()).isEqualTo("/livez");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -103,22 +126,4 @@ class AvailabilityProbesHealthEndpointGroupsTests {
|
||||||
assertThat(probeGroup.isMember("readinessState")).isTrue();
|
assertThat(probeGroup.isMember("readinessState")).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
void containsAllWhenContainsAllReturnTrue() {
|
|
||||||
given(this.delegate.getNames()).willReturn(new LinkedHashSet<>(Arrays.asList("test", "liveness", "readiness")));
|
|
||||||
assertThat(AvailabilityProbesHealthEndpointGroups.containsAllProbeGroups(this.delegate)).isTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void containsAllWhenContainsOneReturnFalse() {
|
|
||||||
given(this.delegate.getNames()).willReturn(new LinkedHashSet<>(Arrays.asList("test", "liveness")));
|
|
||||||
assertThat(AvailabilityProbesHealthEndpointGroups.containsAllProbeGroups(this.delegate)).isFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void containsAllWhenContainsNoneReturnFalse() {
|
|
||||||
given(this.delegate.getNames()).willReturn(new LinkedHashSet<>(Arrays.asList("test", "spring")));
|
|
||||||
assertThat(AvailabilityProbesHealthEndpointGroups.containsAllProbeGroups(this.delegate)).isFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2021 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.availability;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import org.springframework.boot.actuate.health.AdditionalHealthEndpointPath;
|
||||||
|
import org.springframework.boot.actuate.health.HealthEndpointGroup;
|
||||||
|
import org.springframework.boot.actuate.health.HttpCodeStatusMapper;
|
||||||
|
import org.springframework.boot.actuate.health.StatusAggregator;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.BDDMockito.given;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link DelegatingAvailabilityProbesHealthEndpointGroup}.
|
||||||
|
*
|
||||||
|
* @author Madhura Bhave
|
||||||
|
*/
|
||||||
|
class DelegatingAvailabilityProbesHealthEndpointGroupTests {
|
||||||
|
|
||||||
|
private DelegatingAvailabilityProbesHealthEndpointGroup group;
|
||||||
|
|
||||||
|
private HttpCodeStatusMapper mapper;
|
||||||
|
|
||||||
|
private StatusAggregator aggregator;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setup() {
|
||||||
|
HealthEndpointGroup delegate = mock(HealthEndpointGroup.class);
|
||||||
|
this.mapper = mock(HttpCodeStatusMapper.class);
|
||||||
|
this.aggregator = mock(StatusAggregator.class);
|
||||||
|
given(delegate.getHttpCodeStatusMapper()).willReturn(this.mapper);
|
||||||
|
given(delegate.getStatusAggregator()).willReturn(this.aggregator);
|
||||||
|
given(delegate.showComponents(any())).willReturn(true);
|
||||||
|
given(delegate.showDetails(any())).willReturn(false);
|
||||||
|
given(delegate.isMember("test")).willReturn(true);
|
||||||
|
this.group = new DelegatingAvailabilityProbesHealthEndpointGroup(delegate,
|
||||||
|
AdditionalHealthEndpointPath.from("server:test"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void groupDelegatesToDelegate() {
|
||||||
|
assertThat(this.group.getHttpCodeStatusMapper()).isEqualTo(this.mapper);
|
||||||
|
assertThat(this.group.getStatusAggregator()).isEqualTo(this.aggregator);
|
||||||
|
assertThat(this.group.isMember("test")).isTrue();
|
||||||
|
assertThat(this.group.showDetails(null)).isFalse();
|
||||||
|
assertThat(this.group.showComponents(null)).isTrue();
|
||||||
|
assertThat(this.group.getAdditionalPath().getValue()).isEqualTo("test");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue