Commit 5311c044 authored by Phillip Webb's avatar Phillip Webb

Change HealthEndpointGroups customization support

Update the `HealthEndpointGroups` customization support to use a
post-processor rather than a mutable registry. Although this approach
is slightly less flexible, it removes a lot of complexity from the
`HealthEndpointGroups` code. Specifically, it allows us to drop the
`HealthEndpointGroupsRegistry` interface entirely.

The probe health groups are now added via the post-processor if they
aren't already defined. Unlike the previous implementation, users are
no longer able to customize status aggregation and http status code
mapping rules _unless_ they also re-define the health indicators that
are members of the group.

See gh-20962
parent f32a37e2
...@@ -20,7 +20,6 @@ import org.springframework.boot.actuate.autoconfigure.availability.AvailabilityP ...@@ -20,7 +20,6 @@ import org.springframework.boot.actuate.autoconfigure.availability.AvailabilityP
import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator; import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator;
import org.springframework.boot.actuate.availability.LivenessStateHealthIndicator; import org.springframework.boot.actuate.availability.LivenessStateHealthIndicator;
import org.springframework.boot.actuate.availability.ReadinessStateHealthIndicator; import org.springframework.boot.actuate.availability.ReadinessStateHealthIndicator;
import org.springframework.boot.actuate.health.HealthEndpointGroupsRegistryCustomizer;
import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration; import org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration;
...@@ -38,11 +37,10 @@ import org.springframework.core.env.Environment; ...@@ -38,11 +37,10 @@ import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.core.type.AnnotatedTypeMetadata;
/** /**
* {@link EnableAutoConfiguration Auto-configuration} for * {@link EnableAutoConfiguration Auto-configuration} for availability probes.
* {@link LivenessStateHealthIndicator}, {@link ReadinessStateHealthIndicator} and
* {@link HealthEndpointGroupsRegistryCustomizer}.
* *
* @author Brian Clozel * @author Brian Clozel
* @author Phillip Webb
* @since 2.3.0 * @since 2.3.0
*/ */
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
...@@ -66,8 +64,8 @@ public class AvailabilityProbesAutoConfiguration { ...@@ -66,8 +64,8 @@ public class AvailabilityProbesAutoConfiguration {
} }
@Bean @Bean
public HealthEndpointGroupsRegistryCustomizer probesRegistryCustomizer() { public AvailabilityProbesHealthEndpointGroupsPostProcessor availabilityProbesHealthEndpointGroupsPostProcessor() {
return new AvailabilityProbesHealthEndpointGroupsRegistrar(); return new AvailabilityProbesHealthEndpointGroupsPostProcessor();
} }
/** /**
...@@ -75,15 +73,17 @@ public class AvailabilityProbesAutoConfiguration { ...@@ -75,15 +73,17 @@ public class AvailabilityProbesAutoConfiguration {
*/ */
static class ProbesCondition extends SpringBootCondition { static class ProbesCondition extends SpringBootCondition {
private static final String ENABLED_PROPERTY = "management.health.probes.enabled";
@Override @Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment environment = context.getEnvironment(); Environment environment = context.getEnvironment();
ConditionMessage.Builder message = ConditionMessage.forCondition("Health probes"); ConditionMessage.Builder message = ConditionMessage.forCondition("Health availability");
String enabled = environment.getProperty("management.health.probes.enabled"); String enabled = environment.getProperty(ENABLED_PROPERTY);
if (enabled != null) { if (enabled != null) {
boolean match = !"false".equalsIgnoreCase(enabled); boolean match = !"false".equalsIgnoreCase(enabled);
return new ConditionOutcome(match, return new ConditionOutcome(match,
message.because("'management.health.probes.enabled' set to '" + enabled + "'")); message.because("'" + ENABLED_PROPERTY + "' set to '" + enabled + "'"));
} }
if (CloudPlatform.getActive(environment) == CloudPlatform.KUBERNETES) { if (CloudPlatform.getActive(environment) == CloudPlatform.KUBERNETES) {
return ConditionOutcome.match(message.because("running on Kubernetes")); return ConditionOutcome.match(message.because("running on Kubernetes"));
......
...@@ -14,37 +14,54 @@ ...@@ -14,37 +14,54 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.actuate.health; package org.springframework.boot.actuate.autoconfigure.availability;
import java.util.function.Consumer; import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import org.springframework.boot.actuate.endpoint.SecurityContext;
import org.springframework.boot.actuate.health.HealthEndpointGroup;
import org.springframework.boot.actuate.health.HttpCodeStatusMapper;
import org.springframework.boot.actuate.health.StatusAggregator;
/** /**
* Builder for an {@link HealthEndpointGroups} immutable instance. * {@link HealthEndpointGroup} used to support availability probes.
* *
* @author Phillip Webb
* @author Brian Clozel * @author Brian Clozel
* @since 2.3.0
*/ */
public interface HealthEndpointGroupsRegistry extends HealthEndpointGroups { class AvailabilityProbesHealthEndpointGroup implements HealthEndpointGroup {
/** private final Set<String> members;
* Add a new {@link HealthEndpointGroup}.
* @param groupName the name of the group to add AvailabilityProbesHealthEndpointGroup(String... members) {
* @param builder the group to add this.members = new HashSet<>(Arrays.asList(members));
* @return the builder instance }
*/
HealthEndpointGroupsRegistry add(String groupName, Consumer<HealthEndpointGroupConfigurer> builder); @Override
public boolean isMember(String name) {
/** return this.members.contains(name);
* Remove an existing {@link HealthEndpointGroup}. }
* @param groupName the name of the group to remove
* @return the builder instance @Override
*/ public boolean showComponents(SecurityContext securityContext) {
HealthEndpointGroupsRegistry remove(String groupName); return false;
}
/**
* Build an immutable {@link HealthEndpointGroups}. @Override
* @return the {@link HealthEndpointGroups} public boolean showDetails(SecurityContext securityContext) {
*/ return false;
HealthEndpointGroups toGroups(); }
@Override
public StatusAggregator getStatusAggregator() {
return StatusAggregator.DEFAULT;
}
@Override
public HttpCodeStatusMapper getHttpCodeStatusMapper() {
return HttpCodeStatusMapper.DEFAULT;
}
} }
...@@ -16,50 +16,65 @@ ...@@ -16,50 +16,65 @@
package org.springframework.boot.actuate.autoconfigure.availability; package org.springframework.boot.actuate.autoconfigure.availability;
import org.junit.jupiter.api.Test; import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import org.springframework.boot.actuate.autoconfigure.availability.AvailabilityProbesHealthEndpointGroupsRegistrar;
import org.springframework.boot.actuate.health.HealthEndpointGroup; import org.springframework.boot.actuate.health.HealthEndpointGroup;
import org.springframework.boot.actuate.health.HealthEndpointGroupsRegistry; import org.springframework.boot.actuate.health.HealthEndpointGroups;
import org.springframework.boot.actuate.health.TestHealthEndpointGroupsRegistry; import org.springframework.util.Assert;
import static org.assertj.core.api.Assertions.assertThat;
/** /**
* Tests for {@code ProbesHealthEndpointGroupsRegistrar} * {@link HealthEndpointGroups} decorator to support availability probes.
* *
* @author Phillip Webb
* @author Brian Clozel * @author Brian Clozel
*/ */
class AvailabilityProbesHealthEndpointGroupsRegistrarTests { class AvailabilityProbesHealthEndpointGroups implements HealthEndpointGroups {
private static final Map<String, AvailabilityProbesHealthEndpointGroup> GROUPS;
static {
Map<String, AvailabilityProbesHealthEndpointGroup> groups = new LinkedHashMap<>();
groups.put("liveness", new AvailabilityProbesHealthEndpointGroup("livenessState"));
groups.put("readiness", new AvailabilityProbesHealthEndpointGroup("readinessState"));
GROUPS = Collections.unmodifiableMap(groups);
}
private final HealthEndpointGroups groups;
private AvailabilityProbesHealthEndpointGroupsRegistrar registrar = new AvailabilityProbesHealthEndpointGroupsRegistrar(); private final Set<String> names;
@Test AvailabilityProbesHealthEndpointGroups(HealthEndpointGroups groups) {
void shouldAddKubernetesProbes() { Assert.notNull(groups, "Groups must not be null");
HealthEndpointGroupsRegistry registry = new TestHealthEndpointGroupsRegistry(); this.groups = groups;
this.registrar.customize(registry); Set<String> names = new LinkedHashSet<>(groups.getNames());
HealthEndpointGroup liveness = registry.get("liveness"); names.addAll(GROUPS.keySet());
assertThat(liveness).isNotNull(); this.names = Collections.unmodifiableSet(names);
assertThat(liveness.isMember("livenessProbe")).isTrue();
HealthEndpointGroup readiness = registry.get("readiness");
assertThat(readiness).isNotNull();
assertThat(readiness.isMember("readinessProbe")).isTrue();
} }
@Test @Override
void shouldNotAddProbeIfGroupExists() { public HealthEndpointGroup getPrimary() {
HealthEndpointGroupsRegistry registry = new TestHealthEndpointGroupsRegistry(); return this.groups.getPrimary();
registry.add("readiness", (configurer) -> configurer.include("test")); }
this.registrar.customize(registry);
@Override
public Set<String> getNames() {
return this.names;
}
HealthEndpointGroup liveness = registry.get("liveness"); @Override
assertThat(liveness).isNotNull(); public HealthEndpointGroup get(String name) {
assertThat(liveness.isMember("livenessProbe")).isTrue(); HealthEndpointGroup group = this.groups.get(name);
if (group == null) {
group = GROUPS.get(name);
}
return group;
}
HealthEndpointGroup readiness = registry.get("readiness"); static boolean containsAllProbeGroups(HealthEndpointGroups groups) {
assertThat(readiness).isNotNull(); return groups.getNames().containsAll(GROUPS.keySet());
assertThat(readiness.isMember("readinessProbe")).isFalse();
assertThat(readiness.isMember("test")).isTrue();
} }
} }
...@@ -16,35 +16,26 @@ ...@@ -16,35 +16,26 @@
package org.springframework.boot.actuate.autoconfigure.availability; package org.springframework.boot.actuate.autoconfigure.availability;
import org.springframework.boot.actuate.health.HealthEndpointGroup; import org.springframework.boot.actuate.health.HealthEndpointGroups;
import org.springframework.boot.actuate.health.HealthEndpointGroupsRegistry; import org.springframework.boot.actuate.health.HealthEndpointGroupsPostProcessor;
import org.springframework.boot.actuate.health.HealthEndpointGroupsRegistryCustomizer; import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
/** /**
* {@link HealthEndpointGroupsRegistryCustomizer} that registers {@code "liveness"} and * {@link HealthEndpointGroupsPostProcessor} to add
* {@code "readiness"} {@link HealthEndpointGroup groups} if they don't exist already. * {@link AvailabilityProbesHealthEndpointGroups}.
* *
* @author Brian Clozel * @author Phillip Webb
* @since 2.3.0
*/ */
public class AvailabilityProbesHealthEndpointGroupsRegistrar implements HealthEndpointGroupsRegistryCustomizer { @Order(Ordered.LOWEST_PRECEDENCE)
class AvailabilityProbesHealthEndpointGroupsPostProcessor implements HealthEndpointGroupsPostProcessor {
private static final String LIVENESS_GROUP_NAME = "liveness";
private static final String READINESS_GROUP_NAME = "readiness";
private static final String LIVENESS_PROBE_INDICATOR = "livenessProbe";
private static final String READINESS_PROBE_INDICATOR = "readinessProbe";
@Override @Override
public void customize(HealthEndpointGroupsRegistry registry) { public HealthEndpointGroups postProcessHealthEndpointGroups(HealthEndpointGroups groups) {
if (registry.get(LIVENESS_GROUP_NAME) == null) { if (AvailabilityProbesHealthEndpointGroups.containsAllProbeGroups(groups)) {
registry.add(LIVENESS_GROUP_NAME, (configurer) -> configurer.include(LIVENESS_PROBE_INDICATOR)); return groups;
}
if (registry.get(READINESS_GROUP_NAME) == null) {
registry.add(READINESS_GROUP_NAME, (configurer) -> configurer.include(READINESS_PROBE_INDICATOR));
} }
return new AvailabilityProbesHealthEndpointGroups(groups);
} }
} }
...@@ -20,6 +20,7 @@ import java.security.Principal; ...@@ -20,6 +20,7 @@ import java.security.Principal;
import java.util.Collection; import java.util.Collection;
import java.util.function.Predicate; import java.util.function.Predicate;
import org.springframework.boot.actuate.autoconfigure.health.HealthProperties.Show;
import org.springframework.boot.actuate.endpoint.SecurityContext; import org.springframework.boot.actuate.endpoint.SecurityContext;
import org.springframework.boot.actuate.health.HealthEndpointGroup; import org.springframework.boot.actuate.health.HealthEndpointGroup;
import org.springframework.boot.actuate.health.HttpCodeStatusMapper; import org.springframework.boot.actuate.health.HttpCodeStatusMapper;
...@@ -30,12 +31,12 @@ import org.springframework.util.ClassUtils; ...@@ -30,12 +31,12 @@ import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
/** /**
* Auto-configured {@link HealthEndpointGroup}. * Auto-configured {@link HealthEndpointGroup} backed by {@link HealthProperties}.
* *
* @author Phillip Webb * @author Phillip Webb
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
class DefaultHealthEndpointGroup implements HealthEndpointGroup { class AutoConfiguredHealthEndpointGroup implements HealthEndpointGroup {
private final Predicate<String> members; private final Predicate<String> members;
...@@ -50,7 +51,7 @@ class DefaultHealthEndpointGroup implements HealthEndpointGroup { ...@@ -50,7 +51,7 @@ class DefaultHealthEndpointGroup implements HealthEndpointGroup {
private final Collection<String> roles; private final Collection<String> roles;
/** /**
* Create a new {@link DefaultHealthEndpointGroup} instance. * Create a new {@link AutoConfiguredHealthEndpointGroup} instance.
* @param members a predicate used to test for group membership * @param members a predicate used to test for group membership
* @param statusAggregator the status aggregator to use * @param statusAggregator the status aggregator to use
* @param httpCodeStatusMapper the HTTP code status mapper to use * @param httpCodeStatusMapper the HTTP code status mapper to use
...@@ -58,7 +59,7 @@ class DefaultHealthEndpointGroup implements HealthEndpointGroup { ...@@ -58,7 +59,7 @@ class DefaultHealthEndpointGroup implements HealthEndpointGroup {
* @param showDetails the show details setting * @param showDetails the show details setting
* @param roles the roles to match * @param roles the roles to match
*/ */
DefaultHealthEndpointGroup(Predicate<String> members, StatusAggregator statusAggregator, AutoConfiguredHealthEndpointGroup(Predicate<String> members, StatusAggregator statusAggregator,
HttpCodeStatusMapper httpCodeStatusMapper, Show showComponents, Show showDetails, HttpCodeStatusMapper httpCodeStatusMapper, Show showComponents, Show showDetails,
Collection<String> roles) { Collection<String> roles) {
this.members = members; this.members = members;
......
...@@ -22,7 +22,6 @@ import java.util.LinkedHashMap; ...@@ -22,7 +22,6 @@ import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.function.Supplier; import java.util.function.Supplier;
...@@ -32,12 +31,10 @@ import org.springframework.beans.factory.ListableBeanFactory; ...@@ -32,12 +31,10 @@ import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils; import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils;
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointProperties.Group; import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointProperties.Group;
import org.springframework.boot.actuate.autoconfigure.health.HealthProperties.Show;
import org.springframework.boot.actuate.autoconfigure.health.HealthProperties.Status; import org.springframework.boot.actuate.autoconfigure.health.HealthProperties.Status;
import org.springframework.boot.actuate.health.HealthEndpointGroup; import org.springframework.boot.actuate.health.HealthEndpointGroup;
import org.springframework.boot.actuate.health.HealthEndpointGroup.Show;
import org.springframework.boot.actuate.health.HealthEndpointGroupConfigurer;
import org.springframework.boot.actuate.health.HealthEndpointGroups; import org.springframework.boot.actuate.health.HealthEndpointGroups;
import org.springframework.boot.actuate.health.HealthEndpointGroupsRegistry;
import org.springframework.boot.actuate.health.HttpCodeStatusMapper; import org.springframework.boot.actuate.health.HttpCodeStatusMapper;
import org.springframework.boot.actuate.health.SimpleHttpCodeStatusMapper; import org.springframework.boot.actuate.health.SimpleHttpCodeStatusMapper;
import org.springframework.boot.actuate.health.SimpleStatusAggregator; import org.springframework.boot.actuate.health.SimpleStatusAggregator;
...@@ -48,113 +45,71 @@ import org.springframework.util.CollectionUtils; ...@@ -48,113 +45,71 @@ import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
/** /**
* Auto-configured {@link HealthEndpointGroupsRegistry}. * Auto-configured {@link HealthEndpointGroups}.
* *
* @author Phillip Webb * @author Phillip Webb
* @author Brian Clozel
*/ */
class AutoConfiguredHealthEndpointGroupsRegistry implements HealthEndpointGroupsRegistry { class AutoConfiguredHealthEndpointGroups implements HealthEndpointGroups {
private static Predicate<String> ALL = (name) -> true; private static Predicate<String> ALL = (name) -> true;
private final StatusAggregator defaultStatusAggregator; private final HealthEndpointGroup primaryGroup;
private final HttpCodeStatusMapper defaultHttpCodeStatusMapper;
private final Show defaultShowComponents;
private final Show defaultShowDetails;
private final Set<String> defaultRoles;
private HealthEndpointGroup primaryGroup;
private final Map<String, HealthEndpointGroup> groups; private final Map<String, HealthEndpointGroup> groups;
/** /**
* Create a new {@link AutoConfiguredHealthEndpointGroupsRegistry} instance. * Create a new {@link AutoConfiguredHealthEndpointGroups} instance.
* @param applicationContext the application context used to check for override beans * @param applicationContext the application context used to check for override beans
* @param properties the health endpoint properties * @param properties the health endpoint properties
*/ */
AutoConfiguredHealthEndpointGroupsRegistry(ApplicationContext applicationContext, AutoConfiguredHealthEndpointGroups(ApplicationContext applicationContext, HealthEndpointProperties properties) {
HealthEndpointProperties properties) {
ListableBeanFactory beanFactory = (applicationContext instanceof ConfigurableApplicationContext) ListableBeanFactory beanFactory = (applicationContext instanceof ConfigurableApplicationContext)
? ((ConfigurableApplicationContext) applicationContext).getBeanFactory() : applicationContext; ? ((ConfigurableApplicationContext) applicationContext).getBeanFactory() : applicationContext;
this.defaultShowComponents = convertVisibility(properties.getShowComponents()); Show showComponents = properties.getShowComponents();
this.defaultShowDetails = convertVisibility(properties.getShowDetails()); Show showDetails = properties.getShowDetails();
this.defaultRoles = properties.getRoles(); Set<String> roles = properties.getRoles();
StatusAggregator statusAggregator = getNonQualifiedBean(beanFactory, StatusAggregator.class); StatusAggregator statusAggregator = getNonQualifiedBean(beanFactory, StatusAggregator.class);
if (statusAggregator == null) { if (statusAggregator == null) {
statusAggregator = new SimpleStatusAggregator(properties.getStatus().getOrder()); statusAggregator = new SimpleStatusAggregator(properties.getStatus().getOrder());
} }
this.defaultStatusAggregator = statusAggregator;
HttpCodeStatusMapper httpCodeStatusMapper = getNonQualifiedBean(beanFactory, HttpCodeStatusMapper.class); HttpCodeStatusMapper httpCodeStatusMapper = getNonQualifiedBean(beanFactory, HttpCodeStatusMapper.class);
if (httpCodeStatusMapper == null) { if (httpCodeStatusMapper == null) {
httpCodeStatusMapper = new SimpleHttpCodeStatusMapper(properties.getStatus().getHttpMapping()); httpCodeStatusMapper = new SimpleHttpCodeStatusMapper(properties.getStatus().getHttpMapping());
} }
this.defaultHttpCodeStatusMapper = httpCodeStatusMapper; this.primaryGroup = new AutoConfiguredHealthEndpointGroup(ALL, statusAggregator, httpCodeStatusMapper,
showComponents, showDetails, roles);
this.primaryGroup = new DefaultHealthEndpointGroup(ALL, statusAggregator, httpCodeStatusMapper, this.groups = createGroups(properties.getGroup(), beanFactory, statusAggregator, httpCodeStatusMapper,
this.defaultShowComponents, this.defaultShowDetails, this.defaultRoles); showComponents, showDetails, roles);
this.groups = createGroups(properties.getGroup(), beanFactory);
}
@Override
public HealthEndpointGroupsRegistry add(String groupName, Consumer<HealthEndpointGroupConfigurer> consumer) {
DefaultHealthEndpointGroupConfigurer groupConfigurer = new DefaultHealthEndpointGroupConfigurer(
this.defaultStatusAggregator, this.defaultHttpCodeStatusMapper, this.defaultShowComponents,
this.defaultShowDetails, this.defaultRoles);
consumer.accept(groupConfigurer);
this.groups.put(groupName, groupConfigurer.toHealthEndpointGroup());
return this;
}
@Override
public HealthEndpointGroupsRegistry remove(String groupName) {
this.groups.remove(groupName);
return this;
} }
@Override private Map<String, HealthEndpointGroup> createGroups(Map<String, Group> groupProperties, BeanFactory beanFactory,
public HealthEndpointGroups toGroups() { StatusAggregator defaultStatusAggregator, HttpCodeStatusMapper defaultHttpCodeStatusMapper,
return HealthEndpointGroups.of(this.primaryGroup, Collections.unmodifiableMap(this.groups)); Show defaultShowComponents, Show defaultShowDetails, Set<String> defaultRoles) {
}
private Map<String, HealthEndpointGroup> createGroups(Map<String, Group> groupProperties, BeanFactory beanFactory) {
Map<String, HealthEndpointGroup> groups = new LinkedHashMap<>(); Map<String, HealthEndpointGroup> groups = new LinkedHashMap<>();
groupProperties groupProperties.forEach((groupName, group) -> {
.forEach((groupName, group) -> groups.put(groupName, createGroup(groupName, group, beanFactory))); Status status = group.getStatus();
return groups; Show showComponents = (group.getShowComponents() != null) ? group.getShowComponents()
} : defaultShowComponents;
Show showDetails = (group.getShowDetails() != null) ? group.getShowDetails() : defaultShowDetails;
private HealthEndpointGroup createGroup(String groupName, Group groupProperties, BeanFactory beanFactory) { Set<String> roles = !CollectionUtils.isEmpty(group.getRoles()) ? group.getRoles() : defaultRoles;
Status status = groupProperties.getStatus(); StatusAggregator statusAggregator = getQualifiedBean(beanFactory, StatusAggregator.class, groupName, () -> {
Show showComponents = (groupProperties.getShowComponents() != null) if (!CollectionUtils.isEmpty(status.getOrder())) {
? convertVisibility(groupProperties.getShowComponents()) : this.defaultShowComponents; return new SimpleStatusAggregator(status.getOrder());
Show showDetails = (groupProperties.getShowDetails() != null) }
? convertVisibility(groupProperties.getShowDetails()) : this.defaultShowDetails; return defaultStatusAggregator;
Set<String> roles = !CollectionUtils.isEmpty(groupProperties.getRoles()) ? groupProperties.getRoles() });
: this.defaultRoles; HttpCodeStatusMapper httpCodeStatusMapper = getQualifiedBean(beanFactory, HttpCodeStatusMapper.class,
StatusAggregator statusAggregator = getQualifiedBean(beanFactory, StatusAggregator.class, groupName, () -> { groupName, () -> {
if (!CollectionUtils.isEmpty(status.getOrder())) { if (!CollectionUtils.isEmpty(status.getHttpMapping())) {
return new SimpleStatusAggregator(status.getOrder()); return new SimpleHttpCodeStatusMapper(status.getHttpMapping());
} }
return this.defaultStatusAggregator; return defaultHttpCodeStatusMapper;
});
Predicate<String> members = new IncludeExcludeGroupMemberPredicate(group.getInclude(), group.getExclude());
groups.put(groupName, new AutoConfiguredHealthEndpointGroup(members, statusAggregator, httpCodeStatusMapper,
showComponents, showDetails, roles));
}); });
HttpCodeStatusMapper httpCodeStatusMapper = getQualifiedBean(beanFactory, HttpCodeStatusMapper.class, groupName, return Collections.unmodifiableMap(groups);
() -> {
if (!CollectionUtils.isEmpty(status.getHttpMapping())) {
return new SimpleHttpCodeStatusMapper(status.getHttpMapping());
}
return this.defaultHttpCodeStatusMapper;
});
Predicate<String> members = new IncludeExcludeGroupMemberPredicate(groupProperties.getInclude(),
groupProperties.getExclude());
return new DefaultHealthEndpointGroup(members, statusAggregator, httpCodeStatusMapper, showComponents,
showDetails, roles);
} }
private <T> T getNonQualifiedBean(ListableBeanFactory beanFactory, Class<T> type) { private <T> T getNonQualifiedBean(ListableBeanFactory beanFactory, Class<T> type) {
...@@ -185,21 +140,6 @@ class AutoConfiguredHealthEndpointGroupsRegistry implements HealthEndpointGroups ...@@ -185,21 +140,6 @@ class AutoConfiguredHealthEndpointGroupsRegistry implements HealthEndpointGroups
} }
} }
private Show convertVisibility(HealthProperties.Show show) {
if (show == null) {
return null;
}
switch (show) {
case ALWAYS:
return Show.ALWAYS;
case NEVER:
return Show.NEVER;
case WHEN_AUTHORIZED:
return Show.WHEN_AUTHORIZED;
}
throw new IllegalStateException("Unsupported 'show' value " + show);
}
@Override @Override
public HealthEndpointGroup getPrimary() { public HealthEndpointGroup getPrimary() {
return this.primaryGroup; return this.primaryGroup;
......
/*
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.autoconfigure.health;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import org.springframework.boot.actuate.health.HealthEndpointGroup;
import org.springframework.boot.actuate.health.HealthEndpointGroup.Show;
import org.springframework.boot.actuate.health.HealthEndpointGroupConfigurer;
import org.springframework.boot.actuate.health.HttpCodeStatusMapper;
import org.springframework.boot.actuate.health.StatusAggregator;
/**
* Mutable {@link HealthEndpointGroupConfigurer configurer} for
* {@link HealthEndpointGroup}.
*
* @author Brian Clozel
*/
class DefaultHealthEndpointGroupConfigurer implements HealthEndpointGroupConfigurer {
Set<String> includedIndicators;
Set<String> excludedIndicators;
private StatusAggregator statusAggregator;
private HttpCodeStatusMapper httpCodeStatusMapper;
private Show showComponents;
private Show showDetails;
private Set<String> roles;
DefaultHealthEndpointGroupConfigurer(StatusAggregator defaultStatusAggregator,
HttpCodeStatusMapper defaultHttpCodeStatusMapper, Show defaultShowComponents, Show defaultShowDetails,
Set<String> defaultRoles) {
this.statusAggregator = defaultStatusAggregator;
this.httpCodeStatusMapper = defaultHttpCodeStatusMapper;
this.showComponents = defaultShowComponents;
this.showDetails = defaultShowDetails;
this.roles = new HashSet<>(defaultRoles);
}
@Override
public HealthEndpointGroupConfigurer include(String... indicators) {
this.includedIndicators = new HashSet<>(Arrays.asList(indicators));
return this;
}
@Override
public HealthEndpointGroupConfigurer exclude(String... exclude) {
this.excludedIndicators = new HashSet<>(Arrays.asList(exclude));
return this;
}
@Override
public HealthEndpointGroupConfigurer statusAggregator(StatusAggregator statusAggregator) {
this.statusAggregator = statusAggregator;
return this;
}
@Override
public HealthEndpointGroupConfigurer httpCodeStatusMapper(HttpCodeStatusMapper httpCodeStatusMapper) {
this.httpCodeStatusMapper = httpCodeStatusMapper;
return this;
}
@Override
public HealthEndpointGroupConfigurer showComponents(Show showComponents) {
this.showComponents = showComponents;
return this;
}
@Override
public HealthEndpointGroupConfigurer showDetails(Show showDetails) {
this.showDetails = showDetails;
return this;
}
@Override
public HealthEndpointGroupConfigurer roles(String... roles) {
this.roles = new HashSet<>(Arrays.asList(roles));
return this;
}
HealthEndpointGroup toHealthEndpointGroup() {
IncludeExcludeGroupMemberPredicate predicate = new IncludeExcludeGroupMemberPredicate(this.includedIndicators,
this.excludedIndicators);
return new DefaultHealthEndpointGroup(predicate, this.statusAggregator, this.httpCodeStatusMapper,
this.showComponents, this.showDetails, this.roles);
}
}
...@@ -21,7 +21,9 @@ import java.util.Iterator; ...@@ -21,7 +21,9 @@ import java.util.Iterator;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.actuate.health.CompositeHealthContributor; import org.springframework.boot.actuate.health.CompositeHealthContributor;
import org.springframework.boot.actuate.health.CompositeReactiveHealthContributor; import org.springframework.boot.actuate.health.CompositeReactiveHealthContributor;
import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.Health;
...@@ -29,7 +31,7 @@ import org.springframework.boot.actuate.health.HealthContributor; ...@@ -29,7 +31,7 @@ import org.springframework.boot.actuate.health.HealthContributor;
import org.springframework.boot.actuate.health.HealthContributorRegistry; import org.springframework.boot.actuate.health.HealthContributorRegistry;
import org.springframework.boot.actuate.health.HealthEndpoint; import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.health.HealthEndpointGroups; import org.springframework.boot.actuate.health.HealthEndpointGroups;
import org.springframework.boot.actuate.health.HealthEndpointGroupsRegistryCustomizer; import org.springframework.boot.actuate.health.HealthEndpointGroupsPostProcessor;
import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.HttpCodeStatusMapper; import org.springframework.boot.actuate.health.HttpCodeStatusMapper;
import org.springframework.boot.actuate.health.NamedContributor; import org.springframework.boot.actuate.health.NamedContributor;
...@@ -68,11 +70,8 @@ class HealthEndpointConfiguration { ...@@ -68,11 +70,8 @@ class HealthEndpointConfiguration {
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
HealthEndpointGroups healthEndpointGroups(ApplicationContext applicationContext, HealthEndpointGroups healthEndpointGroups(ApplicationContext applicationContext,
HealthEndpointProperties properties, ObjectProvider<HealthEndpointGroupsRegistryCustomizer> customizers) { HealthEndpointProperties properties) {
AutoConfiguredHealthEndpointGroupsRegistry registry = new AutoConfiguredHealthEndpointGroupsRegistry( return new AutoConfiguredHealthEndpointGroups(applicationContext, properties);
applicationContext, properties);
customizers.orderedStream().forEach((customizer) -> customizer.customize(registry));
return registry.toGroups();
} }
@Bean @Bean
...@@ -93,6 +92,42 @@ class HealthEndpointConfiguration { ...@@ -93,6 +92,42 @@ class HealthEndpointConfiguration {
return new HealthEndpoint(registry, groups); return new HealthEndpoint(registry, groups);
} }
@Bean
static HealthEndpointGroupsBeanPostProcessor healthEndpointGroupsBeanPostProcessor(
ObjectProvider<HealthEndpointGroupsPostProcessor> healthEndpointGroupsPostProcessors) {
return new HealthEndpointGroupsBeanPostProcessor(healthEndpointGroupsPostProcessors);
}
/**
* {@link BeanPostProcessor} to invoke {@link HealthEndpointGroupsPostProcessor}
* beans.
*/
private static class HealthEndpointGroupsBeanPostProcessor implements BeanPostProcessor {
private final ObjectProvider<HealthEndpointGroupsPostProcessor> postProcessors;
HealthEndpointGroupsBeanPostProcessor(ObjectProvider<HealthEndpointGroupsPostProcessor> postProcessors) {
this.postProcessors = postProcessors;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof HealthEndpointGroups) {
return applyPostProcessors((HealthEndpointGroups) bean);
}
return bean;
}
private Object applyPostProcessors(HealthEndpointGroups bean) {
for (HealthEndpointGroupsPostProcessor postProcessor : this.postProcessors.orderedStream()
.toArray(HealthEndpointGroupsPostProcessor[]::new)) {
bean = postProcessor.postProcessHealthEndpointGroups(bean);
}
return bean;
}
}
/** /**
* Adapter to expose {@link ReactiveHealthContributor} beans as * Adapter to expose {@link ReactiveHealthContributor} beans as
* {@link HealthContributor} instances. * {@link HealthContributor} instances.
......
...@@ -20,7 +20,6 @@ import org.junit.jupiter.api.Test; ...@@ -20,7 +20,6 @@ import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.availability.LivenessStateHealthIndicator; import org.springframework.boot.actuate.availability.LivenessStateHealthIndicator;
import org.springframework.boot.actuate.availability.ReadinessStateHealthIndicator; import org.springframework.boot.actuate.availability.ReadinessStateHealthIndicator;
import org.springframework.boot.actuate.health.HealthEndpointGroupsRegistryCustomizer;
import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration; import org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration;
import org.springframework.boot.availability.ApplicationAvailability; import org.springframework.boot.availability.ApplicationAvailability;
...@@ -43,7 +42,7 @@ class AvailabilityProbesAutoConfigurationTests { ...@@ -43,7 +42,7 @@ class AvailabilityProbesAutoConfigurationTests {
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(ApplicationAvailability.class) this.contextRunner.run((context) -> assertThat(context).hasSingleBean(ApplicationAvailability.class)
.doesNotHaveBean(LivenessStateHealthIndicator.class) .doesNotHaveBean(LivenessStateHealthIndicator.class)
.doesNotHaveBean(ReadinessStateHealthIndicator.class) .doesNotHaveBean(ReadinessStateHealthIndicator.class)
.doesNotHaveBean(HealthEndpointGroupsRegistryCustomizer.class)); .doesNotHaveBean(AvailabilityProbesHealthEndpointGroupsPostProcessor.class));
} }
@Test @Test
...@@ -52,7 +51,7 @@ class AvailabilityProbesAutoConfigurationTests { ...@@ -52,7 +51,7 @@ class AvailabilityProbesAutoConfigurationTests {
.run((context) -> assertThat(context).hasSingleBean(ApplicationAvailability.class) .run((context) -> assertThat(context).hasSingleBean(ApplicationAvailability.class)
.hasSingleBean(LivenessStateHealthIndicator.class) .hasSingleBean(LivenessStateHealthIndicator.class)
.hasSingleBean(ReadinessStateHealthIndicator.class) .hasSingleBean(ReadinessStateHealthIndicator.class)
.hasSingleBean(HealthEndpointGroupsRegistryCustomizer.class)); .hasSingleBean(AvailabilityProbesHealthEndpointGroupsPostProcessor.class));
} }
@Test @Test
...@@ -61,7 +60,7 @@ class AvailabilityProbesAutoConfigurationTests { ...@@ -61,7 +60,7 @@ class AvailabilityProbesAutoConfigurationTests {
.run((context) -> assertThat(context).hasSingleBean(ApplicationAvailability.class) .run((context) -> assertThat(context).hasSingleBean(ApplicationAvailability.class)
.hasSingleBean(LivenessStateHealthIndicator.class) .hasSingleBean(LivenessStateHealthIndicator.class)
.hasSingleBean(ReadinessStateHealthIndicator.class) .hasSingleBean(ReadinessStateHealthIndicator.class)
.hasSingleBean(HealthEndpointGroupsRegistryCustomizer.class)); .hasSingleBean(AvailabilityProbesHealthEndpointGroupsPostProcessor.class));
} }
@Test @Test
...@@ -71,7 +70,7 @@ class AvailabilityProbesAutoConfigurationTests { ...@@ -71,7 +70,7 @@ class AvailabilityProbesAutoConfigurationTests {
.run((context) -> assertThat(context).hasSingleBean(ApplicationAvailability.class) .run((context) -> assertThat(context).hasSingleBean(ApplicationAvailability.class)
.doesNotHaveBean(LivenessStateHealthIndicator.class) .doesNotHaveBean(LivenessStateHealthIndicator.class)
.doesNotHaveBean(ReadinessStateHealthIndicator.class) .doesNotHaveBean(ReadinessStateHealthIndicator.class)
.doesNotHaveBean(HealthEndpointGroupsRegistryCustomizer.class)); .doesNotHaveBean(AvailabilityProbesHealthEndpointGroupsPostProcessor.class));
} }
} }
/*
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.autoconfigure.availability;
import org.junit.jupiter.api.Test;
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.Mockito.mock;
/**
* Tests for {@link AvailabilityProbesHealthEndpointGroup}.
*
* @author Phillip Webb
*/
class AvailabilityProbesHealthEndpointGroupTests {
private AvailabilityProbesHealthEndpointGroup group = new AvailabilityProbesHealthEndpointGroup("a", "b");
@Test
void isMemberWhenMemberReturnsTrue() {
assertThat(this.group.isMember("a")).isTrue();
assertThat(this.group.isMember("b")).isTrue();
}
@Test
void isMemberWhenNotMemberReturnsFalse() {
assertThat(this.group.isMember("c")).isFalse();
}
@Test
void showComponentsReturnsFalse() {
assertThat(this.group.showComponents(mock(SecurityContext.class))).isFalse();
}
@Test
void showDetailsReturnsFalse() {
assertThat(this.group.showDetails(mock(SecurityContext.class))).isFalse();
}
@Test
void getStatusAggregattorReturnsDefaultStatusAggregator() {
assertThat(this.group.getStatusAggregator()).isEqualTo(StatusAggregator.DEFAULT);
}
@Test
void getHttpCodeStatusMapperReturnsDefaultHttpCodeStatusMapper() {
assertThat(this.group.getHttpCodeStatusMapper()).isEqualTo(HttpCodeStatusMapper.DEFAULT);
}
}
/*
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.autoconfigure.availability;
import java.util.LinkedHashSet;
import java.util.Set;
import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.health.HealthEndpointGroups;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link AvailabilityProbesHealthEndpointGroupsPostProcessor}.
*
* @author Phillip Webb
*/
class AvailabilityProbesHealthEndpointGroupsPostProcessorTests {
private AvailabilityProbesHealthEndpointGroupsPostProcessor postProcessor = new AvailabilityProbesHealthEndpointGroupsPostProcessor();
@Test
void postProcessHealthEndpointGroupsWhenGroupsAlreadyContainedReturnsOriginal() {
HealthEndpointGroups groups = mock(HealthEndpointGroups.class);
Set<String> names = new LinkedHashSet<>();
names.add("test");
names.add("readiness");
names.add("liveness");
given(groups.getNames()).willReturn(names);
assertThat(this.postProcessor.postProcessHealthEndpointGroups(groups)).isSameAs(groups);
}
@Test
void postProcessHealthEndpointGroupsWhenGroupContainsOneReturnsPostProcessed() {
HealthEndpointGroups groups = mock(HealthEndpointGroups.class);
Set<String> names = new LinkedHashSet<>();
names.add("test");
names.add("readiness");
given(groups.getNames()).willReturn(names);
assertThat(this.postProcessor.postProcessHealthEndpointGroups(groups))
.isInstanceOf(AvailabilityProbesHealthEndpointGroups.class);
}
@Test
void postProcessHealthEndpointGroupsWhenGroupsContainsNoneReturnsProcessed() {
HealthEndpointGroups groups = mock(HealthEndpointGroups.class);
Set<String> names = new LinkedHashSet<>();
names.add("test");
names.add("spring");
names.add("boot");
given(groups.getNames()).willReturn(names);
assertThat(this.postProcessor.postProcessHealthEndpointGroups(groups))
.isInstanceOf(AvailabilityProbesHealthEndpointGroups.class);
}
}
/*
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.autoconfigure.availability;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.health.HealthEndpointGroup;
import org.springframework.boot.actuate.health.HealthEndpointGroups;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link AvailabilityProbesHealthEndpointGroups}.
*
* @author Phillip Webb
*/
class AvailabilityProbesHealthEndpointGroupsTests {
private HealthEndpointGroups delegate;
private HealthEndpointGroup group;
@BeforeEach
void setup() {
this.delegate = mock(HealthEndpointGroups.class);
this.group = mock(HealthEndpointGroup.class);
}
@Test
void createWhenGroupsIsNullThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() -> new AvailabilityProbesHealthEndpointGroups(null))
.withMessage("Groups must not be null");
}
@Test
void getPrimaryDelegatesToGroups() {
given(this.delegate.getPrimary()).willReturn(this.group);
HealthEndpointGroups availabilityProbes = new AvailabilityProbesHealthEndpointGroups(this.delegate);
assertThat(availabilityProbes.getPrimary()).isEqualTo(this.group);
}
@Test
void getNamesIncludesAvailabilityProbeGroups() {
given(this.delegate.getNames()).willReturn(Collections.singleton("test"));
HealthEndpointGroups availabilityProbes = new AvailabilityProbesHealthEndpointGroups(this.delegate);
assertThat(availabilityProbes.getNames()).containsExactly("test", "liveness", "readiness");
}
@Test
void getWhenProbeInDelegateReturnsGroupFromDelegate() {
given(this.delegate.get("liveness")).willReturn(this.group);
HealthEndpointGroups availabilityProbes = new AvailabilityProbesHealthEndpointGroups(this.delegate);
assertThat(availabilityProbes.get("liveness")).isEqualTo(this.group);
}
@Test
void getWhenProbeNotInDelegateReturnsProbeGroup() {
HealthEndpointGroups availabilityProbes = new AvailabilityProbesHealthEndpointGroups(this.delegate);
assertThat(availabilityProbes.get("liveness")).isInstanceOf(AvailabilityProbesHealthEndpointGroup.class);
}
@Test
void getWhenNotProbeAndNotInDelegateReturnsNull() {
HealthEndpointGroups availabilityProbes = new AvailabilityProbesHealthEndpointGroups(this.delegate);
assertThat(availabilityProbes.get("mygroup")).isNull();
}
@Test
void getLivenessProbeHasOnlyLivenessStateAsMember() {
HealthEndpointGroups availabilityProbes = new AvailabilityProbesHealthEndpointGroups(this.delegate);
HealthEndpointGroup probeGroup = availabilityProbes.get("liveness");
assertThat(probeGroup.isMember("livenessState")).isTrue();
assertThat(probeGroup.isMember("readinessState")).isFalse();
}
@Test
void getRedinessProbeHasOnlyReadinessStateAsMember() {
HealthEndpointGroups availabilityProbes = new AvailabilityProbesHealthEndpointGroups(this.delegate);
HealthEndpointGroup probeGroup = availabilityProbes.get("readiness");
assertThat(probeGroup.isMember("livenessState")).isFalse();
assertThat(probeGroup.isMember("readinessState")).isTrue();
}
@Test
void containsAllWhenContainsAllReturnTrue() {
given(this.delegate.getNames()).willReturn(new LinkedHashSet<>(Arrays.asList("test", "liveness", "readiness")));
assertThat(AvailabilityProbesHealthEndpointGroups.containsAllProbeGroups(this.delegate)).isTrue();
}
@Test
void containsAllWhenContainsOneReturnFalse() {
given(this.delegate.getNames()).willReturn(new LinkedHashSet<>(Arrays.asList("test", "liveness")));
assertThat(AvailabilityProbesHealthEndpointGroups.containsAllProbeGroups(this.delegate)).isFalse();
}
@Test
void containsAllWhenContainsNoneReturnFalse() {
given(this.delegate.getNames()).willReturn(new LinkedHashSet<>(Arrays.asList("test", "spring")));
assertThat(AvailabilityProbesHealthEndpointGroups.containsAllProbeGroups(this.delegate)).isFalse();
}
}
...@@ -40,11 +40,11 @@ import org.springframework.context.annotation.Primary; ...@@ -40,11 +40,11 @@ import org.springframework.context.annotation.Primary;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
/** /**
* Tests for {@link AutoConfiguredHealthEndpointGroupsRegistry}. * Tests for {@link AutoConfiguredHealthEndpointGroups}.
* *
* @author Phillip Webb * @author Phillip Webb
*/ */
class AutoConfiguredHealthEndpointGroupsBuilderTests { class AutoConfiguredHealthEndpointGroupsTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(AutoConfiguredHealthEndpointGroupsTestConfiguration.class)); .withConfiguration(AutoConfigurations.of(AutoConfiguredHealthEndpointGroupsTestConfiguration.class));
...@@ -313,9 +313,9 @@ class AutoConfiguredHealthEndpointGroupsBuilderTests { ...@@ -313,9 +313,9 @@ class AutoConfiguredHealthEndpointGroupsBuilderTests {
static class AutoConfiguredHealthEndpointGroupsTestConfiguration { static class AutoConfiguredHealthEndpointGroupsTestConfiguration {
@Bean @Bean
AutoConfiguredHealthEndpointGroupsRegistry healthEndpointGroups( AutoConfiguredHealthEndpointGroups healthEndpointGroups(ConfigurableApplicationContext applicationContext,
ConfigurableApplicationContext applicationContext, HealthEndpointProperties properties) { HealthEndpointProperties properties) {
return new AutoConfiguredHealthEndpointGroupsRegistry(applicationContext, properties); return new AutoConfiguredHealthEndpointGroups(applicationContext, properties);
} }
} }
......
...@@ -37,7 +37,7 @@ import org.springframework.boot.actuate.health.HealthComponent; ...@@ -37,7 +37,7 @@ import org.springframework.boot.actuate.health.HealthComponent;
import org.springframework.boot.actuate.health.HealthContributorRegistry; import org.springframework.boot.actuate.health.HealthContributorRegistry;
import org.springframework.boot.actuate.health.HealthEndpoint; import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.health.HealthEndpointGroups; import org.springframework.boot.actuate.health.HealthEndpointGroups;
import org.springframework.boot.actuate.health.HealthEndpointGroupsRegistryCustomizer; import org.springframework.boot.actuate.health.HealthEndpointGroupsPostProcessor;
import org.springframework.boot.actuate.health.HealthEndpointWebExtension; import org.springframework.boot.actuate.health.HealthEndpointWebExtension;
import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.HttpCodeStatusMapper; import org.springframework.boot.actuate.health.HttpCodeStatusMapper;
...@@ -56,6 +56,7 @@ import org.springframework.context.annotation.Bean; ...@@ -56,6 +56,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
...@@ -155,6 +156,7 @@ class HealthEndpointAutoConfigurationTests { ...@@ -155,6 +156,7 @@ class HealthEndpointAutoConfigurationTests {
void runCreatesHealthEndpointGroups() { void runCreatesHealthEndpointGroups() {
this.contextRunner.withPropertyValues("management.endpoint.health.group.ready.include=*").run((context) -> { this.contextRunner.withPropertyValues("management.endpoint.health.group.ready.include=*").run((context) -> {
HealthEndpointGroups groups = context.getBean(HealthEndpointGroups.class); HealthEndpointGroups groups = context.getBean(HealthEndpointGroups.class);
assertThat(groups).isInstanceOf(AutoConfiguredHealthEndpointGroups.class);
assertThat(groups.getNames()).containsOnly("ready"); assertThat(groups.getNames()).containsOnly("ready");
}); });
} }
...@@ -302,12 +304,13 @@ class HealthEndpointAutoConfigurationTests { ...@@ -302,12 +304,13 @@ class HealthEndpointAutoConfigurationTests {
} }
@Test @Test
void runWhenHealthEndpointGroupsRegistryCustomizerAddsHealthEndpointGroup() { void runWhenHasHealthEndpointGroupsPostProcessorPerformsProcessing() {
this.contextRunner.withUserConfiguration(HealthEndpointGroupsRegistryCustomizerConfig.class).run((context) -> { this.contextRunner.withPropertyValues("management.endpoint.health.group.ready.include=*").withUserConfiguration(
assertThat(context).hasSingleBean(HealthEndpointGroupsRegistryCustomizer.class); HealthEndpointGroupsConfiguration.class, TestHealthEndpointGroupsPostProcessor.class).run((context) -> {
HealthEndpointGroups groups = context.getBean(HealthEndpointGroups.class); HealthEndpointGroups groups = context.getBean(HealthEndpointGroups.class);
assertThat(groups.getNames()).contains("test"); assertThatExceptionOfType(RuntimeException.class).isThrownBy(() -> groups.get("test"))
}); .withMessage("postprocessed");
});
} }
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
...@@ -429,12 +432,12 @@ class HealthEndpointAutoConfigurationTests { ...@@ -429,12 +432,12 @@ class HealthEndpointAutoConfigurationTests {
} }
@Configuration(proxyBeanMethods = false) static class TestHealthEndpointGroupsPostProcessor implements HealthEndpointGroupsPostProcessor {
static class HealthEndpointGroupsRegistryCustomizerConfig {
@Bean @Override
HealthEndpointGroupsRegistryCustomizer customHealthEndpointGroup() { public HealthEndpointGroups postProcessHealthEndpointGroups(HealthEndpointGroups groups) {
return (registry) -> registry.add("test", (configurer) -> configurer.include("ping")); given(groups.get("test")).willThrow(new RuntimeException("postprocessed"));
return groups;
} }
} }
......
...@@ -23,7 +23,6 @@ import org.springframework.boot.actuate.endpoint.SecurityContext; ...@@ -23,7 +23,6 @@ import org.springframework.boot.actuate.endpoint.SecurityContext;
* by the {@link HealthEndpoint}. * by the {@link HealthEndpoint}.
* *
* @author Phillip Webb * @author Phillip Webb
* @author Brian Clozel
* @since 2.2.0 * @since 2.2.0
*/ */
public interface HealthEndpointGroup { public interface HealthEndpointGroup {
...@@ -63,27 +62,4 @@ public interface HealthEndpointGroup { ...@@ -63,27 +62,4 @@ public interface HealthEndpointGroup {
*/ */
HttpCodeStatusMapper getHttpCodeStatusMapper(); HttpCodeStatusMapper getHttpCodeStatusMapper();
/**
* Options for showing items in responses from the {@link HealthEndpointGroup} web
* extensions.
*/
enum Show {
/**
* Never show the item in the response.
*/
NEVER,
/**
* Show the item in the response when accessed by an authorized user.
*/
WHEN_AUTHORIZED,
/**
* Always show the item in the response.
*/
ALWAYS
}
} }
/*
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.health;
import org.springframework.boot.actuate.health.HealthEndpointGroup.Show;
/**
* A configurer for customizing an {@link HealthEndpointGroup} being built.
*
* @author Brian Clozel
* @since 2.3.0
*/
public interface HealthEndpointGroupConfigurer {
/**
* Configure the indicator endpoint ids to include in this group.
* @param indicators the indicator endpoint ids
* @return the configurer instance
*/
HealthEndpointGroupConfigurer include(String... indicators);
/**
* Configure the indicator endpoint ids to exclude from this group.
* @param indicators the indicator endpoint ids
* @return the configurer instance
*/
HealthEndpointGroupConfigurer exclude(String... indicators);
/**
* Configure the {@link StatusAggregator} to use for this group.
* <p>
* If none set, this will default to the globally configured {@link StatusAggregator}.
* @param statusAggregator the status aggregator
* @return the configurer instance
*/
HealthEndpointGroupConfigurer statusAggregator(StatusAggregator statusAggregator);
/**
* Configure the {@link HttpCodeStatusMapper} to use for this group.
* <p>
* If none set, this will default to the globally configured
* {@link HttpCodeStatusMapper}.
* @param httpCodeStatusMapper the status code mapper
* @return the configurer instance
*/
HealthEndpointGroupConfigurer httpCodeStatusMapper(HttpCodeStatusMapper httpCodeStatusMapper);
/**
* Configure the {@link Show visibility option} for showing components of this group.
* @param showComponents the components visibility
* @return the configurer instance
*/
HealthEndpointGroupConfigurer showComponents(Show showComponents);
/**
* Configure the {@link Show visibility option} for showing details of this group.
* @param showDetails the details visibility
* @return the configurer instance
*/
HealthEndpointGroupConfigurer showDetails(Show showDetails);
/**
* Configure roles used to determine whether or not a user is authorized to be shown
* details.
* @param roles the roles
* @return the configurer instance
*/
HealthEndpointGroupConfigurer roles(String... roles);
}
...@@ -17,19 +17,22 @@ ...@@ -17,19 +17,22 @@
package org.springframework.boot.actuate.health; package org.springframework.boot.actuate.health;
/** /**
* Callback interface that can be used to customize a * Hook that allows for custom modification of {@link HealthEndpointGroups} &mdash; for
* {@link HealthEndpointGroupsRegistry}. * example, automatically adding additional auto-configured groups.
* *
* @author Phillip Webb
* @author Brian Clozel * @author Brian Clozel
* @since 2.3.0 * @since 2.3.0
*/ */
@FunctionalInterface @FunctionalInterface
public interface HealthEndpointGroupsRegistryCustomizer { public interface HealthEndpointGroupsPostProcessor {
/** /**
* Callback to customize a {@link HealthEndpointGroupsRegistry} instance. * Post-process the given {@link HealthEndpointGroups} instance.
* @param healthEndpointGroupsRegistry the registry to customize * @param groups the existing groups instance
* @return a post-processed groups instance, or the original instance if not
* post-processing was required
*/ */
void customize(HealthEndpointGroupsRegistry healthEndpointGroupsRegistry); HealthEndpointGroups postProcessHealthEndpointGroups(HealthEndpointGroups groups);
} }
/* /*
* Copyright 2012-2019 the original author or authors. * Copyright 2012-2020 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -26,6 +26,12 @@ package org.springframework.boot.actuate.health; ...@@ -26,6 +26,12 @@ package org.springframework.boot.actuate.health;
@FunctionalInterface @FunctionalInterface
public interface HttpCodeStatusMapper { public interface HttpCodeStatusMapper {
/**
* A {@link HttpCodeStatusMapper} instance using default mappings.
* @since 2.3.0
*/
HttpCodeStatusMapper DEFAULT = new SimpleHttpCodeStatusMapper();
/** /**
* Return the HTTP status code that corresponds to the given {@link Status health * Return the HTTP status code that corresponds to the given {@link Status health
* status}. * status}.
......
/* /*
* Copyright 2012-2019 the original author or authors. * Copyright 2012-2020 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -32,6 +32,12 @@ import java.util.Set; ...@@ -32,6 +32,12 @@ import java.util.Set;
@FunctionalInterface @FunctionalInterface
public interface StatusAggregator { public interface StatusAggregator {
/**
* A {@link StatusAggregator} instance using default ordering rules.
* @since 2.3.0
*/
StatusAggregator DEFAULT = new SimpleStatusAggregator();
/** /**
* Return the aggregate status for the given set of statuses. * Return the aggregate status for the given set of statuses.
* @param statuses the statuses to aggregate * @param statuses the statuses to aggregate
......
/*
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.health;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* Test implementation for {@link HealthEndpointGroupsRegistry}
*
* @author Brian Clozel
*/
public class TestHealthEndpointGroupsRegistry implements HealthEndpointGroupsRegistry {
private TestHealthEndpointGroup primary = new TestHealthEndpointGroup();
private Map<String, TestHealthEndpointGroup> groups = new HashMap<>();
@Override
public HealthEndpointGroupsRegistry add(String groupName, Consumer<HealthEndpointGroupConfigurer> builder) {
TestHealthEndpointGroupConfigurer configurer = new TestHealthEndpointGroupConfigurer();
builder.accept(configurer);
this.groups.put(groupName, configurer.toHealthEndpointGroup());
return this;
}
@Override
public HealthEndpointGroupsRegistry remove(String groupName) {
this.groups.remove(groupName);
return this;
}
@Override
public HealthEndpointGroups toGroups() {
return this;
}
@Override
public HealthEndpointGroup getPrimary() {
return this.primary;
}
@Override
public Set<String> getNames() {
return this.groups.keySet();
}
@Override
public HealthEndpointGroup get(String name) {
return this.groups.get(name);
}
class TestHealthEndpointGroupConfigurer implements HealthEndpointGroupConfigurer {
private Predicate<String> predicate = (name) -> true;
@Override
public HealthEndpointGroupConfigurer include(String... indicators) {
Predicate<String> included = Arrays.asList(indicators).stream()
.map((group) -> (Predicate<String>) (s) -> s.equals(group)).reduce(Predicate::or)
.orElse((s) -> true);
this.predicate = this.predicate.and(included);
return this;
}
@Override
public HealthEndpointGroupConfigurer exclude(String... indicators) {
Predicate<String> excluded = Arrays.asList(indicators).stream()
.map((group) -> (Predicate<String>) (s) -> !s.equals(group)).reduce(Predicate::or)
.orElse((s) -> true);
this.predicate = this.predicate.and(excluded);
return this;
}
@Override
public HealthEndpointGroupConfigurer statusAggregator(StatusAggregator statusAggregator) {
throw new UnsupportedOperationException();
}
@Override
public HealthEndpointGroupConfigurer httpCodeStatusMapper(HttpCodeStatusMapper httpCodeStatusMapper) {
throw new UnsupportedOperationException();
}
@Override
public HealthEndpointGroupConfigurer showComponents(HealthEndpointGroup.Show showComponents) {
throw new UnsupportedOperationException();
}
@Override
public HealthEndpointGroupConfigurer showDetails(HealthEndpointGroup.Show showDetails) {
throw new UnsupportedOperationException();
}
@Override
public HealthEndpointGroupConfigurer roles(String... roles) {
throw new UnsupportedOperationException();
}
TestHealthEndpointGroup toHealthEndpointGroup() {
return new TestHealthEndpointGroup(this.predicate);
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment