Add support for health indicator groups
Update the `HealthEndpoint` to support health groups. The `HealthEndpointSettings` interface has been replaced with `HealthEndpointGroups` which provides access to the primary group as well as an optional set of additional groups. Groups can be configured via properties and may have custom `StatusAggregator` and `HttpCodeStatusMapper` settings. Closes gh-14022 Co-authored-by: Stephane Nicoll <snicoll@pivotal.io>pull/17939/head
parent
f09e0264d9
commit
e03f822c6d
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.autoconfigure.health;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.boot.actuate.health.DefaultHealthContributorRegistry;
|
||||
import org.springframework.boot.actuate.health.HealthContributor;
|
||||
import org.springframework.boot.actuate.health.HealthContributorRegistry;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An auto-configured {@link HealthContributorRegistry} that ensures registered indicators
|
||||
* do not clash with groups names.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class AutoConfiguredHealthContributorRegistry extends DefaultHealthContributorRegistry {
|
||||
|
||||
private final Collection<String> groupNames;
|
||||
|
||||
AutoConfiguredHealthContributorRegistry(Map<String, HealthContributor> contributors,
|
||||
Collection<String> groupNames) {
|
||||
super(contributors);
|
||||
this.groupNames = groupNames;
|
||||
contributors.keySet().forEach(this::assertDoesNotClashWithGroup);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerContributor(String name, HealthContributor contributor) {
|
||||
assertDoesNotClashWithGroup(name);
|
||||
super.registerContributor(name, contributor);
|
||||
}
|
||||
|
||||
private void assertDoesNotClashWithGroup(String name) {
|
||||
Assert.state(!this.groupNames.contains(name),
|
||||
() -> "HealthContributor with name \"" + name + "\" clashes with group");
|
||||
}
|
||||
|
||||
}
|
25
spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/AutoConfiguredHealthEndpointSettings.java → spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/AutoConfiguredHealthEndpointGroup.java
25
spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/AutoConfiguredHealthEndpointSettings.java → spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/AutoConfiguredHealthEndpointGroup.java
@ -0,0 +1,155 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.autoconfigure.health;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryUtils;
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils;
|
||||
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointProperties.Group;
|
||||
import org.springframework.boot.actuate.autoconfigure.health.HealthProperties.ShowDetails;
|
||||
import org.springframework.boot.actuate.autoconfigure.health.HealthProperties.Status;
|
||||
import org.springframework.boot.actuate.health.HealthEndpointGroup;
|
||||
import org.springframework.boot.actuate.health.HealthEndpointGroups;
|
||||
import org.springframework.boot.actuate.health.HttpCodeStatusMapper;
|
||||
import org.springframework.boot.actuate.health.SimpleHttpCodeStatusMapper;
|
||||
import org.springframework.boot.actuate.health.SimpleStatusAggregator;
|
||||
import org.springframework.boot.actuate.health.StatusAggregator;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* Auto-configured {@link HealthEndpointGroups}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class AutoConfiguredHealthEndpointGroups implements HealthEndpointGroups {
|
||||
|
||||
private static Predicate<String> ALL = (name) -> true;
|
||||
|
||||
private final HealthEndpointGroup primaryGroup;
|
||||
|
||||
private final Map<String, HealthEndpointGroup> groups;
|
||||
|
||||
/**
|
||||
* Create a new {@link AutoConfiguredHealthEndpointGroups} instance.
|
||||
* @param applicationContext the application context used to check for override beans
|
||||
* @param properties the health endpoint properties
|
||||
*/
|
||||
AutoConfiguredHealthEndpointGroups(ApplicationContext applicationContext, HealthEndpointProperties properties) {
|
||||
ListableBeanFactory beanFactory = (applicationContext instanceof ConfigurableApplicationContext)
|
||||
? ((ConfigurableApplicationContext) applicationContext).getBeanFactory() : applicationContext;
|
||||
ShowDetails showDetails = properties.getShowDetails();
|
||||
Set<String> roles = properties.getRoles();
|
||||
StatusAggregator statusAggregator = getNonQualifiedBean(beanFactory, StatusAggregator.class);
|
||||
if (statusAggregator == null) {
|
||||
statusAggregator = new SimpleStatusAggregator(properties.getStatus().getOrder());
|
||||
}
|
||||
HttpCodeStatusMapper httpCodeStatusMapper = getNonQualifiedBean(beanFactory, HttpCodeStatusMapper.class);
|
||||
if (httpCodeStatusMapper == null) {
|
||||
httpCodeStatusMapper = new SimpleHttpCodeStatusMapper(properties.getStatus().getHttpMapping());
|
||||
}
|
||||
this.primaryGroup = new AutoConfiguredHealthEndpointGroup(ALL, statusAggregator, httpCodeStatusMapper,
|
||||
showDetails, roles);
|
||||
this.groups = createGroups(properties.getGroup(), beanFactory, statusAggregator, httpCodeStatusMapper,
|
||||
showDetails, roles);
|
||||
}
|
||||
|
||||
private Map<String, HealthEndpointGroup> createGroups(Map<String, Group> groupProperties, BeanFactory beanFactory,
|
||||
StatusAggregator defaultStatusAggregator, HttpCodeStatusMapper defaultHttpCodeStatusMapper,
|
||||
ShowDetails defaultShowDetails, Set<String> defaultRoles) {
|
||||
Map<String, HealthEndpointGroup> groups = new LinkedHashMap<String, HealthEndpointGroup>();
|
||||
groupProperties.forEach((groupName, group) -> {
|
||||
Status status = group.getStatus();
|
||||
ShowDetails showDetails = (group.getShowDetails() != null) ? group.getShowDetails() : defaultShowDetails;
|
||||
Set<String> roles = !CollectionUtils.isEmpty(group.getRoles()) ? group.getRoles() : defaultRoles;
|
||||
StatusAggregator statusAggregator = getQualifiedBean(beanFactory, StatusAggregator.class, groupName, () -> {
|
||||
if (!CollectionUtils.isEmpty(status.getOrder())) {
|
||||
return new SimpleStatusAggregator(status.getOrder());
|
||||
}
|
||||
return defaultStatusAggregator;
|
||||
});
|
||||
HttpCodeStatusMapper httpCodeStatusMapper = getQualifiedBean(beanFactory, HttpCodeStatusMapper.class,
|
||||
groupName, () -> {
|
||||
if (!CollectionUtils.isEmpty(status.getHttpMapping())) {
|
||||
return new SimpleHttpCodeStatusMapper(status.getHttpMapping());
|
||||
}
|
||||
return defaultHttpCodeStatusMapper;
|
||||
});
|
||||
Predicate<String> members = new IncludeExcludeGroupMemberPredicate(group.getInclude(), group.getExclude());
|
||||
groups.put(groupName, new AutoConfiguredHealthEndpointGroup(members, statusAggregator, httpCodeStatusMapper,
|
||||
showDetails, roles));
|
||||
});
|
||||
return Collections.unmodifiableMap(groups);
|
||||
}
|
||||
|
||||
private <T> T getNonQualifiedBean(ListableBeanFactory beanFactory, Class<T> type) {
|
||||
List<String> candidates = new ArrayList<>();
|
||||
for (String beanName : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, type)) {
|
||||
String[] aliases = beanFactory.getAliases(beanName);
|
||||
if (!BeanFactoryAnnotationUtils.isQualifierMatch(
|
||||
(qualifier) -> !qualifier.equals(beanName) && !ObjectUtils.containsElement(aliases, qualifier),
|
||||
beanName, beanFactory)) {
|
||||
candidates.add(beanName);
|
||||
}
|
||||
}
|
||||
if (candidates.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
if (candidates.size() == 1) {
|
||||
return beanFactory.getBean(candidates.get(0), type);
|
||||
}
|
||||
return beanFactory.getBean(type);
|
||||
}
|
||||
|
||||
private <T> T getQualifiedBean(BeanFactory beanFactory, Class<T> type, String qualifier, Supplier<T> fallback) {
|
||||
try {
|
||||
return BeanFactoryAnnotationUtils.qualifiedBeanOfType(beanFactory, type, qualifier);
|
||||
}
|
||||
catch (NoSuchBeanDefinitionException ex) {
|
||||
return fallback.get();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public HealthEndpointGroup getPrimary() {
|
||||
return this.primaryGroup;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getNames() {
|
||||
return this.groups.keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HealthEndpointGroup get(String name) {
|
||||
return this.groups.get(name);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.autoconfigure.health;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.boot.actuate.health.DefaultReactiveHealthContributorRegistry;
|
||||
import org.springframework.boot.actuate.health.HealthContributorRegistry;
|
||||
import org.springframework.boot.actuate.health.ReactiveHealthContributor;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An auto-configured {@link HealthContributorRegistry} that ensures registered indicators
|
||||
* do not clash with groups names.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class AutoConfiguredReactiveHealthContributorRegistry extends DefaultReactiveHealthContributorRegistry {
|
||||
|
||||
private final Collection<String> groupNames;
|
||||
|
||||
AutoConfiguredReactiveHealthContributorRegistry(Map<String, ReactiveHealthContributor> contributors,
|
||||
Collection<String> groupNames) {
|
||||
super(contributors);
|
||||
this.groupNames = groupNames;
|
||||
contributors.keySet().forEach(this::assertDoesNotClashWithGroup);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerContributor(String name, ReactiveHealthContributor contributor) {
|
||||
assertDoesNotClashWithGroup(name);
|
||||
super.registerContributor(name, contributor);
|
||||
}
|
||||
|
||||
private void assertDoesNotClashWithGroup(String name) {
|
||||
Assert.state(!this.groupNames.contains(name),
|
||||
() -> "ReactiveHealthContributor with name \"" + name + "\" clashes with group");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,124 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.autoconfigure.health;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.boot.actuate.health.HealthEndpoint;
|
||||
|
||||
/**
|
||||
* Properties used to configure the health endpoint and endpoint groups.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Phillip Webb
|
||||
* @since 2.2.0
|
||||
*/
|
||||
public abstract class HealthProperties {
|
||||
|
||||
private final Status status = new Status();
|
||||
|
||||
/**
|
||||
* When to show full health details.
|
||||
*/
|
||||
private ShowDetails showDetails = ShowDetails.NEVER;
|
||||
|
||||
/**
|
||||
* Roles used to determine whether or not a user is authorized to be shown details.
|
||||
* When empty, all authenticated users are authorized.
|
||||
*/
|
||||
private Set<String> roles = new HashSet<>();
|
||||
|
||||
public Status getStatus() {
|
||||
return this.status;
|
||||
}
|
||||
|
||||
public ShowDetails getShowDetails() {
|
||||
return this.showDetails;
|
||||
}
|
||||
|
||||
public void setShowDetails(ShowDetails showDetails) {
|
||||
this.showDetails = showDetails;
|
||||
}
|
||||
|
||||
public Set<String> getRoles() {
|
||||
return this.roles;
|
||||
}
|
||||
|
||||
public void setRoles(Set<String> roles) {
|
||||
this.roles = roles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Status properties for the group.
|
||||
*/
|
||||
public static class Status {
|
||||
|
||||
/**
|
||||
* Comma-separated list of health statuses in order of severity.
|
||||
*/
|
||||
private List<String> order = null;
|
||||
|
||||
/**
|
||||
* Mapping of health statuses to HTTP status codes. By default, registered health
|
||||
* statuses map to sensible defaults (for example, UP maps to 200).
|
||||
*/
|
||||
private final Map<String, Integer> httpMapping = new HashMap<>();
|
||||
|
||||
public List<String> getOrder() {
|
||||
return this.order;
|
||||
}
|
||||
|
||||
public void setOrder(List<String> statusOrder) {
|
||||
if (statusOrder != null && !statusOrder.isEmpty()) {
|
||||
this.order = statusOrder;
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, Integer> getHttpMapping() {
|
||||
return this.httpMapping;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for showing details in responses from the {@link HealthEndpoint} web
|
||||
* extensions.
|
||||
*/
|
||||
public enum ShowDetails {
|
||||
|
||||
/**
|
||||
* Never show details in the response.
|
||||
*/
|
||||
NEVER,
|
||||
|
||||
/**
|
||||
* Show details in the response when accessed by an authorized user.
|
||||
*/
|
||||
WHEN_AUTHORIZED,
|
||||
|
||||
/**
|
||||
* Always show details in the response.
|
||||
*/
|
||||
ALWAYS
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.autoconfigure.health;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.actuate.health.HealthContributor;
|
||||
import org.springframework.boot.actuate.health.HealthContributorRegistry;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link AutoConfiguredHealthContributorRegistry}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class AutoConfiguredHealthContributorRegistryTests {
|
||||
|
||||
@Test
|
||||
void createWhenContributorsClashesWithGroupNameThrowsException() {
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> new AutoConfiguredHealthContributorRegistry(
|
||||
Collections.singletonMap("boot", mock(HealthContributor.class)),
|
||||
Arrays.asList("spring", "boot")))
|
||||
.withMessage("HealthContributor with name \"boot\" clashes with group");
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerContributorWithGroupNameThrowsException() {
|
||||
HealthContributorRegistry registry = new AutoConfiguredHealthContributorRegistry(Collections.emptyMap(),
|
||||
Arrays.asList("spring", "boot"));
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> registry.registerContributor("spring", mock(HealthContributor.class)))
|
||||
.withMessage("HealthContributor with name \"spring\" clashes with group");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,140 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.autoconfigure.health;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.health.HealthProperties.ShowDetails;
|
||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||
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.BDDMockito.given;
|
||||
|
||||
/**
|
||||
* Tests for {@link AutoConfiguredHealthEndpointGroup}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class AutoConfiguredHealthEndpointGroupTests {
|
||||
|
||||
@Mock
|
||||
private StatusAggregator statusAggregator;
|
||||
|
||||
@Mock
|
||||
private HttpCodeStatusMapper httpCodeStatusMapper;
|
||||
|
||||
@Mock
|
||||
private SecurityContext securityContext;
|
||||
|
||||
@Mock
|
||||
private Principal principal;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
}
|
||||
|
||||
@Test
|
||||
void isMemberWhenMemberPredicateMatchesAcceptsTrue() {
|
||||
AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> name.startsWith("a"),
|
||||
this.statusAggregator, this.httpCodeStatusMapper, ShowDetails.ALWAYS, Collections.emptySet());
|
||||
assertThat(group.isMember("albert")).isTrue();
|
||||
assertThat(group.isMember("arnold")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isMemberWhenMemberPredicateRejectsReturnsTrue() {
|
||||
AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> name.startsWith("a"),
|
||||
this.statusAggregator, this.httpCodeStatusMapper, ShowDetails.ALWAYS, Collections.emptySet());
|
||||
assertThat(group.isMember("bert")).isFalse();
|
||||
assertThat(group.isMember("ernie")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void includeDetailsWhenShowDetailsIsNeverReturnsFalse() {
|
||||
AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> true,
|
||||
this.statusAggregator, this.httpCodeStatusMapper, ShowDetails.NEVER, Collections.emptySet());
|
||||
assertThat(group.includeDetails(SecurityContext.NONE)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void includeDetailsWhenShowDetailsIsAlwaysReturnsTrue() {
|
||||
AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> true,
|
||||
this.statusAggregator, this.httpCodeStatusMapper, ShowDetails.ALWAYS, Collections.emptySet());
|
||||
assertThat(group.includeDetails(SecurityContext.NONE)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void includeDetailsWhenShowDetailsIsWhenAuthorizedAndPrincipalIsNullReturnsFalse() {
|
||||
AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> true,
|
||||
this.statusAggregator, this.httpCodeStatusMapper, ShowDetails.WHEN_AUTHORIZED, Collections.emptySet());
|
||||
given(this.securityContext.getPrincipal()).willReturn(null);
|
||||
assertThat(group.includeDetails(this.securityContext)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void includeDetailsWhenShowDetailsIsWhenAuthorizedAndRolesAreEmptyReturnsTrue() {
|
||||
AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> true,
|
||||
this.statusAggregator, this.httpCodeStatusMapper, ShowDetails.WHEN_AUTHORIZED, Collections.emptySet());
|
||||
given(this.securityContext.getPrincipal()).willReturn(this.principal);
|
||||
assertThat(group.includeDetails(this.securityContext)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void includeDetailsWhenShowDetailsIsWhenAuthorizedAndUseIsInRoleReturnsTrue() {
|
||||
AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> true,
|
||||
this.statusAggregator, this.httpCodeStatusMapper, ShowDetails.WHEN_AUTHORIZED,
|
||||
Arrays.asList("admin", "root", "bossmode"));
|
||||
given(this.securityContext.getPrincipal()).willReturn(this.principal);
|
||||
given(this.securityContext.isUserInRole("root")).willReturn(true);
|
||||
assertThat(group.includeDetails(this.securityContext)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void includeDetailsWhenShowDetailsIsWhenAuthorizedAndUseIsNotInRoleReturnsFalse() {
|
||||
AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> true,
|
||||
this.statusAggregator, this.httpCodeStatusMapper, ShowDetails.WHEN_AUTHORIZED,
|
||||
Arrays.asList("admin", "rot", "bossmode"));
|
||||
given(this.securityContext.getPrincipal()).willReturn(this.principal);
|
||||
given(this.securityContext.isUserInRole("root")).willReturn(true);
|
||||
assertThat(group.includeDetails(this.securityContext)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getStatusAggregatorReturnsStatusAggregator() {
|
||||
AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> true,
|
||||
this.statusAggregator, this.httpCodeStatusMapper, ShowDetails.ALWAYS, Collections.emptySet());
|
||||
assertThat(group.getStatusAggregator()).isSameAs(this.statusAggregator);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getHttpCodeStatusMapperReturnsHttpCodeStatusMapper() {
|
||||
AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> true,
|
||||
this.statusAggregator, this.httpCodeStatusMapper, ShowDetails.ALWAYS, Collections.emptySet());
|
||||
assertThat(group.getHttpCodeStatusMapper()).isSameAs(this.httpCodeStatusMapper);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,366 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.autoconfigure.health;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||
import org.springframework.boot.actuate.health.HealthEndpointGroup;
|
||||
import org.springframework.boot.actuate.health.HealthEndpointGroups;
|
||||
import org.springframework.boot.actuate.health.HttpCodeStatusMapper;
|
||||
import org.springframework.boot.actuate.health.SimpleHttpCodeStatusMapper;
|
||||
import org.springframework.boot.actuate.health.SimpleStatusAggregator;
|
||||
import org.springframework.boot.actuate.health.Status;
|
||||
import org.springframework.boot.actuate.health.StatusAggregator;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link AutoConfiguredHealthEndpointGroups}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class AutoConfiguredHealthEndpointGroupsTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(AutoConfiguredHealthEndpointGroupsTestConfiguration.class));
|
||||
|
||||
@Test
|
||||
void getPrimaryGroupMatchesAllMembers() {
|
||||
this.contextRunner.run((context) -> {
|
||||
HealthEndpointGroups groups = context.getBean(HealthEndpointGroups.class);
|
||||
HealthEndpointGroup primary = groups.getPrimary();
|
||||
assertThat(primary.isMember("a")).isTrue();
|
||||
assertThat(primary.isMember("b")).isTrue();
|
||||
assertThat(primary.isMember("C")).isTrue();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void getNamesReturnsGroupNames() {
|
||||
this.contextRunner.withPropertyValues("management.endpoint.health.group.a.include=*",
|
||||
"management.endpoint.health.group.b.include=*").run((context) -> {
|
||||
HealthEndpointGroups groups = context.getBean(HealthEndpointGroups.class);
|
||||
assertThat(groups.getNames()).containsExactlyInAnyOrder("a", "b");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void getGroupWhenGroupExistsReturnsGroup() {
|
||||
this.contextRunner.withPropertyValues("management.endpoint.health.group.a.include=*").run((context) -> {
|
||||
HealthEndpointGroups groups = context.getBean(HealthEndpointGroups.class);
|
||||
HealthEndpointGroup group = groups.get("a");
|
||||
assertThat(group).isNotNull();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void getGroupWhenGroupDoesNotExistReturnsNull() {
|
||||
this.contextRunner.withPropertyValues("management.endpoint.health.group.a.include=*").run((context) -> {
|
||||
HealthEndpointGroups groups = context.getBean(HealthEndpointGroups.class);
|
||||
HealthEndpointGroup group = groups.get("b");
|
||||
assertThat(group).isNull();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWhenNoDefinedBeansAdaptsProperties() {
|
||||
this.contextRunner.withPropertyValues("management.endpoint.health.show-details=always",
|
||||
"management.endpoint.health.status.order=up,down",
|
||||
"management.endpoint.health.status.http-mapping.down=200").run((context) -> {
|
||||
HealthEndpointGroups groups = context.getBean(HealthEndpointGroups.class);
|
||||
HealthEndpointGroup primary = groups.getPrimary();
|
||||
assertThat(primary.includeDetails(SecurityContext.NONE)).isTrue();
|
||||
assertThat(primary.getStatusAggregator().getAggregateStatus(Status.UP, Status.DOWN))
|
||||
.isEqualTo(Status.UP);
|
||||
assertThat(primary.getHttpCodeStatusMapper().getStatusCode(Status.DOWN)).isEqualTo(200);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWhenHasStatusAggregatorBeanReturnsInstanceWithAgregatorUsedForAllGroups() {
|
||||
this.contextRunner.withUserConfiguration(CustomStatusAggregatorConfiguration.class)
|
||||
.withPropertyValues("management.endpoint.health.status.order=up,down",
|
||||
"management.endpoint.health.group.a.include=*")
|
||||
.run((context) -> {
|
||||
HealthEndpointGroups groups = context.getBean(HealthEndpointGroups.class);
|
||||
HealthEndpointGroup primary = groups.getPrimary();
|
||||
HealthEndpointGroup groupA = groups.get("a");
|
||||
assertThat(primary.getStatusAggregator().getAggregateStatus(Status.UP, Status.DOWN, Status.UNKNOWN))
|
||||
.isEqualTo(Status.UNKNOWN);
|
||||
assertThat(groupA.getStatusAggregator().getAggregateStatus(Status.UP, Status.DOWN, Status.UNKNOWN))
|
||||
.isEqualTo(Status.UNKNOWN);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWhenHasStatusAggregatorBeanAndGroupSpecificPropertyReturnsInstanceThatUsesBeanOnlyForUnconfiguredGroups() {
|
||||
this.contextRunner.withUserConfiguration(CustomStatusAggregatorConfiguration.class)
|
||||
.withPropertyValues("management.endpoint.health.group.a.include=*",
|
||||
"management.endpoint.health.group.a.status.order=up,down",
|
||||
"management.endpoint.health.group.b.include=*")
|
||||
.run((context) -> {
|
||||
HealthEndpointGroups groups = context.getBean(HealthEndpointGroups.class);
|
||||
HealthEndpointGroup primary = groups.getPrimary();
|
||||
HealthEndpointGroup groupA = groups.get("a");
|
||||
HealthEndpointGroup groupB = groups.get("b");
|
||||
assertThat(primary.getStatusAggregator().getAggregateStatus(Status.UP, Status.DOWN, Status.UNKNOWN))
|
||||
.isEqualTo(Status.UNKNOWN);
|
||||
assertThat(groupA.getStatusAggregator().getAggregateStatus(Status.UP, Status.DOWN, Status.UNKNOWN))
|
||||
.isEqualTo(Status.UP);
|
||||
assertThat(groupB.getStatusAggregator().getAggregateStatus(Status.UP, Status.DOWN, Status.UNKNOWN))
|
||||
.isEqualTo(Status.UNKNOWN);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWhenHasStatusAggregatorPropertyReturnsInstanceWithPropertyUsedForAllGroups() {
|
||||
this.contextRunner.withPropertyValues("management.endpoint.health.status.order=up,down",
|
||||
"management.endpoint.health.group.a.include=*").run((context) -> {
|
||||
HealthEndpointGroups groups = context.getBean(HealthEndpointGroups.class);
|
||||
HealthEndpointGroup primary = groups.getPrimary();
|
||||
HealthEndpointGroup groupA = groups.get("a");
|
||||
assertThat(primary.getStatusAggregator().getAggregateStatus(Status.UP, Status.DOWN))
|
||||
.isEqualTo(Status.UP);
|
||||
assertThat(groupA.getStatusAggregator().getAggregateStatus(Status.UP, Status.DOWN))
|
||||
.isEqualTo(Status.UP);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWhenHasStatusAggregatorPropertyAndGroupSpecificPropertyReturnsInstanceWithPropertyUsedForExpectedGroups() {
|
||||
this.contextRunner.withPropertyValues("management.endpoint.health.status.order=up,down",
|
||||
"management.endpoint.health.group.a.include=*",
|
||||
"management.endpoint.health.group.a.status.order=unknown,up,down",
|
||||
"management.endpoint.health.group.b.include=*").run((context) -> {
|
||||
HealthEndpointGroups groups = context.getBean(HealthEndpointGroups.class);
|
||||
HealthEndpointGroup primary = groups.getPrimary();
|
||||
HealthEndpointGroup groupA = groups.get("a");
|
||||
HealthEndpointGroup groupB = groups.get("b");
|
||||
assertThat(primary.getStatusAggregator().getAggregateStatus(Status.UP, Status.DOWN, Status.UNKNOWN))
|
||||
.isEqualTo(Status.UP);
|
||||
assertThat(groupA.getStatusAggregator().getAggregateStatus(Status.UP, Status.DOWN, Status.UNKNOWN))
|
||||
.isEqualTo(Status.UNKNOWN);
|
||||
assertThat(groupB.getStatusAggregator().getAggregateStatus(Status.UP, Status.DOWN, Status.UNKNOWN))
|
||||
.isEqualTo(Status.UP);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWhenHasStatusAggregatorPropertyAndGroupQualifiedBeanReturnsInstanceWithBeanUsedForExpectedGroups() {
|
||||
this.contextRunner.withUserConfiguration(CustomStatusAggregatorGroupAConfiguration.class)
|
||||
.withPropertyValues("management.endpoint.health.status.order=up,down",
|
||||
"management.endpoint.health.group.a.include=*",
|
||||
"management.endpoint.health.group.a.status.order=up,down",
|
||||
"management.endpoint.health.group.b.include=*")
|
||||
.run((context) -> {
|
||||
HealthEndpointGroups groups = context.getBean(HealthEndpointGroups.class);
|
||||
HealthEndpointGroup primary = groups.getPrimary();
|
||||
HealthEndpointGroup groupA = groups.get("a");
|
||||
HealthEndpointGroup groupB = groups.get("b");
|
||||
assertThat(primary.getStatusAggregator().getAggregateStatus(Status.UP, Status.DOWN, Status.UNKNOWN))
|
||||
.isEqualTo(Status.UP);
|
||||
assertThat(groupA.getStatusAggregator().getAggregateStatus(Status.UP, Status.DOWN, Status.UNKNOWN))
|
||||
.isEqualTo(Status.UNKNOWN);
|
||||
assertThat(groupB.getStatusAggregator().getAggregateStatus(Status.UP, Status.DOWN, Status.UNKNOWN))
|
||||
.isEqualTo(Status.UP);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWhenHasGroupSpecificStatusAggregatorPropertyAndGroupQualifiedBeanReturnsInstanceWithBeanUsedForExpectedGroups() {
|
||||
this.contextRunner.withUserConfiguration(CustomStatusAggregatorGroupAConfiguration.class)
|
||||
.withPropertyValues("management.endpoint.health.group.a.include=*",
|
||||
"management.endpoint.health.group.a.status.order=up,down",
|
||||
"management.endpoint.health.group.b.include=*",
|
||||
"management.endpoint.health.group.b.status.order=up,down")
|
||||
.run((context) -> {
|
||||
HealthEndpointGroups groups = context.getBean(HealthEndpointGroups.class);
|
||||
HealthEndpointGroup primary = groups.getPrimary();
|
||||
HealthEndpointGroup groupA = groups.get("a");
|
||||
HealthEndpointGroup groupB = groups.get("b");
|
||||
assertThat(primary.getStatusAggregator().getAggregateStatus(Status.UP, Status.DOWN, Status.UNKNOWN))
|
||||
.isEqualTo(Status.DOWN);
|
||||
assertThat(groupA.getStatusAggregator().getAggregateStatus(Status.UP, Status.DOWN, Status.UNKNOWN))
|
||||
.isEqualTo(Status.UNKNOWN);
|
||||
assertThat(groupB.getStatusAggregator().getAggregateStatus(Status.UP, Status.DOWN, Status.UNKNOWN))
|
||||
.isEqualTo(Status.UP);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWhenHasHttpCodeStatusMapperBeanReturnsInstanceWithMapperUsedForAllGroups() {
|
||||
this.contextRunner.withUserConfiguration(CustomHttpCodeStatusMapperConfiguration.class)
|
||||
.withPropertyValues("management.endpoint.health.status.http-mapping.down=201",
|
||||
"management.endpoint.health.group.a.include=*")
|
||||
.run((context) -> {
|
||||
HealthEndpointGroups groups = context.getBean(HealthEndpointGroups.class);
|
||||
HealthEndpointGroup primary = groups.getPrimary();
|
||||
HealthEndpointGroup groupA = groups.get("a");
|
||||
assertThat(primary.getHttpCodeStatusMapper().getStatusCode(Status.DOWN)).isEqualTo(200);
|
||||
assertThat(groupA.getHttpCodeStatusMapper().getStatusCode(Status.DOWN)).isEqualTo(200);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWhenHasHttpCodeStatusMapperBeanAndGroupSpecificPropertyReturnsInstanceThatUsesBeanOnlyForUnconfiguredGroups() {
|
||||
this.contextRunner.withUserConfiguration(CustomHttpCodeStatusMapperConfiguration.class)
|
||||
.withPropertyValues("management.endpoint.health.group.a.include=*",
|
||||
"management.endpoint.health.group.a.status.http-mapping.down=201",
|
||||
"management.endpoint.health.group.b.include=*")
|
||||
.run((context) -> {
|
||||
HealthEndpointGroups groups = context.getBean(HealthEndpointGroups.class);
|
||||
HealthEndpointGroup primary = groups.getPrimary();
|
||||
HealthEndpointGroup groupA = groups.get("a");
|
||||
HealthEndpointGroup groupB = groups.get("b");
|
||||
assertThat(primary.getHttpCodeStatusMapper().getStatusCode(Status.DOWN)).isEqualTo(200);
|
||||
assertThat(groupA.getHttpCodeStatusMapper().getStatusCode(Status.DOWN)).isEqualTo(201);
|
||||
assertThat(groupB.getHttpCodeStatusMapper().getStatusCode(Status.DOWN)).isEqualTo(200);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWhenHasHttpCodeStatusMapperPropertyReturnsInstanceWithPropertyUsedForAllGroups() {
|
||||
this.contextRunner.withPropertyValues("management.endpoint.health.status.http-mapping.down=201",
|
||||
"management.endpoint.health.group.a.include=*").run((context) -> {
|
||||
HealthEndpointGroups groups = context.getBean(HealthEndpointGroups.class);
|
||||
HealthEndpointGroup primary = groups.getPrimary();
|
||||
HealthEndpointGroup groupA = groups.get("a");
|
||||
assertThat(primary.getHttpCodeStatusMapper().getStatusCode(Status.DOWN)).isEqualTo(201);
|
||||
assertThat(groupA.getHttpCodeStatusMapper().getStatusCode(Status.DOWN)).isEqualTo(201);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWhenHasHttpCodeStatusMapperPropertyAndGroupSpecificPropertyReturnsInstanceWithPropertyUsedForExpectedGroups() {
|
||||
this.contextRunner.withPropertyValues("management.endpoint.health.status.http-mapping.down=201",
|
||||
"management.endpoint.health.group.a.include=*",
|
||||
"management.endpoint.health.group.a.status.http-mapping.down=202",
|
||||
"management.endpoint.health.group.b.include=*").run((context) -> {
|
||||
HealthEndpointGroups groups = context.getBean(HealthEndpointGroups.class);
|
||||
HealthEndpointGroup primary = groups.getPrimary();
|
||||
HealthEndpointGroup groupA = groups.get("a");
|
||||
HealthEndpointGroup groupB = groups.get("b");
|
||||
assertThat(primary.getHttpCodeStatusMapper().getStatusCode(Status.DOWN)).isEqualTo(201);
|
||||
assertThat(groupA.getHttpCodeStatusMapper().getStatusCode(Status.DOWN)).isEqualTo(202);
|
||||
assertThat(groupB.getHttpCodeStatusMapper().getStatusCode(Status.DOWN)).isEqualTo(201);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWhenHasHttpCodeStatusMapperPropertyAndGroupQualifiedBeanReturnsInstanceWithBeanUsedForExpectedGroups() {
|
||||
this.contextRunner.withUserConfiguration(CustomHttpCodeStatusMapperGroupAConfiguration.class)
|
||||
.withPropertyValues("management.endpoint.health.status.http-mapping.down=201",
|
||||
"management.endpoint.health.group.a.include=*",
|
||||
"management.endpoint.health.group.a.status.http-mapping.down=201",
|
||||
"management.endpoint.health.group.b.include=*")
|
||||
.run((context) -> {
|
||||
HealthEndpointGroups groups = context.getBean(HealthEndpointGroups.class);
|
||||
HealthEndpointGroup primary = groups.getPrimary();
|
||||
HealthEndpointGroup groupA = groups.get("a");
|
||||
HealthEndpointGroup groupB = groups.get("b");
|
||||
assertThat(primary.getHttpCodeStatusMapper().getStatusCode(Status.DOWN)).isEqualTo(201);
|
||||
assertThat(groupA.getHttpCodeStatusMapper().getStatusCode(Status.DOWN)).isEqualTo(200);
|
||||
assertThat(groupB.getHttpCodeStatusMapper().getStatusCode(Status.DOWN)).isEqualTo(201);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWhenHasGroupSpecificHttpCodeStatusMapperPropertyAndGroupQualifiedBeanReturnsInstanceWithBeanUsedForExpectedGroups() {
|
||||
this.contextRunner.withUserConfiguration(CustomHttpCodeStatusMapperGroupAConfiguration.class)
|
||||
.withPropertyValues("management.endpoint.health.group.a.include=*",
|
||||
"management.endpoint.health.group.a.status.http-mapping.down=201",
|
||||
"management.endpoint.health.group.b.include=*",
|
||||
"management.endpoint.health.group.b.status.http-mapping.down=201")
|
||||
.run((context) -> {
|
||||
HealthEndpointGroups groups = context.getBean(HealthEndpointGroups.class);
|
||||
HealthEndpointGroup primary = groups.getPrimary();
|
||||
HealthEndpointGroup groupA = groups.get("a");
|
||||
HealthEndpointGroup groupB = groups.get("b");
|
||||
assertThat(primary.getHttpCodeStatusMapper().getStatusCode(Status.DOWN)).isEqualTo(503);
|
||||
assertThat(groupA.getHttpCodeStatusMapper().getStatusCode(Status.DOWN)).isEqualTo(200);
|
||||
assertThat(groupB.getHttpCodeStatusMapper().getStatusCode(Status.DOWN)).isEqualTo(201);
|
||||
});
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableConfigurationProperties(HealthEndpointProperties.class)
|
||||
static class AutoConfiguredHealthEndpointGroupsTestConfiguration {
|
||||
|
||||
@Bean
|
||||
AutoConfiguredHealthEndpointGroups healthEndpointGroups(ConfigurableApplicationContext applicationContext,
|
||||
HealthEndpointProperties properties) {
|
||||
return new AutoConfiguredHealthEndpointGroups(applicationContext, properties);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class CustomStatusAggregatorConfiguration {
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
StatusAggregator statusAggregator() {
|
||||
return new SimpleStatusAggregator(Status.UNKNOWN, Status.UP, Status.DOWN);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class CustomStatusAggregatorGroupAConfiguration {
|
||||
|
||||
@Bean
|
||||
@Qualifier("a")
|
||||
StatusAggregator statusAggregator() {
|
||||
return new SimpleStatusAggregator(Status.UNKNOWN, Status.UP, Status.DOWN);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class CustomHttpCodeStatusMapperConfiguration {
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
HttpCodeStatusMapper httpCodeStatusMapper() {
|
||||
return new SimpleHttpCodeStatusMapper(Collections.singletonMap(Status.DOWN.getCode(), 200));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class CustomHttpCodeStatusMapperGroupAConfiguration {
|
||||
|
||||
@Bean
|
||||
@Qualifier("a")
|
||||
HttpCodeStatusMapper httpCodeStatusMapper() {
|
||||
return new SimpleHttpCodeStatusMapper(Collections.singletonMap(Status.DOWN.getCode(), 200));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,122 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.autoconfigure.health;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointProperties.ShowDetails;
|
||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||
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.BDDMockito.given;
|
||||
|
||||
/**
|
||||
* Tests for {@link AutoConfiguredHealthEndpointSettings}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class AutoConfiguredHealthEndpointSettingsTests {
|
||||
|
||||
@Mock
|
||||
private StatusAggregator statusAggregator;
|
||||
|
||||
@Mock
|
||||
private HttpCodeStatusMapper httpCodeStatusMapper;
|
||||
|
||||
@Mock
|
||||
private SecurityContext securityContext;
|
||||
|
||||
@Mock
|
||||
private Principal principal;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
}
|
||||
|
||||
@Test
|
||||
void includeDetailsWhenShowDetailsIsNeverReturnsFalse() {
|
||||
AutoConfiguredHealthEndpointSettings settings = new AutoConfiguredHealthEndpointSettings(this.statusAggregator,
|
||||
this.httpCodeStatusMapper, ShowDetails.NEVER, Collections.emptySet());
|
||||
assertThat(settings.includeDetails(SecurityContext.NONE)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void includeDetailsWhenShowDetailsIsAlwaysReturnsTrue() {
|
||||
AutoConfiguredHealthEndpointSettings settings = new AutoConfiguredHealthEndpointSettings(this.statusAggregator,
|
||||
this.httpCodeStatusMapper, ShowDetails.ALWAYS, Collections.emptySet());
|
||||
assertThat(settings.includeDetails(SecurityContext.NONE)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void includeDetailsWhenShowDetailsIsWhenAuthorizedAndPrincipalIsNullReturnsFalse() {
|
||||
AutoConfiguredHealthEndpointSettings settings = new AutoConfiguredHealthEndpointSettings(this.statusAggregator,
|
||||
this.httpCodeStatusMapper, ShowDetails.WHEN_AUTHORIZED, Collections.emptySet());
|
||||
given(this.securityContext.getPrincipal()).willReturn(null);
|
||||
assertThat(settings.includeDetails(this.securityContext)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void includeDetailsWhenShowDetailsIsWhenAuthorizedAndRolesAreEmptyReturnsTrue() {
|
||||
AutoConfiguredHealthEndpointSettings settings = new AutoConfiguredHealthEndpointSettings(this.statusAggregator,
|
||||
this.httpCodeStatusMapper, ShowDetails.WHEN_AUTHORIZED, Collections.emptySet());
|
||||
given(this.securityContext.getPrincipal()).willReturn(this.principal);
|
||||
assertThat(settings.includeDetails(this.securityContext)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void includeDetailsWhenShowDetailsIsWhenAuthorizedAndUseIsInRoleReturnsTrue() {
|
||||
AutoConfiguredHealthEndpointSettings settings = new AutoConfiguredHealthEndpointSettings(this.statusAggregator,
|
||||
this.httpCodeStatusMapper, ShowDetails.WHEN_AUTHORIZED, Arrays.asList("admin", "root", "bossmode"));
|
||||
given(this.securityContext.getPrincipal()).willReturn(this.principal);
|
||||
given(this.securityContext.isUserInRole("root")).willReturn(true);
|
||||
assertThat(settings.includeDetails(this.securityContext)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void includeDetailsWhenShowDetailsIsWhenAuthorizedAndUseIsNotInRoleReturnsFalse() {
|
||||
AutoConfiguredHealthEndpointSettings settings = new AutoConfiguredHealthEndpointSettings(this.statusAggregator,
|
||||
this.httpCodeStatusMapper, ShowDetails.WHEN_AUTHORIZED, Arrays.asList("admin", "rot", "bossmode"));
|
||||
given(this.securityContext.getPrincipal()).willReturn(this.principal);
|
||||
given(this.securityContext.isUserInRole("root")).willReturn(true);
|
||||
assertThat(settings.includeDetails(this.securityContext)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getStatusAggregatorReturnsStatusAggregator() {
|
||||
AutoConfiguredHealthEndpointSettings settings = new AutoConfiguredHealthEndpointSettings(this.statusAggregator,
|
||||
this.httpCodeStatusMapper, ShowDetails.ALWAYS, Collections.emptySet());
|
||||
assertThat(settings.getStatusAggregator()).isSameAs(this.statusAggregator);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getHttpCodeStatusMapperReturnsHttpCodeStatusMapper() {
|
||||
AutoConfiguredHealthEndpointSettings settings = new AutoConfiguredHealthEndpointSettings(this.statusAggregator,
|
||||
this.httpCodeStatusMapper, ShowDetails.ALWAYS, Collections.emptySet());
|
||||
assertThat(settings.getHttpCodeStatusMapper()).isSameAs(this.httpCodeStatusMapper);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.autoconfigure.health;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.actuate.health.ReactiveHealthContributor;
|
||||
import org.springframework.boot.actuate.health.ReactiveHealthContributorRegistry;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link AutoConfiguredReactiveHealthContributorRegistry}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class AutoConfiguredReactiveHealthContributorRegistryTests {
|
||||
|
||||
@Test
|
||||
void createWhenContributorsClashesWithGroupNameThrowsException() {
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> new AutoConfiguredReactiveHealthContributorRegistry(
|
||||
Collections.singletonMap("boot", mock(ReactiveHealthContributor.class)),
|
||||
Arrays.asList("spring", "boot")))
|
||||
.withMessage("ReactiveHealthContributor with name \"boot\" clashes with group");
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerContributorWithGroupNameThrowsException() {
|
||||
ReactiveHealthContributorRegistry registry = new AutoConfiguredReactiveHealthContributorRegistry(
|
||||
Collections.emptyMap(), Arrays.asList("spring", "boot"));
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> registry.registerContributor("spring", mock(ReactiveHealthContributor.class)))
|
||||
.withMessage("ReactiveHealthContributor with name \"spring\" clashes with group");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.health;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A collection of {@link HealthEndpointGroup groups} for use with a health endpoint.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 2.2.0
|
||||
*/
|
||||
public interface HealthEndpointGroups {
|
||||
|
||||
/**
|
||||
* Return the primary group used by the endpoint.
|
||||
* @return the primary group (never {@code null})
|
||||
*/
|
||||
HealthEndpointGroup getPrimary();
|
||||
|
||||
/**
|
||||
* Return the names of any additional groups.
|
||||
* @return the additional group names
|
||||
*/
|
||||
Set<String> getNames();
|
||||
|
||||
/**
|
||||
* Return the group with the specified name or {@code null} if the name is not known.
|
||||
* @param name the name of the group
|
||||
* @return the {@link HealthEndpointGroup} or {@code null}
|
||||
*/
|
||||
HealthEndpointGroup get(String name);
|
||||
|
||||
/**
|
||||
* Factory method to create a {@link HealthEndpointGroups} instance.
|
||||
* @param primary the primary group
|
||||
* @param additional the additional groups
|
||||
* @return a new {@link HealthEndpointGroups} instance
|
||||
*/
|
||||
static HealthEndpointGroups of(HealthEndpointGroup primary, Map<String, HealthEndpointGroup> additional) {
|
||||
Assert.notNull(primary, "Primary must not be null");
|
||||
Assert.notNull(additional, "Additional must not be null");
|
||||
return new HealthEndpointGroups() {
|
||||
|
||||
@Override
|
||||
public HealthEndpointGroup getPrimary() {
|
||||
return primary;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getNames() {
|
||||
return additional.keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HealthEndpointGroup get(String name) {
|
||||
return additional.get(name);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.health;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
|
||||
/**
|
||||
* A {@link HealthComponent} that represents the overall system health and the available
|
||||
* groups.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 2.2.0
|
||||
*/
|
||||
public final class SystemHealth extends CompositeHealth {
|
||||
|
||||
private final Set<String> groups;
|
||||
|
||||
SystemHealth(Status status, Map<String, HealthComponent> instances, Set<String> groups) {
|
||||
super(status, instances);
|
||||
this.groups = (groups != null) ? new TreeSet<>(groups) : null;
|
||||
}
|
||||
|
||||
@JsonInclude(Include.NON_EMPTY)
|
||||
public Set<String> getGroups() {
|
||||
return this.groups;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.health;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link HealthEndpointGroups}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class HealthEndpointGroupsTests {
|
||||
|
||||
@Test
|
||||
void ofWhenPrimaryIsNullThrowsException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> HealthEndpointGroups.of(null, Collections.emptyMap()))
|
||||
.withMessage("Primary must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void ofWhenAdditionalIsNullThrowsException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> HealthEndpointGroups.of(mock(HealthEndpointGroup.class), null))
|
||||
.withMessage("Additional must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void ofReturnsHealthEndpointGroupsInstance() {
|
||||
HealthEndpointGroup primary = mock(HealthEndpointGroup.class);
|
||||
HealthEndpointGroup group = mock(HealthEndpointGroup.class);
|
||||
HealthEndpointGroups groups = HealthEndpointGroups.of(primary, Collections.singletonMap("group", group));
|
||||
assertThat(groups.getPrimary()).isSameAs(primary);
|
||||
assertThat(groups.getNames()).containsExactly("group");
|
||||
assertThat(groups.get("group")).isSameAs(group);
|
||||
assertThat(groups.get("missing")).isNull();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.health;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link SystemHealth}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class SystemHealthTests {
|
||||
|
||||
@Test
|
||||
void serializeWithJacksonReturnsValidJson() throws Exception {
|
||||
Map<String, HealthComponent> components = new LinkedHashMap<>();
|
||||
components.put("db1", Health.up().build());
|
||||
components.put("db2", Health.down().withDetail("a", "b").build());
|
||||
Set<String> groups = new LinkedHashSet<>(Arrays.asList("liveness", "readiness"));
|
||||
CompositeHealth health = new SystemHealth(Status.UP, components, groups);
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
String json = mapper.writeValueAsString(health);
|
||||
assertThat(json).isEqualTo("{\"status\":\"UP\",\"details\":{" + "\"db1\":{\"status\":\"UP\"},"
|
||||
+ "\"db2\":{\"status\":\"DOWN\",\"details\":{\"a\":\"b\"}}},"
|
||||
+ "\"groups\":[\"liveness\",\"readiness\"]}");
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue