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>
This commit is contained in:
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,22 +17,24 @@
|
||||
package org.springframework.boot.actuate.autoconfigure.health;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointProperties.ShowDetails;
|
||||
import org.springframework.boot.actuate.autoconfigure.health.HealthProperties.ShowDetails;
|
||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||
import org.springframework.boot.actuate.health.HealthEndpointSettings;
|
||||
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.CollectionUtils;
|
||||
|
||||
/**
|
||||
* Auto-configured {@link HealthEndpointSettings} backed by
|
||||
* {@link HealthEndpointProperties}.
|
||||
* Auto-configured {@link HealthEndpointGroup} backed by {@link HealthProperties}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
class AutoConfiguredHealthEndpointSettings implements HealthEndpointSettings {
|
||||
class AutoConfiguredHealthEndpointGroup implements HealthEndpointGroup {
|
||||
|
||||
private final Predicate<String> members;
|
||||
|
||||
private final StatusAggregator statusAggregator;
|
||||
|
||||
@@ -43,20 +45,27 @@ class AutoConfiguredHealthEndpointSettings implements HealthEndpointSettings {
|
||||
private final Collection<String> roles;
|
||||
|
||||
/**
|
||||
* Create a new {@link AutoConfiguredHealthEndpointSettings} instance.
|
||||
* Create a new {@link AutoConfiguredHealthEndpointGroup} instance.
|
||||
* @param members a predicate used to test for group membership
|
||||
* @param statusAggregator the status aggregator to use
|
||||
* @param httpCodeStatusMapper the HTTP code status mapper to use
|
||||
* @param showDetails the show details setting
|
||||
* @param roles the roles to match
|
||||
*/
|
||||
AutoConfiguredHealthEndpointSettings(StatusAggregator statusAggregator, HttpCodeStatusMapper httpCodeStatusMapper,
|
||||
ShowDetails showDetails, Collection<String> roles) {
|
||||
AutoConfiguredHealthEndpointGroup(Predicate<String> members, StatusAggregator statusAggregator,
|
||||
HttpCodeStatusMapper httpCodeStatusMapper, ShowDetails showDetails, Collection<String> roles) {
|
||||
this.members = members;
|
||||
this.statusAggregator = statusAggregator;
|
||||
this.httpCodeStatusMapper = httpCodeStatusMapper;
|
||||
this.showDetails = showDetails;
|
||||
this.roles = roles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMember(String name) {
|
||||
return this.members.test(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean includeDetails(SecurityContext securityContext) {
|
||||
ShowDetails showDetails = this.showDetails;
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,17 +18,16 @@ package org.springframework.boot.actuate.autoconfigure.health;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.actuate.health.DefaultHealthContributorRegistry;
|
||||
import org.springframework.boot.actuate.health.HealthContributor;
|
||||
import org.springframework.boot.actuate.health.HealthContributorRegistry;
|
||||
import org.springframework.boot.actuate.health.HealthEndpoint;
|
||||
import org.springframework.boot.actuate.health.HealthEndpointSettings;
|
||||
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.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@@ -55,27 +54,22 @@ class HealthEndpointConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
HealthEndpointSettings healthEndpointSettings(HealthEndpointProperties properties,
|
||||
ObjectProvider<StatusAggregator> statusAggregatorProvider,
|
||||
ObjectProvider<HttpCodeStatusMapper> httpCodeStatusMapperProvider) {
|
||||
StatusAggregator statusAggregator = statusAggregatorProvider
|
||||
.getIfAvailable(() -> new SimpleStatusAggregator(properties.getStatus().getOrder()));
|
||||
HttpCodeStatusMapper httpCodeStatusMapper = httpCodeStatusMapperProvider
|
||||
.getIfAvailable(() -> new SimpleHttpCodeStatusMapper(properties.getStatus().getHttpMapping()));
|
||||
return new AutoConfiguredHealthEndpointSettings(statusAggregator, httpCodeStatusMapper,
|
||||
properties.getShowDetails(), properties.getRoles());
|
||||
HealthEndpointGroups healthEndpointGroups(ApplicationContext applicationContext,
|
||||
HealthEndpointProperties properties) {
|
||||
return new AutoConfiguredHealthEndpointGroups(applicationContext, properties);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
HealthContributorRegistry healthContributorRegistry(Map<String, HealthContributor> healthContributors) {
|
||||
return new DefaultHealthContributorRegistry(healthContributors);
|
||||
HealthContributorRegistry healthContributorRegistry(Map<String, HealthContributor> healthContributors,
|
||||
HealthEndpointGroups groups) {
|
||||
return new AutoConfiguredHealthContributorRegistry(healthContributors, groups.getNames());
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
HealthEndpoint healthEndpoint(HealthContributorRegistry registry, HealthEndpointSettings settings) {
|
||||
return new HealthEndpoint(registry, settings);
|
||||
HealthEndpoint healthEndpoint(HealthContributorRegistry registry, HealthEndpointGroups groups) {
|
||||
return new HealthEndpoint(registry, groups);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,9 +16,7 @@
|
||||
|
||||
package org.springframework.boot.actuate.autoconfigure.health;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -32,93 +30,47 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@ConfigurationProperties("management.endpoint.health")
|
||||
public class HealthEndpointProperties {
|
||||
|
||||
private final Status status = new Status();
|
||||
public class HealthEndpointProperties extends HealthProperties {
|
||||
|
||||
/**
|
||||
* When to show full health details.
|
||||
* Health endpoint groups.
|
||||
*/
|
||||
private ShowDetails showDetails = ShowDetails.NEVER;
|
||||
private Map<String, Group> group = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* 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;
|
||||
public Map<String, Group> getGroup() {
|
||||
return this.group;
|
||||
}
|
||||
|
||||
/**
|
||||
* Status properties for the group.
|
||||
* A health endpoint group.
|
||||
*/
|
||||
public static class Status {
|
||||
public static class Group extends HealthProperties {
|
||||
|
||||
/**
|
||||
* Comma-separated list of health statuses in order of severity.
|
||||
* The health indicator IDs to include. Use '*' if you want to include all.
|
||||
*/
|
||||
private List<String> order = null;
|
||||
private Set<String> include;
|
||||
|
||||
/**
|
||||
* Mapping of health statuses to HTTP status codes. By default, registered health
|
||||
* statuses map to sensible defaults (for example, UP maps to 200).
|
||||
* The health indicator IDs to exclude. Use '*' if you want to exclude all.
|
||||
*/
|
||||
private final Map<String, Integer> httpMapping = new HashMap<>();
|
||||
private Set<String> exclude;
|
||||
|
||||
public List<String> getOrder() {
|
||||
return this.order;
|
||||
public Set<String> getInclude() {
|
||||
return this.include;
|
||||
}
|
||||
|
||||
public void setOrder(List<String> statusOrder) {
|
||||
if (statusOrder != null && !statusOrder.isEmpty()) {
|
||||
this.order = statusOrder;
|
||||
}
|
||||
public void setInclude(Set<String> include) {
|
||||
this.include = include;
|
||||
}
|
||||
|
||||
public Map<String, Integer> getHttpMapping() {
|
||||
return this.httpMapping;
|
||||
public Set<String> getExclude() {
|
||||
return this.exclude;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
public void setExclude(Set<String> exclude) {
|
||||
this.exclude = exclude;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
package org.springframework.boot.actuate.autoconfigure.health;
|
||||
|
||||
import org.springframework.boot.actuate.health.HealthEndpoint;
|
||||
import org.springframework.boot.actuate.health.HealthEndpointSettings;
|
||||
import org.springframework.boot.actuate.health.HealthEndpointGroups;
|
||||
import org.springframework.boot.actuate.health.ReactiveHealthContributorRegistry;
|
||||
import org.springframework.boot.actuate.health.ReactiveHealthEndpointWebExtension;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
@@ -42,8 +42,8 @@ class HealthEndpointReactiveWebExtensionConfiguration {
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnBean(HealthEndpoint.class)
|
||||
ReactiveHealthEndpointWebExtension reactiveHealthEndpointWebExtension(
|
||||
ReactiveHealthContributorRegistry reactiveHealthContributorRegistry, HealthEndpointSettings settings) {
|
||||
return new ReactiveHealthEndpointWebExtension(reactiveHealthContributorRegistry, settings);
|
||||
ReactiveHealthContributorRegistry reactiveHealthContributorRegistry, HealthEndpointGroups groups) {
|
||||
return new ReactiveHealthEndpointWebExtension(reactiveHealthContributorRegistry, groups);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ package org.springframework.boot.actuate.autoconfigure.health;
|
||||
|
||||
import org.springframework.boot.actuate.health.HealthContributorRegistry;
|
||||
import org.springframework.boot.actuate.health.HealthEndpoint;
|
||||
import org.springframework.boot.actuate.health.HealthEndpointSettings;
|
||||
import org.springframework.boot.actuate.health.HealthEndpointGroups;
|
||||
import org.springframework.boot.actuate.health.HealthEndpointWebExtension;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
@@ -42,8 +42,8 @@ class HealthEndpointWebExtensionConfiguration {
|
||||
@ConditionalOnBean(HealthEndpoint.class)
|
||||
@ConditionalOnMissingBean
|
||||
HealthEndpointWebExtension healthEndpointWebExtension(HealthContributorRegistry healthContributorRegistry,
|
||||
HealthEndpointSettings settings) {
|
||||
return new HealthEndpointWebExtension(healthContributorRegistry, settings);
|
||||
HealthEndpointGroups groups) {
|
||||
return new HealthEndpointWebExtension(healthContributorRegistry, groups);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,9 +21,9 @@ import java.util.Map;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.boot.actuate.health.DefaultReactiveHealthContributorRegistry;
|
||||
import org.springframework.boot.actuate.health.HealthContributor;
|
||||
import org.springframework.boot.actuate.health.HealthEndpoint;
|
||||
import org.springframework.boot.actuate.health.HealthEndpointGroups;
|
||||
import org.springframework.boot.actuate.health.ReactiveHealthContributor;
|
||||
import org.springframework.boot.actuate.health.ReactiveHealthContributorRegistry;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
@@ -47,11 +47,11 @@ class ReactiveHealthEndpointConfiguration {
|
||||
@ConditionalOnMissingBean
|
||||
ReactiveHealthContributorRegistry reactiveHealthContributorRegistry(
|
||||
Map<String, HealthContributor> healthContributors,
|
||||
Map<String, ReactiveHealthContributor> reactiveHealthContributors) {
|
||||
Map<String, ReactiveHealthContributor> reactiveHealthContributors, HealthEndpointGroups groups) {
|
||||
Map<String, ReactiveHealthContributor> allContributors = new LinkedHashMap<>(reactiveHealthContributors);
|
||||
healthContributors.forEach((name, contributor) -> allContributors.computeIfAbsent(name,
|
||||
(key) -> ReactiveHealthContributor.adapt(contributor)));
|
||||
return new DefaultReactiveHealthContributorRegistry(allContributors);
|
||||
return new AutoConfiguredReactiveHealthContributorRegistry(allContributors, groups.getNames());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ import org.springframework.boot.actuate.endpoint.web.WebOperation;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension;
|
||||
import org.springframework.boot.actuate.health.HealthContributorRegistry;
|
||||
import org.springframework.boot.actuate.health.HealthEndpoint;
|
||||
import org.springframework.boot.actuate.health.HealthEndpointSettings;
|
||||
import org.springframework.boot.actuate.health.HealthEndpointGroups;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@@ -112,8 +112,8 @@ class CloudFoundryWebEndpointDiscovererTests {
|
||||
@Bean
|
||||
HealthEndpoint healthEndpoint() {
|
||||
HealthContributorRegistry registry = mock(HealthContributorRegistry.class);
|
||||
HealthEndpointSettings settings = mock(HealthEndpointSettings.class);
|
||||
return new HealthEndpoint(registry, settings);
|
||||
HealthEndpointGroups groups = mock(HealthEndpointGroups.class);
|
||||
return new HealthEndpoint(registry, groups);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.boot.actuate.autoconfigure.endpoint.web.documentatio
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -33,7 +34,8 @@ import org.springframework.boot.actuate.health.Health;
|
||||
import org.springframework.boot.actuate.health.HealthContributor;
|
||||
import org.springframework.boot.actuate.health.HealthContributorRegistry;
|
||||
import org.springframework.boot.actuate.health.HealthEndpoint;
|
||||
import org.springframework.boot.actuate.health.HealthEndpointSettings;
|
||||
import org.springframework.boot.actuate.health.HealthEndpointGroup;
|
||||
import org.springframework.boot.actuate.health.HealthEndpointGroups;
|
||||
import org.springframework.boot.actuate.health.HealthIndicator;
|
||||
import org.springframework.boot.actuate.health.HttpCodeStatusMapper;
|
||||
import org.springframework.boot.actuate.health.SimpleHttpCodeStatusMapper;
|
||||
@@ -104,8 +106,9 @@ class HealthEndpointDocumentationTests extends MockMvcEndpointDocumentationTests
|
||||
@Bean
|
||||
HealthEndpoint healthEndpoint(Map<String, HealthContributor> healthContributors) {
|
||||
HealthContributorRegistry registry = new DefaultHealthContributorRegistry(healthContributors);
|
||||
HealthEndpointSettings settings = new TestHealthEndpointSettings();
|
||||
return new HealthEndpoint(registry, settings);
|
||||
HealthEndpointGroup primary = new TestHealthEndpointGroup();
|
||||
HealthEndpointGroups groups = HealthEndpointGroups.of(primary, Collections.emptyMap());
|
||||
return new HealthEndpoint(registry, groups);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -128,12 +131,17 @@ class HealthEndpointDocumentationTests extends MockMvcEndpointDocumentationTests
|
||||
|
||||
}
|
||||
|
||||
private static class TestHealthEndpointSettings implements HealthEndpointSettings {
|
||||
private static class TestHealthEndpointGroup implements HealthEndpointGroup {
|
||||
|
||||
private final StatusAggregator statusAggregator = new SimpleStatusAggregator();
|
||||
|
||||
private final HttpCodeStatusMapper httpCodeStatusMapper = new SimpleHttpCodeStatusMapper();
|
||||
|
||||
@Override
|
||||
public boolean isMember(String name) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean includeDetails(SecurityContext securityContext) {
|
||||
return true;
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,10 +16,10 @@
|
||||
|
||||
package org.springframework.boot.actuate.autoconfigure.health;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||
@@ -32,7 +32,7 @@ import org.springframework.boot.actuate.health.HealthAggregator;
|
||||
import org.springframework.boot.actuate.health.HealthComponent;
|
||||
import org.springframework.boot.actuate.health.HealthContributorRegistry;
|
||||
import org.springframework.boot.actuate.health.HealthEndpoint;
|
||||
import org.springframework.boot.actuate.health.HealthEndpointSettings;
|
||||
import org.springframework.boot.actuate.health.HealthEndpointGroups;
|
||||
import org.springframework.boot.actuate.health.HealthEndpointWebExtension;
|
||||
import org.springframework.boot.actuate.health.HealthIndicator;
|
||||
import org.springframework.boot.actuate.health.HealthStatusHttpMapper;
|
||||
@@ -50,6 +50,7 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
@@ -75,7 +76,7 @@ class HealthEndpointAutoConfigurationTests {
|
||||
this.contextRunner.withPropertyValues("management.endpoint.health.enabled=false").run((context) -> {
|
||||
assertThat(context).doesNotHaveBean(StatusAggregator.class);
|
||||
assertThat(context).doesNotHaveBean(HttpCodeStatusMapper.class);
|
||||
assertThat(context).doesNotHaveBean(HealthEndpointSettings.class);
|
||||
assertThat(context).doesNotHaveBean(HealthEndpointGroups.class);
|
||||
assertThat(context).doesNotHaveBean(HealthContributorRegistry.class);
|
||||
assertThat(context).doesNotHaveBean(HealthEndpoint.class);
|
||||
assertThat(context).doesNotHaveBean(ReactiveHealthContributorRegistry.class);
|
||||
@@ -152,19 +153,21 @@ class HealthEndpointAutoConfigurationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void runCreatesHealthEndpointSettings() {
|
||||
this.contextRunner.run((context) -> {
|
||||
HealthEndpointSettings settings = context.getBean(HealthEndpointSettings.class);
|
||||
assertThat(settings).isInstanceOf(AutoConfiguredHealthEndpointSettings.class);
|
||||
void runCreatesHealthEndpointGroups() {
|
||||
this.contextRunner.withPropertyValues("management.endpoint.health.group.ready.include=*").run((context) -> {
|
||||
HealthEndpointGroups groups = context.getBean(HealthEndpointGroups.class);
|
||||
assertThat(groups).isInstanceOf(AutoConfiguredHealthEndpointGroups.class);
|
||||
assertThat(groups.getNames()).containsOnly("ready");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void runWhenHasHealthEndpointSettingsBeanDoesNotCreateAdditionalHealthEndpointSettings() {
|
||||
this.contextRunner.withUserConfiguration(HealthEndpointSettingsConfiguration.class).run((context) -> {
|
||||
HealthEndpointSettings settings = context.getBean(HealthEndpointSettings.class);
|
||||
assertThat(Mockito.mockingDetails(settings).isMock()).isTrue();
|
||||
});
|
||||
void runWhenHasHealthEndpointGroupsBeanDoesNotCreateAdditionalHealthEndpointGroups() {
|
||||
this.contextRunner.withUserConfiguration(HealthEndpointGroupsConfiguration.class)
|
||||
.withPropertyValues("management.endpoint.health.group.ready.include=*").run((context) -> {
|
||||
HealthEndpointGroups groups = context.getBean(HealthEndpointGroups.class);
|
||||
assertThat(groups.getNames()).containsOnly("mock");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -340,11 +343,13 @@ class HealthEndpointAutoConfigurationTests {
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class HealthEndpointSettingsConfiguration {
|
||||
static class HealthEndpointGroupsConfiguration {
|
||||
|
||||
@Bean
|
||||
HealthEndpointSettings healthEndpointSettings() {
|
||||
return mock(HealthEndpointSettings.class);
|
||||
HealthEndpointGroups healthEndpointGroups() {
|
||||
HealthEndpointGroups groups = mock(HealthEndpointGroups.class);
|
||||
given(groups.getNames()).willReturn(Collections.singleton("mock"));
|
||||
return groups;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.boot.actuate.health;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
@@ -43,7 +44,7 @@ public class HealthEndpoint extends HealthEndpointSupport<HealthContributor, Hea
|
||||
* healthIndicator} to generate its response.
|
||||
* @param healthIndicator the health indicator
|
||||
* @deprecated since 2.2.0 in favor of
|
||||
* {@link #HealthEndpoint(HealthContributorRegistry, HealthEndpointSettings)}
|
||||
* {@link #HealthEndpoint(HealthContributorRegistry, HealthEndpointGroups)}
|
||||
*/
|
||||
@Deprecated
|
||||
public HealthEndpoint(HealthIndicator healthIndicator) {
|
||||
@@ -52,10 +53,10 @@ public class HealthEndpoint extends HealthEndpointSupport<HealthContributor, Hea
|
||||
/**
|
||||
* Create a new {@link HealthEndpoint} instance.
|
||||
* @param registry the health contributor registry
|
||||
* @param settings the health endpoint settings
|
||||
* @param groups the health endpoint groups
|
||||
*/
|
||||
public HealthEndpoint(HealthContributorRegistry registry, HealthEndpointSettings settings) {
|
||||
super(registry, settings);
|
||||
public HealthEndpoint(HealthContributorRegistry registry, HealthEndpointGroups groups) {
|
||||
super(registry, groups);
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
@@ -65,7 +66,8 @@ public class HealthEndpoint extends HealthEndpointSupport<HealthContributor, Hea
|
||||
|
||||
@ReadOperation
|
||||
public HealthComponent healthForPath(@Selector(match = Match.ALL_REMAINING) String... path) {
|
||||
return getHealth(SecurityContext.NONE, true, path);
|
||||
HealthResult<HealthComponent> result = getHealth(SecurityContext.NONE, true, path);
|
||||
return (result != null) ? result.getHealth() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -75,8 +77,8 @@ public class HealthEndpoint extends HealthEndpointSupport<HealthContributor, Hea
|
||||
|
||||
@Override
|
||||
protected HealthComponent aggregateContributions(Map<String, HealthComponent> contributions,
|
||||
StatusAggregator statusAggregator, boolean includeDetails) {
|
||||
return getCompositeHealth(contributions, statusAggregator, includeDetails);
|
||||
StatusAggregator statusAggregator, boolean includeDetails, Set<String> groupNames) {
|
||||
return getCompositeHealth(contributions, statusAggregator, includeDetails, groupNames);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,12 +19,20 @@ package org.springframework.boot.actuate.health;
|
||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||
|
||||
/**
|
||||
* Setting for a {@link HealthEndpoint}.
|
||||
* A logical grouping of {@link HealthContributor health contributors} that can be exposed
|
||||
* by the {@link HealthEndpoint}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 2.2.0
|
||||
*/
|
||||
public interface HealthEndpointSettings {
|
||||
public interface HealthEndpointGroup {
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the given contributor is a member of this group.
|
||||
* @param name the contributor name
|
||||
* @return {@code true} if the contributor is a member of this group
|
||||
*/
|
||||
boolean isMember(String name);
|
||||
|
||||
/**
|
||||
* Returns if {@link Health#getDetails() health details} should be included in the
|
||||
@@ -35,13 +43,13 @@ public interface HealthEndpointSettings {
|
||||
boolean includeDetails(SecurityContext securityContext);
|
||||
|
||||
/**
|
||||
* Returns the status agreggator that should be used for the endpoint.
|
||||
* @return the status aggregator
|
||||
* Returns the status agreggator that should be used for this group.
|
||||
* @return the status aggregator for this group
|
||||
*/
|
||||
StatusAggregator getStatusAggregator();
|
||||
|
||||
/**
|
||||
* Returns the {@link HttpCodeStatusMapper} that should be used for the endpoint.
|
||||
* Returns the {@link HttpCodeStatusMapper} that should be used for this group.
|
||||
* @return the HTTP code status mapper
|
||||
*/
|
||||
HttpCodeStatusMapper getHttpCodeStatusMapper();
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,6 +18,7 @@ package org.springframework.boot.actuate.health;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||
@@ -34,7 +35,7 @@ abstract class HealthEndpointSupport<C, T> {
|
||||
|
||||
private final ContributorRegistry<C> registry;
|
||||
|
||||
private final HealthEndpointSettings settings;
|
||||
private final HealthEndpointGroups groups;
|
||||
|
||||
/**
|
||||
* Throw a new {@link IllegalStateException} to indicate a constructor has been
|
||||
@@ -49,37 +50,39 @@ abstract class HealthEndpointSupport<C, T> {
|
||||
/**
|
||||
* Create a new {@link HealthEndpointSupport} instance.
|
||||
* @param registry the health contributor registry
|
||||
* @param settings the health settings
|
||||
* @param groups the health endpoint groups
|
||||
*/
|
||||
HealthEndpointSupport(ContributorRegistry<C> registry, HealthEndpointSettings settings) {
|
||||
HealthEndpointSupport(ContributorRegistry<C> registry, HealthEndpointGroups groups) {
|
||||
Assert.notNull(registry, "Registry must not be null");
|
||||
Assert.notNull(settings, "Settings must not be null");
|
||||
Assert.notNull(groups, "Groups must not be null");
|
||||
this.registry = registry;
|
||||
this.settings = settings;
|
||||
this.groups = groups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the health endpoint settings.
|
||||
* @return the settings
|
||||
*/
|
||||
protected final HealthEndpointSettings getSettings() {
|
||||
return this.settings;
|
||||
HealthResult<T> getHealth(SecurityContext securityContext, boolean alwaysIncludeDetails, String... path) {
|
||||
HealthEndpointGroup group = (path.length > 0) ? this.groups.get(path[0]) : null;
|
||||
if (group != null) {
|
||||
return getHealth(group, securityContext, alwaysIncludeDetails, path, 1);
|
||||
}
|
||||
return getHealth(this.groups.getPrimary(), securityContext, alwaysIncludeDetails, path, 0);
|
||||
}
|
||||
|
||||
T getHealth(SecurityContext securityContext, boolean alwaysIncludeDetails, String... path) {
|
||||
boolean includeDetails = alwaysIncludeDetails || this.settings.includeDetails(securityContext);
|
||||
boolean isRoot = path.length == 0;
|
||||
private HealthResult<T> getHealth(HealthEndpointGroup group, SecurityContext securityContext,
|
||||
boolean alwaysIncludeDetails, String[] path, int pathOffset) {
|
||||
boolean includeDetails = alwaysIncludeDetails || group.includeDetails(securityContext);
|
||||
boolean isSystemHealth = group == this.groups.getPrimary() && pathOffset == 0;
|
||||
boolean isRoot = path.length - pathOffset == 0;
|
||||
if (!includeDetails && !isRoot) {
|
||||
return null;
|
||||
}
|
||||
Object contributor = getContributor(path);
|
||||
return getContribution(contributor, includeDetails);
|
||||
Object contributor = getContributor(path, pathOffset);
|
||||
T health = getContribution(group, contributor, includeDetails, isSystemHealth ? this.groups.getNames() : null);
|
||||
return (health != null) ? new HealthResult<T>(health, group) : null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Object getContributor(String[] path) {
|
||||
private Object getContributor(String[] path, int pathOffset) {
|
||||
Object contributor = this.registry;
|
||||
int pathOffset = 0;
|
||||
while (pathOffset < path.length) {
|
||||
if (!(contributor instanceof NamedContributors)) {
|
||||
return null;
|
||||
@@ -91,37 +94,70 @@ abstract class HealthEndpointSupport<C, T> {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private T getContribution(Object contributor, boolean includeDetails) {
|
||||
private T getContribution(HealthEndpointGroup group, Object contributor, boolean includeDetails,
|
||||
Set<String> groupNames) {
|
||||
if (contributor instanceof NamedContributors) {
|
||||
return getAggregateHealth((NamedContributors<C>) contributor, includeDetails);
|
||||
return getAggregateHealth(group, (NamedContributors<C>) contributor, includeDetails, groupNames);
|
||||
}
|
||||
return (contributor != null) ? getHealth((C) contributor, includeDetails) : null;
|
||||
}
|
||||
|
||||
private T getAggregateHealth(NamedContributors<C> namedContributors, boolean includeDetails) {
|
||||
private T getAggregateHealth(HealthEndpointGroup group, NamedContributors<C> namedContributors,
|
||||
boolean includeDetails, Set<String> groupNames) {
|
||||
Map<String, T> contributions = new LinkedHashMap<>();
|
||||
for (NamedContributor<C> namedContributor : namedContributors) {
|
||||
String name = namedContributor.getName();
|
||||
T contribution = getContribution(namedContributor.getContributor(), includeDetails);
|
||||
contributions.put(name, contribution);
|
||||
if (group.isMember(name)) {
|
||||
T contribution = getContribution(group, namedContributor.getContributor(), includeDetails, null);
|
||||
contributions.put(name, contribution);
|
||||
}
|
||||
}
|
||||
if (contributions.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return aggregateContributions(contributions, this.settings.getStatusAggregator(), includeDetails);
|
||||
return aggregateContributions(contributions, group.getStatusAggregator(), includeDetails, groupNames);
|
||||
}
|
||||
|
||||
protected abstract T getHealth(C contributor, boolean includeDetails);
|
||||
|
||||
protected abstract T aggregateContributions(Map<String, T> contributions, StatusAggregator statusAggregator,
|
||||
boolean includeDetails);
|
||||
boolean includeDetails, Set<String> groupNames);
|
||||
|
||||
protected final CompositeHealth getCompositeHealth(Map<String, HealthComponent> components,
|
||||
StatusAggregator statusAggregator, boolean includeDetails) {
|
||||
StatusAggregator statusAggregator, boolean includeDetails, Set<String> groupNames) {
|
||||
Status status = statusAggregator.getAggregateStatus(
|
||||
components.values().stream().map(HealthComponent::getStatus).collect(Collectors.toSet()));
|
||||
Map<String, HealthComponent> includedComponents = includeDetails ? components : null;
|
||||
if (groupNames != null) {
|
||||
return new SystemHealth(status, includedComponents, groupNames);
|
||||
}
|
||||
return new CompositeHealth(status, includedComponents);
|
||||
}
|
||||
|
||||
/**
|
||||
* A health result containing health and the group that created it.
|
||||
*
|
||||
* @param <T> the contributed health component
|
||||
*/
|
||||
static class HealthResult<T> {
|
||||
|
||||
private final T health;
|
||||
|
||||
private final HealthEndpointGroup group;
|
||||
|
||||
HealthResult(T health, HealthEndpointGroup group) {
|
||||
this.health = health;
|
||||
this.group = group;
|
||||
}
|
||||
|
||||
T getHealth() {
|
||||
return this.health;
|
||||
}
|
||||
|
||||
HealthEndpointGroup getGroup() {
|
||||
return this.group;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.boot.actuate.health;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||
@@ -47,7 +48,7 @@ public class HealthEndpointWebExtension extends HealthEndpointSupport<HealthCont
|
||||
* @param delegate the delegate endpoint
|
||||
* @param responseMapper the response mapper
|
||||
* @deprecated since 2.2.0 in favor of
|
||||
* {@link #HealthEndpointWebExtension(HealthContributorRegistry, HealthEndpointSettings)}
|
||||
* {@link #HealthEndpointWebExtension(HealthContributorRegistry, HealthEndpointGroups)}
|
||||
*/
|
||||
@Deprecated
|
||||
public HealthEndpointWebExtension(HealthEndpoint delegate, HealthWebEndpointResponseMapper responseMapper) {
|
||||
@@ -56,10 +57,10 @@ public class HealthEndpointWebExtension extends HealthEndpointSupport<HealthCont
|
||||
/**
|
||||
* Create a new {@link HealthEndpointWebExtension} instance.
|
||||
* @param registry the health contributor registry
|
||||
* @param settings the health endpoint settings
|
||||
* @param groups the health endpoint groups
|
||||
*/
|
||||
public HealthEndpointWebExtension(HealthContributorRegistry registry, HealthEndpointSettings settings) {
|
||||
super(registry, settings);
|
||||
public HealthEndpointWebExtension(HealthContributorRegistry registry, HealthEndpointGroups groups) {
|
||||
super(registry, groups);
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
@@ -75,11 +76,13 @@ public class HealthEndpointWebExtension extends HealthEndpointSupport<HealthCont
|
||||
|
||||
public WebEndpointResponse<HealthComponent> health(SecurityContext securityContext, boolean alwaysIncludeDetails,
|
||||
String... path) {
|
||||
HealthComponent health = getHealth(securityContext, alwaysIncludeDetails, path);
|
||||
if (health == null) {
|
||||
HealthResult<HealthComponent> result = getHealth(securityContext, alwaysIncludeDetails, path);
|
||||
if (result == null) {
|
||||
return new WebEndpointResponse<>(WebEndpointResponse.STATUS_NOT_FOUND);
|
||||
}
|
||||
int statusCode = getSettings().getHttpCodeStatusMapper().getStatusCode(health.getStatus());
|
||||
HealthComponent health = result.getHealth();
|
||||
HealthEndpointGroup group = result.getGroup();
|
||||
int statusCode = group.getHttpCodeStatusMapper().getStatusCode(health.getStatus());
|
||||
return new WebEndpointResponse<>(health, statusCode);
|
||||
}
|
||||
|
||||
@@ -90,8 +93,8 @@ public class HealthEndpointWebExtension extends HealthEndpointSupport<HealthCont
|
||||
|
||||
@Override
|
||||
protected HealthComponent aggregateContributions(Map<String, HealthComponent> contributions,
|
||||
StatusAggregator statusAggregator, boolean includeDetails) {
|
||||
return getCompositeHealth(contributions, statusAggregator, includeDetails);
|
||||
StatusAggregator statusAggregator, boolean includeDetails, Set<String> groupNames) {
|
||||
return getCompositeHealth(contributions, statusAggregator, includeDetails, groupNames);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.boot.actuate.health;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
@@ -47,7 +48,7 @@ public class ReactiveHealthEndpointWebExtension
|
||||
* @param delegate the delegate health indicator
|
||||
* @param responseMapper the response mapper
|
||||
* @deprecated since 2.2.0 in favor of
|
||||
* {@link #ReactiveHealthEndpointWebExtension(ReactiveHealthContributorRegistry, HealthEndpointSettings)}
|
||||
* {@link #ReactiveHealthEndpointWebExtension(ReactiveHealthContributorRegistry, HealthEndpointGroups)}
|
||||
*/
|
||||
@Deprecated
|
||||
public ReactiveHealthEndpointWebExtension(ReactiveHealthIndicator delegate,
|
||||
@@ -57,11 +58,10 @@ public class ReactiveHealthEndpointWebExtension
|
||||
/**
|
||||
* Create a new {@link ReactiveHealthEndpointWebExtension} instance.
|
||||
* @param registry the health contributor registry
|
||||
* @param settings the health endpoint settings
|
||||
* @param groups the health endpoint groups
|
||||
*/
|
||||
public ReactiveHealthEndpointWebExtension(ReactiveHealthContributorRegistry registry,
|
||||
HealthEndpointSettings settings) {
|
||||
super(registry, settings);
|
||||
public ReactiveHealthEndpointWebExtension(ReactiveHealthContributorRegistry registry, HealthEndpointGroups groups) {
|
||||
super(registry, groups);
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
@@ -77,12 +77,13 @@ public class ReactiveHealthEndpointWebExtension
|
||||
|
||||
public Mono<WebEndpointResponse<? extends HealthComponent>> health(SecurityContext securityContext,
|
||||
boolean alwaysIncludeDetails, String... path) {
|
||||
Mono<? extends HealthComponent> result = getHealth(securityContext, alwaysIncludeDetails, path);
|
||||
HealthResult<Mono<? extends HealthComponent>> result = getHealth(securityContext, alwaysIncludeDetails, path);
|
||||
if (result == null) {
|
||||
return Mono.just(new WebEndpointResponse<>(WebEndpointResponse.STATUS_NOT_FOUND));
|
||||
}
|
||||
return result.map((health) -> {
|
||||
int statusCode = getSettings().getHttpCodeStatusMapper().getStatusCode(health.getStatus());
|
||||
HealthEndpointGroup group = result.getGroup();
|
||||
return result.getHealth().map((health) -> {
|
||||
int statusCode = group.getHttpCodeStatusMapper().getStatusCode(health.getStatus());
|
||||
return new WebEndpointResponse<>(health, statusCode);
|
||||
});
|
||||
}
|
||||
@@ -95,10 +96,10 @@ public class ReactiveHealthEndpointWebExtension
|
||||
@Override
|
||||
protected Mono<? extends HealthComponent> aggregateContributions(
|
||||
Map<String, Mono<? extends HealthComponent>> contributions, StatusAggregator statusAggregator,
|
||||
boolean includeDetails) {
|
||||
boolean includeDetails, Set<String> groupNames) {
|
||||
return Flux.fromIterable(contributions.entrySet()).flatMap(NamedHealthComponent::create)
|
||||
.collectMap(NamedHealthComponent::getName, NamedHealthComponent::getHealth)
|
||||
.map((components) -> this.getCompositeHealth(components, statusAggregator, includeDetails));
|
||||
.map((components) -> this.getCompositeHealth(components, statusAggregator, includeDetails, groupNames));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.boot.actuate.health;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -24,6 +25,7 @@ import org.junit.jupiter.api.Test;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||
import org.springframework.boot.actuate.health.HealthEndpointSupport.HealthResult;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
@@ -44,7 +46,12 @@ abstract class HealthEndpointSupportTests<R extends ContributorRegistry<C>, C, T
|
||||
|
||||
final Health down = Health.down().build();
|
||||
|
||||
final TestHealthEndpointSettings settings = new TestHealthEndpointSettings();
|
||||
final TestHealthEndpointGroup primaryGroup = new TestHealthEndpointGroup();
|
||||
|
||||
final TestHealthEndpointGroup allTheAs = new TestHealthEndpointGroup((name) -> name.startsWith("a"));
|
||||
|
||||
final HealthEndpointGroups groups = HealthEndpointGroups.of(this.primaryGroup,
|
||||
Collections.singletonMap("alltheas", this.allTheAs));
|
||||
|
||||
HealthEndpointSupportTests() {
|
||||
this.registry = createRegistry();
|
||||
@@ -57,65 +64,76 @@ abstract class HealthEndpointSupportTests<R extends ContributorRegistry<C>, C, T
|
||||
|
||||
@Test
|
||||
void createWhenRegistryIsNullThrowsException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> create(null, this.settings))
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> create(null, this.groups))
|
||||
.withMessage("Registry must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWhenSettingsIsNullThrowsException() {
|
||||
void createWhenGroupsIsNullThrowsException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> create(this.registry, null))
|
||||
.withMessage("Settings must not be null");
|
||||
.withMessage("Groups must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getHealthWhenPathIsEmptyReturnsHealth() {
|
||||
void getHealthResultWhenPathIsEmptyUsesPrimaryGroup() {
|
||||
this.registry.registerContributor("test", createContributor(this.up));
|
||||
T result = create(this.registry, this.settings).getHealth(SecurityContext.NONE, false);
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(SecurityContext.NONE, false);
|
||||
assertThat(result.getGroup()).isEqualTo(this.primaryGroup);
|
||||
assertThat(getHealth(result)).isNotSameAs(this.up);
|
||||
assertThat(getHealth(result).getStatus()).isEqualTo(Status.UP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getHealthWhenHasPathReturnsSubResult() {
|
||||
void getHealthResultWhenPathIsNotGroupReturnsResultFromPrimaryGroup() {
|
||||
this.registry.registerContributor("test", createContributor(this.up));
|
||||
T result = create(this.registry, this.settings).getHealth(SecurityContext.NONE, false, "test");
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(SecurityContext.NONE, false, "test");
|
||||
assertThat(result.getGroup()).isEqualTo(this.primaryGroup);
|
||||
assertThat(getHealth(result)).isEqualTo(this.up);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void getHealthWhenAlwaysIncludesDetailsIsFalseAndSettingsIsTrueIncludesDetails() {
|
||||
void getHealthResultWhenPathIsGroupReturnsResultFromGroup() {
|
||||
this.registry.registerContributor("atest", createContributor(this.up));
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(SecurityContext.NONE, false, "alltheas",
|
||||
"atest");
|
||||
assertThat(result.getGroup()).isEqualTo(this.allTheAs);
|
||||
assertThat(getHealth(result)).isEqualTo(this.up);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getHealthResultWhenAlwaysIncludesDetailsIsFalseAndGroupIsTrueIncludesDetails() {
|
||||
this.registry.registerContributor("test", createContributor(this.up));
|
||||
T result = create(this.registry, this.settings).getHealth(SecurityContext.NONE, false, "test");
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(SecurityContext.NONE, false, "test");
|
||||
assertThat(((Health) getHealth(result)).getDetails()).containsEntry("spring", "boot");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getHealthWhenAlwaysIncludesDetailsIsFalseAndSettingsIsFalseIncludesNoDetails() {
|
||||
this.settings.setIncludeDetails(false);
|
||||
void getHealthResultWhenAlwaysIncludesDetailsIsFalseAndGroupIsFalseIncludesNoDetails() {
|
||||
this.primaryGroup.setIncludeDetails(false);
|
||||
this.registry.registerContributor("test", createContributor(this.up));
|
||||
HealthEndpointSupport<C, T> endpoint = create(this.registry, this.settings);
|
||||
T rootResult = endpoint.getHealth(SecurityContext.NONE, false);
|
||||
T componentResult = endpoint.getHealth(SecurityContext.NONE, false, "test");
|
||||
HealthEndpointSupport<C, T> endpoint = create(this.registry, this.groups);
|
||||
HealthResult<T> rootResult = endpoint.getHealth(SecurityContext.NONE, false);
|
||||
HealthResult<T> componentResult = endpoint.getHealth(SecurityContext.NONE, false, "test");
|
||||
assertThat(((CompositeHealth) getHealth(rootResult)).getStatus()).isEqualTo(Status.UP);
|
||||
assertThat(componentResult).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getHealthWhenAlwaysIncludesDetailsIsTrueIncludesDetails() {
|
||||
this.settings.setIncludeDetails(false);
|
||||
void getHealthResultWhenAlwaysIncludesDetailsIsTrueIncludesDetails() {
|
||||
this.primaryGroup.setIncludeDetails(false);
|
||||
this.registry.registerContributor("test", createContributor(this.up));
|
||||
T result = create(this.registry, this.settings).getHealth(SecurityContext.NONE, true, "test");
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(SecurityContext.NONE, true, "test");
|
||||
assertThat(((Health) getHealth(result)).getDetails()).containsEntry("spring", "boot");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getHealthWhenCompositeReturnsAggregateResult() {
|
||||
void getHealthResultWhenCompositeReturnsAggregateResult() {
|
||||
Map<String, C> contributors = new LinkedHashMap<>();
|
||||
contributors.put("a", createContributor(this.up));
|
||||
contributors.put("b", createContributor(this.down));
|
||||
this.registry.registerContributor("test", createCompositeContributor(contributors));
|
||||
T result = create(this.registry, this.settings).getHealth(SecurityContext.NONE, false);
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(SecurityContext.NONE, false);
|
||||
CompositeHealth root = (CompositeHealth) getHealth(result);
|
||||
CompositeHealth component = (CompositeHealth) root.getDetails().get("test");
|
||||
assertThat(root.getStatus()).isEqualTo(Status.DOWN);
|
||||
@@ -124,12 +142,26 @@ abstract class HealthEndpointSupportTests<R extends ContributorRegistry<C>, C, T
|
||||
}
|
||||
|
||||
@Test
|
||||
void getHealthWhenPathDoesNotExistReturnsNull() {
|
||||
T result = create(this.registry, this.settings).getHealth(SecurityContext.NONE, false, "missing");
|
||||
void getHealthResultWhenPathDoesNotExistReturnsNull() {
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(SecurityContext.NONE, false, "missing");
|
||||
assertThat(result).isNull();
|
||||
}
|
||||
|
||||
protected abstract HealthEndpointSupport<C, T> create(R registry, HealthEndpointSettings settings);
|
||||
@Test
|
||||
void getHealthResultWhenPathIsEmptyIncludesGroups() {
|
||||
this.registry.registerContributor("test", createContributor(this.up));
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(SecurityContext.NONE, false);
|
||||
assertThat(((SystemHealth) getHealth(result)).getGroups()).containsOnly("alltheas");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getHealthResultWhenPathIsGroupDoesNotIncludesGroups() {
|
||||
this.registry.registerContributor("atest", createContributor(this.up));
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(SecurityContext.NONE, false, "alltheas");
|
||||
assertThat(getHealth(result)).isNotInstanceOf(SystemHealth.class);
|
||||
}
|
||||
|
||||
protected abstract HealthEndpointSupport<C, T> create(R registry, HealthEndpointGroups groups);
|
||||
|
||||
protected abstract R createRegistry();
|
||||
|
||||
@@ -137,6 +169,6 @@ abstract class HealthEndpointSupportTests<R extends ContributorRegistry<C>, C, T
|
||||
|
||||
protected abstract C createCompositeContributor(Map<String, C> contributors);
|
||||
|
||||
protected abstract HealthComponent getHealth(T result);
|
||||
protected abstract HealthComponent getHealth(HealthResult<T> result);
|
||||
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.actuate.health.HealthEndpointSupport.HealthResult;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
import static org.mockito.Mockito.mock;
|
||||
@@ -42,30 +44,30 @@ class HealthEndpointTests
|
||||
}
|
||||
|
||||
@Test
|
||||
void healthReturnsCompositeHealth() {
|
||||
void healthReturnsSystemHealth() {
|
||||
this.registry.registerContributor("test", createContributor(this.up));
|
||||
HealthComponent health = create(this.registry, this.settings).health();
|
||||
HealthComponent health = create(this.registry, this.groups).health();
|
||||
assertThat(health.getStatus()).isEqualTo(Status.UP);
|
||||
assertThat(health).isInstanceOf(CompositeHealth.class);
|
||||
assertThat(health).isInstanceOf(SystemHealth.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void healthWhenPathDoesNotExistReturnsNull() {
|
||||
this.registry.registerContributor("test", createContributor(this.up));
|
||||
HealthComponent health = create(this.registry, this.settings).healthForPath("missing");
|
||||
HealthComponent health = create(this.registry, this.groups).healthForPath("missing");
|
||||
assertThat(health).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void healthWhenPathExistsReturnsHealth() {
|
||||
this.registry.registerContributor("test", createContributor(this.up));
|
||||
HealthComponent health = create(this.registry, this.settings).healthForPath("test");
|
||||
HealthComponent health = create(this.registry, this.groups).healthForPath("test");
|
||||
assertThat(health).isEqualTo(this.up);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HealthEndpoint create(HealthContributorRegistry registry, HealthEndpointSettings settings) {
|
||||
return new HealthEndpoint(registry, settings);
|
||||
protected HealthEndpoint create(HealthContributorRegistry registry, HealthEndpointGroups groups) {
|
||||
return new HealthEndpoint(registry, groups);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -84,8 +86,8 @@ class HealthEndpointTests
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HealthComponent getHealth(HealthComponent result) {
|
||||
return result;
|
||||
protected HealthComponent getHealth(HealthResult<HealthComponent> result) {
|
||||
return result.getHealth();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
||||
import org.springframework.boot.actuate.health.HealthEndpointSupport.HealthResult;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
@@ -49,19 +50,18 @@ class HealthEndpointWebExtensionTests
|
||||
@Test
|
||||
void healthReturnsSystemHealth() {
|
||||
this.registry.registerContributor("test", createContributor(this.up));
|
||||
WebEndpointResponse<HealthComponent> response = create(this.registry, this.settings)
|
||||
.health(SecurityContext.NONE);
|
||||
WebEndpointResponse<HealthComponent> response = create(this.registry, this.groups).health(SecurityContext.NONE);
|
||||
HealthComponent health = response.getBody();
|
||||
assertThat(health.getStatus()).isEqualTo(Status.UP);
|
||||
assertThat(health).isInstanceOf(CompositeHealth.class);
|
||||
assertThat(health).isInstanceOf(SystemHealth.class);
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
}
|
||||
|
||||
@Test
|
||||
void healthWhenPathDoesNotExistReturnsHttp404() {
|
||||
this.registry.registerContributor("test", createContributor(this.up));
|
||||
WebEndpointResponse<HealthComponent> response = create(this.registry, this.settings)
|
||||
.health(SecurityContext.NONE, "missing");
|
||||
WebEndpointResponse<HealthComponent> response = create(this.registry, this.groups).health(SecurityContext.NONE,
|
||||
"missing");
|
||||
assertThat(response.getBody()).isNull();
|
||||
assertThat(response.getStatus()).isEqualTo(404);
|
||||
}
|
||||
@@ -69,15 +69,15 @@ class HealthEndpointWebExtensionTests
|
||||
@Test
|
||||
void healthWhenPathExistsReturnsHealth() {
|
||||
this.registry.registerContributor("test", createContributor(this.up));
|
||||
WebEndpointResponse<HealthComponent> response = create(this.registry, this.settings)
|
||||
.health(SecurityContext.NONE, "test");
|
||||
WebEndpointResponse<HealthComponent> response = create(this.registry, this.groups).health(SecurityContext.NONE,
|
||||
"test");
|
||||
assertThat(response.getBody()).isEqualTo(this.up);
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HealthEndpointWebExtension create(HealthContributorRegistry registry, HealthEndpointSettings settings) {
|
||||
return new HealthEndpointWebExtension(registry, settings);
|
||||
protected HealthEndpointWebExtension create(HealthContributorRegistry registry, HealthEndpointGroups groups) {
|
||||
return new HealthEndpointWebExtension(registry, groups);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -96,8 +96,8 @@ class HealthEndpointWebExtensionTests
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HealthComponent getHealth(HealthComponent result) {
|
||||
return result;
|
||||
protected HealthComponent getHealth(HealthResult<HealthComponent> result) {
|
||||
return result.getHealth();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -156,28 +156,30 @@ class HealthEndpointWebIntegrationTests {
|
||||
|
||||
@Bean
|
||||
HealthEndpoint healthEndpoint(HealthContributorRegistry healthContributorRegistry,
|
||||
HealthEndpointSettings healthEndpointSettings) {
|
||||
return new HealthEndpoint(healthContributorRegistry, healthEndpointSettings);
|
||||
HealthEndpointGroups healthEndpointGroups) {
|
||||
return new HealthEndpoint(healthContributorRegistry, healthEndpointGroups);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnWebApplication(type = Type.SERVLET)
|
||||
HealthEndpointWebExtension healthWebEndpointExtension(HealthContributorRegistry healthContributorRegistry,
|
||||
HealthEndpointSettings healthEndpointSettings) {
|
||||
return new HealthEndpointWebExtension(healthContributorRegistry, healthEndpointSettings);
|
||||
HealthEndpointGroups healthEndpointGroups) {
|
||||
return new HealthEndpointWebExtension(healthContributorRegistry, healthEndpointGroups);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnWebApplication(type = Type.REACTIVE)
|
||||
ReactiveHealthEndpointWebExtension reactiveHealthWebEndpointExtension(
|
||||
ReactiveHealthContributorRegistry reactiveHealthContributorRegistry,
|
||||
HealthEndpointSettings healthEndpointSettings) {
|
||||
return new ReactiveHealthEndpointWebExtension(reactiveHealthContributorRegistry, healthEndpointSettings);
|
||||
HealthEndpointGroups healthEndpointGroups) {
|
||||
return new ReactiveHealthEndpointWebExtension(reactiveHealthContributorRegistry, healthEndpointGroups);
|
||||
}
|
||||
|
||||
@Bean
|
||||
HealthEndpointSettings healthEndpointSettings() {
|
||||
return new TestHealthEndpointSettings();
|
||||
HealthEndpointGroups healthEndpointGroups() {
|
||||
TestHealthEndpointGroup primary = new TestHealthEndpointGroup();
|
||||
TestHealthEndpointGroup allTheAs = new TestHealthEndpointGroup((name) -> name.startsWith("a"));
|
||||
return HealthEndpointGroups.of(primary, Collections.singletonMap("alltheas", allTheAs));
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -23,6 +23,7 @@ import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
||||
import org.springframework.boot.actuate.health.HealthEndpointSupport.HealthResult;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
@@ -50,18 +51,18 @@ class ReactiveHealthEndpointWebExtensionTests extends
|
||||
@Test
|
||||
void healthReturnsSystemHealth() {
|
||||
this.registry.registerContributor("test", createContributor(this.up));
|
||||
WebEndpointResponse<? extends HealthComponent> response = create(this.registry, this.settings)
|
||||
WebEndpointResponse<? extends HealthComponent> response = create(this.registry, this.groups)
|
||||
.health(SecurityContext.NONE).block();
|
||||
HealthComponent health = response.getBody();
|
||||
assertThat(health.getStatus()).isEqualTo(Status.UP);
|
||||
assertThat(health).isInstanceOf(CompositeHealth.class);
|
||||
assertThat(health).isInstanceOf(SystemHealth.class);
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
}
|
||||
|
||||
@Test
|
||||
void healthWhenPathDoesNotExistReturnsHttp404() {
|
||||
this.registry.registerContributor("test", createContributor(this.up));
|
||||
WebEndpointResponse<? extends HealthComponent> response = create(this.registry, this.settings)
|
||||
WebEndpointResponse<? extends HealthComponent> response = create(this.registry, this.groups)
|
||||
.health(SecurityContext.NONE, "missing").block();
|
||||
assertThat(response.getBody()).isNull();
|
||||
assertThat(response.getStatus()).isEqualTo(404);
|
||||
@@ -70,7 +71,7 @@ class ReactiveHealthEndpointWebExtensionTests extends
|
||||
@Test
|
||||
void healthWhenPathExistsReturnsHealth() {
|
||||
this.registry.registerContributor("test", createContributor(this.up));
|
||||
WebEndpointResponse<? extends HealthComponent> response = create(this.registry, this.settings)
|
||||
WebEndpointResponse<? extends HealthComponent> response = create(this.registry, this.groups)
|
||||
.health(SecurityContext.NONE, "test").block();
|
||||
assertThat(response.getBody()).isEqualTo(this.up);
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
@@ -78,8 +79,8 @@ class ReactiveHealthEndpointWebExtensionTests extends
|
||||
|
||||
@Override
|
||||
protected ReactiveHealthEndpointWebExtension create(ReactiveHealthContributorRegistry registry,
|
||||
HealthEndpointSettings settings) {
|
||||
return new ReactiveHealthEndpointWebExtension(registry, settings);
|
||||
HealthEndpointGroups groups) {
|
||||
return new ReactiveHealthEndpointWebExtension(registry, groups);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -99,8 +100,8 @@ class ReactiveHealthEndpointWebExtensionTests extends
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HealthComponent getHealth(Mono<? extends HealthComponent> result) {
|
||||
return result.block();
|
||||
protected HealthComponent getHealth(HealthResult<Mono<? extends HealthComponent>> result) {
|
||||
return result.getHealth().block();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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\"]}");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,21 +16,38 @@
|
||||
|
||||
package org.springframework.boot.actuate.health;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||
|
||||
/**
|
||||
* Test implementation of {@link HealthEndpointSettings}.
|
||||
* Test implementation of {@link HealthEndpointGroups}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class TestHealthEndpointSettings implements HealthEndpointSettings {
|
||||
class TestHealthEndpointGroup implements HealthEndpointGroup {
|
||||
|
||||
private final StatusAggregator statusAggregator = new SimpleStatusAggregator();
|
||||
|
||||
private final HttpCodeStatusMapper httpCodeStatusMapper = new SimpleHttpCodeStatusMapper();
|
||||
|
||||
private final Predicate<String> memberPredicate;
|
||||
|
||||
private boolean includeDetails = true;
|
||||
|
||||
TestHealthEndpointGroup() {
|
||||
this((name) -> true);
|
||||
}
|
||||
|
||||
TestHealthEndpointGroup(Predicate<String> memberPredicate) {
|
||||
this.memberPredicate = memberPredicate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMember(String name) {
|
||||
return this.memberPredicate.test(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean includeDetails(SecurityContext securityContext) {
|
||||
return this.includeDetails;
|
||||
@@ -951,6 +951,42 @@ TIP: If necessary, reactive indicators replace the regular ones. Also, any
|
||||
|
||||
|
||||
|
||||
==== Health Groups
|
||||
It's sometimes useful to organize health indicators into groups that can be used for
|
||||
different purposes. For example, if you deploy your application to Kubernetes, you may
|
||||
want one different sets of health indicators for your "`liveness`" and "`readiness`"
|
||||
probes.
|
||||
|
||||
To create a health indicator group you can use the `management.endpoint.health.group.<name>`
|
||||
property and specify a list of health indicator IDs to `include` or `exclude`. For example,
|
||||
to create a group that includes only database indicators you can define the following:
|
||||
|
||||
[source,properties,indent=0]
|
||||
----
|
||||
management.endpoint.health.group.custom.include=db
|
||||
----
|
||||
|
||||
You can then check the result by hitting `http://localhost:8080/actuator/health/custom`.
|
||||
|
||||
By default groups will inherit the same `StatusAggregator` and `HttpCodeStatusMapper`
|
||||
settings as the system health, however, these can also be defined on a per-group
|
||||
basis. It's also possible to override the `show-details` and `roles` properties
|
||||
if required:
|
||||
|
||||
[source,properties,indent=0]
|
||||
----
|
||||
management.endpoint.health.group.custom.show-details=when-authorized
|
||||
management.endpoint.health.group.custom.roles=admin
|
||||
management.endpoint.health.group.custom.status.order=fatal,up
|
||||
management.endpoint.health.group.custom.status.http-mapping.fatal=500
|
||||
|
||||
----
|
||||
|
||||
TIP: You can use `@Qualifier("groupname")` if you need to register custom
|
||||
`StatusAggregator` or `HttpCodeStatusMapper` beans for use with the group.
|
||||
|
||||
|
||||
|
||||
[[production-ready-application-info]]
|
||||
=== Application Information
|
||||
Application information exposes various information collected from all
|
||||
|
||||
@@ -19,3 +19,8 @@ spring.jmx.enabled=true
|
||||
spring.jackson.serialization.write_dates_as_timestamps=false
|
||||
|
||||
management.trace.http.include=request-headers,response-headers,principal,remote-address,session-id
|
||||
|
||||
management.endpoint.health.show-details=always
|
||||
management.endpoint.health.group.ready.include=db,diskSpace
|
||||
management.endpoint.health.group.live.include=example,hello,db
|
||||
management.endpoint.health.group.live.show-details=never
|
||||
|
||||
@@ -75,7 +75,7 @@ class ManagementPortAndPathSampleActuatorApplicationTests {
|
||||
ResponseEntity<String> entity = new TestRestTemplate().withBasicAuth("user", getPassword())
|
||||
.getForEntity("http://localhost:" + this.managementPort + "/admin/health", String.class);
|
||||
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
assertThat(entity.getBody()).isEqualTo("{\"status\":\"UP\"}");
|
||||
assertThat(entity.getBody()).isEqualTo("{\"status\":\"UP\",\"groups\":[\"live\",\"ready\"]}");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user