Create spring-boot-health module

Closes gh-46155
This commit is contained in:
Phillip Webb 2025-06-12 09:57:33 -07:00 committed by Andy Wilkinson
parent 3ecbbce773
commit 3ca1e91cde
310 changed files with 4564 additions and 2765 deletions

View File

@ -95,6 +95,7 @@ include "spring-boot-project:spring-boot-gson"
include "spring-boot-project:spring-boot-h2console"
include "spring-boot-project:spring-boot-hateoas"
include "spring-boot-project:spring-boot-hazelcast"
include "spring-boot-project:spring-boot-health"
include "spring-boot-project:spring-boot-hibernate"
include "spring-boot-project:spring-boot-http-client"
include "spring-boot-project:spring-boot-http-converter"

View File

@ -28,10 +28,11 @@ description = "Spring Boot Actuator AutoConfigure"
dependencies {
api(project(":spring-boot-project:spring-boot-actuator"))
api(project(":spring-boot-project:spring-boot-autoconfigure"))
implementation("com.fasterxml.jackson.core:jackson-databind")
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
optional(project(":spring-boot-project:spring-boot-health"))
optional(project(":spring-boot-project:spring-boot-web-server"))
optional("com.fasterxml.jackson.core:jackson-databind")

View File

@ -23,8 +23,10 @@ import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.availability.ApplicationAvailability;
import org.springframework.boot.health.contributor.Health;
import org.springframework.context.annotation.Bean;
/**
@ -35,6 +37,7 @@ import org.springframework.context.annotation.Bean;
* @since 2.3.2
*/
@AutoConfiguration(after = ApplicationAvailabilityAutoConfiguration.class)
@ConditionalOnClass(Health.class)
public class AvailabilityHealthContributorAutoConfiguration {
@Bean

View File

@ -23,10 +23,12 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.availability.ApplicationAvailability;
import org.springframework.boot.cloud.CloudPlatform;
import org.springframework.boot.health.contributor.Health;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
@ -42,6 +44,7 @@ import org.springframework.core.type.AnnotatedTypeMetadata;
*/
@AutoConfiguration(after = { AvailabilityHealthContributorAutoConfiguration.class,
ApplicationAvailabilityAutoConfiguration.class })
@ConditionalOnClass(Health.class)
@Conditional(AvailabilityProbesAutoConfiguration.ProbesCondition.class)
public class AvailabilityProbesAutoConfiguration {

View File

@ -16,6 +16,9 @@
package org.springframework.boot.actuate.autoconfigure.endpoint.jackson;
import java.util.HashSet;
import java.util.Set;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
@ -28,6 +31,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProp
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.util.ClassUtils;
/**
* {@link EnableAutoConfiguration Auto-configuration} for Endpoint Jackson support.
@ -39,6 +43,8 @@ import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
@SuppressWarnings("removal")
public class JacksonEndpointAutoConfiguration {
private static final String CONTRIBUTED_HEALTH = "org.springframework.boot.health.contributor.ContributedHealth";
@Bean
@ConditionalOnBooleanProperty(name = "management.endpoints.jackson.isolated-object-mapper", matchIfMissing = true)
@ConditionalOnClass({ ObjectMapper.class, Jackson2ObjectMapperBuilder.class })
@ -49,7 +55,24 @@ public class JacksonEndpointAutoConfiguration {
SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS)
.serializationInclusion(Include.NON_NULL)
.build();
return () -> objectMapper;
Set<Class<?>> supportedTypes = new HashSet<>(EndpointObjectMapper.DEFAULT_SUPPORTED_TYPES);
if (ClassUtils.isPresent(CONTRIBUTED_HEALTH, null)) {
supportedTypes.add(ClassUtils.resolveClassName(CONTRIBUTED_HEALTH, null));
}
return new EndpointObjectMapper() {
@Override
public ObjectMapper get() {
return objectMapper;
}
@Override
public Set<Class<?>> getSupportedTypes() {
return supportedTypes;
}
};
}
}

View File

@ -1,55 +0,0 @@
/*
* Copyright 2012-present 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");
}
}

View File

@ -18,11 +18,11 @@ 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.Objects;
import java.util.Set;
import java.util.TreeMap;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Stream;
@ -93,7 +93,7 @@ class AutoConfiguredHealthEndpointGroups implements HealthEndpointGroups, Additi
private Map<String, HealthEndpointGroup> createGroups(Map<String, Group> groupProperties, BeanFactory beanFactory,
StatusAggregator defaultStatusAggregator, HttpCodeStatusMapper defaultHttpCodeStatusMapper,
Show defaultShowComponents, Show defaultShowDetails, Set<String> defaultRoles) {
Map<String, HealthEndpointGroup> groups = new LinkedHashMap<>();
Map<String, HealthEndpointGroup> groups = new TreeMap<>();
groupProperties.forEach((groupName, group) -> {
Status status = group.getStatus();
Show showComponents = (group.getShowComponents() != null) ? group.getShowComponents()

View File

@ -1,55 +0,0 @@
/*
* Copyright 2012-present 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");
}
}

View File

@ -0,0 +1,45 @@
/*
* Copyright 2012-present 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 java.util.Set;
import org.springframework.boot.actuate.health.HealthEndpointGroups;
import org.springframework.boot.health.registry.HealthContributorNameValidator;
import org.springframework.util.Assert;
/**
* {@link HealthContributorNameValidator} to ensure names don't clash with groups.
*
* @author Phillip Webb
*/
class GroupsHealthContributorNameValidator implements HealthContributorNameValidator {
private final Set<String> groupNames;
GroupsHealthContributorNameValidator(HealthEndpointGroups groups) {
this.groupNames = (groups != null) ? groups.getNames() : Collections.emptySet();
}
@Override
public void validate(String name) throws IllegalStateException {
Assert.state(!this.groupNames.contains(name),
() -> "HealthContributor with name \"" + name + "\" clashes with group");
}
}

View File

@ -20,7 +20,10 @@ import org.springframework.boot.actuate.autoconfigure.endpoint.condition.Conditi
import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.health.contributor.Health;
import org.springframework.context.annotation.Import;
/**
@ -32,11 +35,14 @@ import org.springframework.context.annotation.Import;
* @author Scott Frederick
* @since 2.0.0
*/
@AutoConfiguration
@AutoConfiguration(
afterName = "org.springframework.boot.health.autoconfigure.registry.HealthContributorRegistryAutoConfiguration")
@ConditionalOnClass(Health.class)
@ConditionalOnBean(type = "org.springframework.boot.health.registry.HealthContributorRegistry")
@ConditionalOnAvailableEndpoint(HealthEndpoint.class)
@EnableConfigurationProperties(HealthEndpointProperties.class)
@Import({ HealthEndpointConfiguration.class, ReactiveHealthEndpointConfiguration.class,
HealthEndpointWebExtensionConfiguration.class, HealthEndpointReactiveWebExtensionConfiguration.class })
@Import({ HealthEndpointConfiguration.class, HealthEndpointWebExtensionConfiguration.class,
HealthEndpointReactiveWebExtensionConfiguration.class })
public class HealthEndpointAutoConfiguration {
}

View File

@ -16,39 +16,27 @@
package org.springframework.boot.actuate.autoconfigure.health;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.actuate.health.CompositeHealthContributor;
import org.springframework.boot.actuate.health.CompositeReactiveHealthContributor;
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.HealthEndpointGroups;
import org.springframework.boot.actuate.health.HealthEndpointGroupsPostProcessor;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.HttpCodeStatusMapper;
import org.springframework.boot.actuate.health.NamedContributor;
import org.springframework.boot.actuate.health.NamedContributors;
import org.springframework.boot.actuate.health.ReactiveHealthContributor;
import org.springframework.boot.actuate.health.ReactiveHealthIndicator;
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.ConditionalOnBooleanProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.health.contributor.HealthContributors;
import org.springframework.boot.health.registry.HealthContributorRegistry;
import org.springframework.boot.health.registry.ReactiveHealthContributorRegistry;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
/**
@ -80,14 +68,9 @@ class HealthEndpointConfiguration {
}
@Bean
@ConditionalOnMissingBean
HealthContributorRegistry healthContributorRegistry(ApplicationContext applicationContext,
HealthEndpointGroups groups, Map<String, HealthContributor> healthContributors,
Map<String, ReactiveHealthContributor> reactiveHealthContributors) {
if (ClassUtils.isPresent("reactor.core.publisher.Flux", applicationContext.getClassLoader())) {
healthContributors.putAll(new AdaptedReactiveHealthContributors(reactiveHealthContributors).get());
}
return new AutoConfiguredHealthContributorRegistry(healthContributors, groups.getNames());
GroupsHealthContributorNameValidator groupsHealthContributorNameValidator(
ObjectProvider<HealthEndpointGroups> healthEndpointGroups) {
return new GroupsHealthContributorNameValidator(healthEndpointGroups.getIfAvailable());
}
@Bean
@ -99,9 +82,11 @@ class HealthEndpointConfiguration {
@Bean
@ConditionalOnMissingBean
HealthEndpoint healthEndpoint(HealthContributorRegistry registry, HealthEndpointGroups groups,
HealthEndpointProperties properties) {
return new HealthEndpoint(registry, groups, properties.getLogging().getSlowIndicatorThreshold());
HealthEndpoint healthEndpoint(HealthContributorRegistry halthContributorRegistry,
ObjectProvider<ReactiveHealthContributorRegistry> reactiveHealthContributorRegistry,
HealthEndpointGroups groups, HealthEndpointProperties properties) {
return new HealthEndpoint(halthContributorRegistry, reactiveHealthContributorRegistry.getIfAvailable(), groups,
properties.getLogging().getSlowIndicatorThreshold());
}
@Bean
@ -140,82 +125,6 @@ class HealthEndpointConfiguration {
}
/**
* Adapter to expose {@link ReactiveHealthContributor} beans as
* {@link HealthContributor} instances.
*/
private static class AdaptedReactiveHealthContributors {
private final Map<String, HealthContributor> adapted;
AdaptedReactiveHealthContributors(Map<String, ReactiveHealthContributor> reactiveContributors) {
Map<String, HealthContributor> adapted = new LinkedHashMap<>();
reactiveContributors.forEach((name, contributor) -> adapted.put(name, adapt(contributor)));
this.adapted = Collections.unmodifiableMap(adapted);
}
private HealthContributor adapt(ReactiveHealthContributor contributor) {
if (contributor instanceof ReactiveHealthIndicator healthIndicator) {
return adapt(healthIndicator);
}
if (contributor instanceof CompositeReactiveHealthContributor healthContributor) {
return adapt(healthContributor);
}
throw new IllegalStateException("Unsupported ReactiveHealthContributor type " + contributor.getClass());
}
private HealthIndicator adapt(ReactiveHealthIndicator indicator) {
return new HealthIndicator() {
@Override
public Health getHealth(boolean includeDetails) {
return indicator.getHealth(includeDetails).block();
}
@Override
public Health health() {
return indicator.health().block();
}
};
}
private CompositeHealthContributor adapt(CompositeReactiveHealthContributor composite) {
return new CompositeHealthContributor() {
@Override
public Iterator<NamedContributor<HealthContributor>> iterator() {
Iterator<NamedContributor<ReactiveHealthContributor>> iterator = composite.iterator();
return new Iterator<>() {
@Override
public boolean hasNext() {
return iterator.hasNext();
}
@Override
public NamedContributor<HealthContributor> next() {
NamedContributor<ReactiveHealthContributor> next = iterator.next();
return NamedContributor.of(next.getName(), adapt(next.getContributor()));
}
};
}
@Override
public HealthContributor getContributor(String name) {
return adapt(composite.getContributor(name));
}
};
}
Map<String, HealthContributor> get() {
return this.adapted;
}
}
/**
* {@link SmartInitializingSingleton} that validates health endpoint group membership,
* throwing a {@link NoSuchHealthContributorException} if an included or excluded
@ -264,10 +173,10 @@ class HealthEndpointConfiguration {
int pathOffset = 0;
Object contributor = this.registry;
while (pathOffset < path.length) {
if (!(contributor instanceof NamedContributors)) {
if (!(contributor instanceof HealthContributors)) {
return false;
}
contributor = ((NamedContributors<?>) contributor).getContributor(path[pathOffset]);
contributor = ((HealthContributors) contributor).getContributor(path[pathOffset]);
pathOffset++;
}
return (contributor != null);

View File

@ -16,16 +16,18 @@
package org.springframework.boot.actuate.autoconfigure.health;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint;
import org.springframework.boot.actuate.autoconfigure.endpoint.expose.EndpointExposure;
import org.springframework.boot.actuate.health.HealthEndpoint;
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;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.health.registry.HealthContributorRegistry;
import org.springframework.boot.health.registry.ReactiveHealthContributorRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -45,9 +47,11 @@ class HealthEndpointReactiveWebExtensionConfiguration {
@ConditionalOnMissingBean
@ConditionalOnBean(HealthEndpoint.class)
ReactiveHealthEndpointWebExtension reactiveHealthEndpointWebExtension(
ReactiveHealthContributorRegistry reactiveHealthContributorRegistry, HealthEndpointGroups groups,
ReactiveHealthContributorRegistry reactiveHealthContributorRegistry,
ObjectProvider<HealthContributorRegistry> healthContributorRegistry, HealthEndpointGroups groups,
HealthEndpointProperties properties) {
return new ReactiveHealthEndpointWebExtension(reactiveHealthContributorRegistry, groups,
return new ReactiveHealthEndpointWebExtension(reactiveHealthContributorRegistry,
healthContributorRegistry.getIfAvailable(), groups,
properties.getLogging().getSlowIndicatorThreshold());
}

View File

@ -16,9 +16,9 @@
package org.springframework.boot.actuate.autoconfigure.health;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint;
import org.springframework.boot.actuate.autoconfigure.endpoint.expose.EndpointExposure;
import org.springframework.boot.actuate.health.HealthContributorRegistry;
import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.health.HealthEndpointGroups;
import org.springframework.boot.actuate.health.HealthEndpointWebExtension;
@ -26,6 +26,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.health.registry.HealthContributorRegistry;
import org.springframework.boot.health.registry.ReactiveHealthContributorRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -45,8 +47,10 @@ class HealthEndpointWebExtensionConfiguration {
@Bean
@ConditionalOnMissingBean
HealthEndpointWebExtension healthEndpointWebExtension(HealthContributorRegistry healthContributorRegistry,
ObjectProvider<ReactiveHealthContributorRegistry> reactiveHealthContributorRegistry,
HealthEndpointGroups groups, HealthEndpointProperties properties) {
return new HealthEndpointWebExtension(healthContributorRegistry, groups,
return new HealthEndpointWebExtension(healthContributorRegistry,
reactiveHealthContributorRegistry.getIfAvailable(), groups,
properties.getLogging().getSlowIndicatorThreshold());
}

View File

@ -1,33 +0,0 @@
/*
* Copyright 2012-present 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 org.springframework.boot.actuate.autoconfigure.OnEndpointElementCondition;
import org.springframework.context.annotation.Condition;
/**
* {@link Condition} that checks if a health indicator is enabled.
*
* @author Stephane Nicoll
*/
class OnEnabledHealthIndicatorCondition extends OnEndpointElementCondition {
OnEnabledHealthIndicatorCondition() {
super("management.health.", ConditionalOnEnabledHealthIndicator.class);
}
}

View File

@ -1,57 +0,0 @@
/*
* Copyright 2012-present 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.LinkedHashMap;
import java.util.Map;
import reactor.core.publisher.Flux;
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;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Configuration for reactive {@link HealthEndpoint} infrastructure beans.
*
* @author Phillip Webb
* @see HealthEndpointAutoConfiguration
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Flux.class)
@ConditionalOnBean(HealthEndpoint.class)
class ReactiveHealthEndpointConfiguration {
@Bean
@ConditionalOnMissingBean
ReactiveHealthContributorRegistry reactiveHealthContributorRegistry(
Map<String, HealthContributor> healthContributors,
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 AutoConfiguredReactiveHealthContributorRegistry(allContributors, groups.getNames());
}
}

View File

@ -16,13 +16,14 @@
package org.springframework.boot.actuate.autoconfigure.ssl;
import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator;
import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration;
import org.springframework.boot.actuate.ssl.SslHealthIndicator;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.health.autoconfigure.contributor.ConditionalOnEnabledHealthIndicator;
import org.springframework.boot.health.contributor.Health;
import org.springframework.boot.info.SslInfo;
import org.springframework.boot.ssl.SslBundles;
import org.springframework.context.annotation.Bean;
@ -33,7 +34,9 @@ import org.springframework.context.annotation.Bean;
* @author Jonatan Ivanov
* @since 3.4.0
*/
@AutoConfiguration(before = HealthContributorAutoConfiguration.class)
@AutoConfiguration(
beforeName = "org.springframework.boot.health.autoconfigure.contributor.HealthContributorAutoConfiguration")
@ConditionalOnClass(Health.class)
@ConditionalOnEnabledHealthIndicator("ssl")
@EnableConfigurationProperties(SslHealthIndicatorProperties.class)
public class SslHealthContributorAutoConfiguration {

View File

@ -16,13 +16,14 @@
package org.springframework.boot.actuate.autoconfigure.system;
import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator;
import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration;
import org.springframework.boot.actuate.system.DiskSpaceHealthIndicator;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.health.autoconfigure.contributor.ConditionalOnEnabledHealthIndicator;
import org.springframework.boot.health.contributor.Health;
import org.springframework.context.annotation.Bean;
/**
@ -33,7 +34,9 @@ import org.springframework.context.annotation.Bean;
* @author Andy Wilkinson
* @since 2.0.0
*/
@AutoConfiguration(before = HealthContributorAutoConfiguration.class)
@AutoConfiguration(
beforeName = "org.springframework.boot.health.autoconfigure.contributor.HealthContributorAutoConfiguration")
@ConditionalOnClass(Health.class)
@ConditionalOnEnabledHealthIndicator("diskspace")
@EnableConfigurationProperties(DiskSpaceHealthIndicatorProperties.class)
public class DiskSpaceHealthContributorAutoConfiguration {

View File

@ -11,7 +11,6 @@ org.springframework.boot.actuate.autoconfigure.endpoint.jackson.JacksonEndpointA
org.springframework.boot.actuate.autoconfigure.endpoint.jmx.JmxEndpointAutoConfiguration
org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration
org.springframework.boot.actuate.autoconfigure.env.EnvironmentEndpointAutoConfiguration
org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration
org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration
org.springframework.boot.actuate.autoconfigure.info.InfoContributorAutoConfiguration
org.springframework.boot.actuate.autoconfigure.info.InfoEndpointAutoConfiguration

View File

@ -23,6 +23,7 @@ import org.springframework.boot.actuate.availability.ReadinessStateHealthIndicat
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration;
import org.springframework.boot.availability.ApplicationAvailability;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
@ -60,6 +61,13 @@ class AvailabilityProbesAutoConfigurationTests {
.run(this::hasProbesBeans);
}
@Test
void probesWhenPropertyEnabledButNoHealthDependencyDoesNotAddBeans() {
this.contextRunner.withPropertyValues("management.endpoint.health.probes.enabled=true")
.withClassLoader(new FilteredClassLoader("org.springframework.boot.health"))
.run(this::doesNotHaveProbeBeans);
}
@Test
void probesWhenKubernetesAndPropertyDisabledAddsNotBeans() {
this.contextRunner

View File

@ -25,13 +25,13 @@ import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.autoconfigure.beans.BeansEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration;
import org.springframework.boot.actuate.endpoint.annotation.DeleteOperation;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
import org.springframework.boot.actuate.endpoint.jmx.annotation.JmxEndpoint;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration;
import org.springframework.boot.health.autoconfigure.contributor.HealthContributorAutoConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.util.StringUtils;

View File

@ -31,11 +31,12 @@ import org.springframework.boot.actuate.audit.InMemoryAuditEventRepository;
import org.springframework.boot.actuate.autoconfigure.beans.BeansEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.context.ShutdownEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
import org.springframework.boot.actuate.web.exchanges.InMemoryHttpExchangeRepository;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration;
import org.springframework.boot.health.autoconfigure.contributor.HealthContributorAutoConfiguration;
import org.springframework.boot.health.autoconfigure.registry.HealthContributorRegistryAutoConfiguration;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -53,7 +54,8 @@ class JmxEndpointIntegrationTests {
private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(JmxAutoConfiguration.class, EndpointAutoConfiguration.class,
JmxEndpointAutoConfiguration.class, HealthContributorAutoConfiguration.class))
JmxEndpointAutoConfiguration.class, HealthContributorRegistryAutoConfiguration.class,
HealthContributorAutoConfiguration.class))
.withUserConfiguration(HttpExchangeRepositoryConfiguration.class, AuditEventRepositoryConfiguration.class)
.withPropertyValues("spring.jmx.enabled=true")
.withConfiguration(AutoConfigurations.of(HealthEndpointAutoConfiguration.class,

View File

@ -1,54 +0,0 @@
/*
* Copyright 2012-present 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");
}
}

View File

@ -31,10 +31,10 @@ 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.health.contributor.Status;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;

View File

@ -1,55 +0,0 @@
/*
* Copyright 2012-present 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");
}
}

View File

@ -30,26 +30,30 @@ import org.springframework.boot.actuate.endpoint.ApiVersion;
import org.springframework.boot.actuate.endpoint.SecurityContext;
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
import org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
import org.springframework.boot.actuate.health.CompositeHealthContributor;
import org.springframework.boot.actuate.health.DefaultHealthContributorRegistry;
import org.springframework.boot.actuate.health.DefaultReactiveHealthContributorRegistry;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthComponent;
import org.springframework.boot.actuate.health.HealthContributorRegistry;
import org.springframework.boot.actuate.health.CompositeHealthDescriptor;
import org.springframework.boot.actuate.health.HealthDescriptor;
import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.health.HealthEndpointGroups;
import org.springframework.boot.actuate.health.HealthEndpointGroupsPostProcessor;
import org.springframework.boot.actuate.health.HealthEndpointWebExtension;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.HttpCodeStatusMapper;
import org.springframework.boot.actuate.health.NamedContributor;
import org.springframework.boot.actuate.health.ReactiveHealthContributorRegistry;
import org.springframework.boot.actuate.health.IndicatedHealthDescriptor;
import org.springframework.boot.actuate.health.ReactiveHealthEndpointWebExtension;
import org.springframework.boot.actuate.health.ReactiveHealthIndicator;
import org.springframework.boot.actuate.health.Status;
import org.springframework.boot.actuate.health.StatusAggregator;
import org.springframework.boot.actuate.health.SystemHealth;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.health.autoconfigure.contributor.HealthContributorAutoConfiguration;
import org.springframework.boot.health.autoconfigure.registry.HealthContributorRegistryAutoConfiguration;
import org.springframework.boot.health.contributor.CompositeHealthContributor;
import org.springframework.boot.health.contributor.Health;
import org.springframework.boot.health.contributor.HealthContributors;
import org.springframework.boot.health.contributor.HealthIndicator;
import org.springframework.boot.health.contributor.ReactiveHealthContributors;
import org.springframework.boot.health.contributor.ReactiveHealthIndicator;
import org.springframework.boot.health.contributor.Status;
import org.springframework.boot.health.registry.DefaultHealthContributorRegistry;
import org.springframework.boot.health.registry.DefaultReactiveHealthContributorRegistry;
import org.springframework.boot.health.registry.HealthContributorRegistry;
import org.springframework.boot.health.registry.ReactiveHealthContributorRegistry;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
@ -74,14 +78,14 @@ class HealthEndpointAutoConfigurationTests {
private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()
.withUserConfiguration(HealthIndicatorsConfiguration.class)
.withConfiguration(
AutoConfigurations.of(HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class));
.withConfiguration(AutoConfigurations.of(HealthEndpointAutoConfiguration.class,
HealthContributorRegistryAutoConfiguration.class, HealthContributorAutoConfiguration.class));
private final ReactiveWebApplicationContextRunner reactiveContextRunner = new ReactiveWebApplicationContextRunner()
.withUserConfiguration(HealthIndicatorsConfiguration.class)
.withConfiguration(
AutoConfigurations.of(HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class,
WebEndpointAutoConfiguration.class, EndpointAutoConfiguration.class));
.withConfiguration(AutoConfigurations.of(HealthEndpointAutoConfiguration.class,
HealthContributorRegistryAutoConfiguration.class, HealthContributorAutoConfiguration.class,
WebEndpointAutoConfiguration.class, EndpointAutoConfiguration.class));
@Test
void runWhenHealthEndpointIsDisabledDoesNotCreateBeans() {
@ -89,9 +93,7 @@ class HealthEndpointAutoConfigurationTests {
assertThat(context).doesNotHaveBean(StatusAggregator.class);
assertThat(context).doesNotHaveBean(HttpCodeStatusMapper.class);
assertThat(context).doesNotHaveBean(HealthEndpointGroups.class);
assertThat(context).doesNotHaveBean(HealthContributorRegistry.class);
assertThat(context).doesNotHaveBean(HealthEndpoint.class);
assertThat(context).doesNotHaveBean(ReactiveHealthContributorRegistry.class);
assertThat(context).doesNotHaveBean(HealthEndpointWebExtension.class);
assertThat(context).doesNotHaveBean(ReactiveHealthEndpointWebExtension.class);
});
@ -192,8 +194,8 @@ class HealthEndpointAutoConfigurationTests {
void runCreatesHealthContributorRegistryContainingHealthBeans() {
this.contextRunner.run((context) -> {
HealthContributorRegistry registry = context.getBean(HealthContributorRegistry.class);
Object[] names = registry.stream().map(NamedContributor::getName).toArray();
assertThat(names).containsExactlyInAnyOrder("simple", "additional", "ping", "reactive");
Object[] names = registry.stream().map(HealthContributors.Entry::name).toArray();
assertThat(names).containsExactlyInAnyOrder("simple", "additional", "ping");
});
}
@ -202,7 +204,7 @@ class HealthEndpointAutoConfigurationTests {
ClassLoader classLoader = new FilteredClassLoader(Mono.class, Flux.class);
this.contextRunner.withClassLoader(classLoader).run((context) -> {
HealthContributorRegistry registry = context.getBean(HealthContributorRegistry.class);
Object[] names = registry.stream().map(NamedContributor::getName).toArray();
Object[] names = registry.stream().map(HealthContributors.Entry::name).toArray();
assertThat(names).containsExactlyInAnyOrder("simple", "additional", "ping");
});
}
@ -211,7 +213,7 @@ class HealthEndpointAutoConfigurationTests {
void runWhenHasHealthContributorRegistryBeanDoesNotCreateAdditionalRegistry() {
this.contextRunner.withUserConfiguration(HealthContributorRegistryConfiguration.class).run((context) -> {
HealthContributorRegistry registry = context.getBean(HealthContributorRegistry.class);
Object[] names = registry.stream().map(NamedContributor::getName).toArray();
Object[] names = registry.stream().map(HealthContributors.Entry::name).toArray();
assertThat(names).isEmpty();
});
}
@ -220,8 +222,8 @@ class HealthEndpointAutoConfigurationTests {
void runCreatesHealthEndpoint() {
this.contextRunner.withPropertyValues("management.endpoint.health.show-details=always").run((context) -> {
HealthEndpoint endpoint = context.getBean(HealthEndpoint.class);
Health health = (Health) endpoint.healthForPath("simple");
assertThat(health.getDetails()).containsEntry("counter", 42);
IndicatedHealthDescriptor descriptor = (IndicatedHealthDescriptor) endpoint.healthForPath("simple");
assertThat(descriptor.getDetails()).containsEntry("counter", 42);
});
}
@ -234,11 +236,12 @@ class HealthEndpointAutoConfigurationTests {
}
@Test
void runCreatesReactiveHealthContributorRegistryContainingAdaptedBeans() {
void runCreatesReactiveHealthContributorRegistryContainingReactiveHealthBeans() {
this.reactiveContextRunner.run((context) -> {
ReactiveHealthContributorRegistry registry = context.getBean(ReactiveHealthContributorRegistry.class);
Object[] names = registry.stream().map(NamedContributor::getName).toArray();
assertThat(names).containsExactlyInAnyOrder("simple", "additional", "reactive", "ping");
ReactiveHealthContributorRegistry reactiveRegistry = context
.getBean(ReactiveHealthContributorRegistry.class);
Object[] names = reactiveRegistry.stream().map(ReactiveHealthContributors.Entry::name).toArray();
assertThat(names).containsExactlyInAnyOrder("reactive");
});
}
@ -247,7 +250,7 @@ class HealthEndpointAutoConfigurationTests {
this.reactiveContextRunner.withUserConfiguration(ReactiveHealthContributorRegistryConfiguration.class)
.run((context) -> {
ReactiveHealthContributorRegistry registry = context.getBean(ReactiveHealthContributorRegistry.class);
Object[] names = registry.stream().map(NamedContributor::getName).toArray();
Object[] names = registry.stream().map(ReactiveHealthContributors.Entry::name).toArray();
assertThat(names).isEmpty();
});
}
@ -256,11 +259,11 @@ class HealthEndpointAutoConfigurationTests {
void runCreatesHealthEndpointWebExtension() {
this.contextRunner.run((context) -> {
HealthEndpointWebExtension webExtension = context.getBean(HealthEndpointWebExtension.class);
WebEndpointResponse<HealthComponent> response = webExtension.health(ApiVersion.V3,
WebEndpointResponse<HealthDescriptor> response = webExtension.health(ApiVersion.V3,
WebServerNamespace.SERVER, SecurityContext.NONE, true, "simple");
Health health = (Health) response.getBody();
IndicatedHealthDescriptor descriptor = (IndicatedHealthDescriptor) response.getBody();
assertThat(response.getStatus()).isEqualTo(200);
assertThat(health.getDetails()).containsEntry("counter", 42);
assertThat(descriptor.getDetails()).containsEntry("counter", 42);
});
}
@ -268,7 +271,7 @@ class HealthEndpointAutoConfigurationTests {
void runWhenHasHealthEndpointWebExtensionBeanDoesNotCreateExtraHealthEndpointWebExtension() {
this.contextRunner.withUserConfiguration(HealthEndpointWebExtensionConfiguration.class).run((context) -> {
HealthEndpointWebExtension webExtension = context.getBean(HealthEndpointWebExtension.class);
WebEndpointResponse<HealthComponent> response = webExtension.health(ApiVersion.V3,
WebEndpointResponse<HealthDescriptor> response = webExtension.health(ApiVersion.V3,
WebServerNamespace.SERVER, SecurityContext.NONE, true, "simple");
assertThat(response).isNull();
});
@ -278,10 +281,10 @@ class HealthEndpointAutoConfigurationTests {
void runCreatesReactiveHealthEndpointWebExtension() {
this.reactiveContextRunner.run((context) -> {
ReactiveHealthEndpointWebExtension webExtension = context.getBean(ReactiveHealthEndpointWebExtension.class);
Mono<WebEndpointResponse<? extends HealthComponent>> response = webExtension.health(ApiVersion.V3,
Mono<WebEndpointResponse<? extends HealthDescriptor>> response = webExtension.health(ApiVersion.V3,
WebServerNamespace.SERVER, SecurityContext.NONE, true, "simple");
Health health = (Health) (response.block().getBody());
assertThat(health.getDetails()).containsEntry("counter", 42);
IndicatedHealthDescriptor descriptor = (IndicatedHealthDescriptor) (response.block().getBody());
assertThat(descriptor.getDetails()).containsEntry("counter", 42);
});
}
@ -291,7 +294,7 @@ class HealthEndpointAutoConfigurationTests {
.run((context) -> {
ReactiveHealthEndpointWebExtension webExtension = context
.getBean(ReactiveHealthEndpointWebExtension.class);
Mono<WebEndpointResponse<? extends HealthComponent>> response = webExtension.health(ApiVersion.V3,
Mono<WebEndpointResponse<? extends HealthDescriptor>> response = webExtension.health(ApiVersion.V3,
WebServerNamespace.SERVER, SecurityContext.NONE, true, "simple");
assertThat(response).isNull();
});
@ -312,12 +315,12 @@ class HealthEndpointAutoConfigurationTests {
void runWithIndicatorsInParentContextFindsIndicators() {
new ApplicationContextRunner().withUserConfiguration(HealthIndicatorsConfiguration.class)
.run((parent) -> new WebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(HealthContributorAutoConfiguration.class,
HealthEndpointAutoConfiguration.class))
.withConfiguration(AutoConfigurations.of(HealthEndpointAutoConfiguration.class,
HealthContributorRegistryAutoConfiguration.class, HealthContributorAutoConfiguration.class))
.withParent(parent)
.run((context) -> {
HealthComponent health = context.getBean(HealthEndpoint.class).health();
Map<String, HealthComponent> components = ((SystemHealth) health).getComponents();
HealthDescriptor descriptor = context.getBean(HealthEndpoint.class).health();
Map<String, HealthDescriptor> components = ((CompositeHealthDescriptor) descriptor).getComponents();
assertThat(components).containsKeys("additional", "ping", "simple");
}));
}
@ -326,17 +329,24 @@ class HealthEndpointAutoConfigurationTests {
void runWithReactiveContextAndIndicatorsInParentContextFindsIndicators() {
new ApplicationContextRunner().withUserConfiguration(HealthIndicatorsConfiguration.class)
.run((parent) -> new ReactiveWebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(HealthContributorAutoConfiguration.class,
HealthEndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class,
EndpointAutoConfiguration.class))
.withConfiguration(AutoConfigurations.of(HealthEndpointAutoConfiguration.class,
HealthContributorRegistryAutoConfiguration.class, HealthContributorAutoConfiguration.class,
WebEndpointAutoConfiguration.class, EndpointAutoConfiguration.class))
.withParent(parent)
.run((context) -> {
HealthComponent health = context.getBean(HealthEndpoint.class).health();
Map<String, HealthComponent> components = ((SystemHealth) health).getComponents();
HealthDescriptor descriptor = context.getBean(HealthEndpoint.class).health();
Map<String, HealthDescriptor> components = ((CompositeHealthDescriptor) descriptor).getComponents();
assertThat(components).containsKeys("additional", "ping", "simple");
}));
}
@Test
void runWithClashingGroupNameThrowsException() {
this.contextRunner.withPropertyValues("management.endpoint.health.group.ping.include=*")
.run((context) -> assertThat(context).getFailure()
.hasMessageContaining("HealthContributor with name \"ping\" clashes with group"));
}
@Configuration(proxyBeanMethods = false)
static class HealthIndicatorsConfiguration {

View File

@ -23,6 +23,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointConfiguration.HealthEndpointGroupMembershipValidator.NoSuchHealthContributorException;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.diagnostics.FailureAnalysis;
import org.springframework.boot.health.autoconfigure.registry.HealthContributorRegistryAutoConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import static org.assertj.core.api.Assertions.assertThat;
@ -34,8 +35,8 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
class NoSuchHealthContributorFailureAnalyzerTests {
private final ApplicationContextRunner runner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(HealthEndpointAutoConfiguration.class));
private final ApplicationContextRunner runner = new ApplicationContextRunner().withConfiguration(AutoConfigurations
.of(HealthEndpointAutoConfiguration.class, HealthContributorRegistryAutoConfiguration.class));
@Test
void analyzesMissingRequiredConfiguration() throws Throwable {

View File

@ -22,12 +22,13 @@ import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.autoconfigure.ssl.SslHealthContributorAutoConfigurationTests.CustomSslInfoConfiguration.CustomSslHealthIndicator;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.Status;
import org.springframework.boot.actuate.ssl.SslHealthIndicator;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration;
import org.springframework.boot.health.autoconfigure.registry.HealthContributorRegistryAutoConfiguration;
import org.springframework.boot.health.contributor.Health;
import org.springframework.boot.health.contributor.HealthIndicator;
import org.springframework.boot.health.contributor.Status;
import org.springframework.boot.info.SslInfo;
import org.springframework.boot.info.SslInfo.CertificateChainInfo;
import org.springframework.boot.ssl.SslBundles;
@ -47,8 +48,8 @@ import static org.assertj.core.api.Assertions.assertThat;
class SslHealthContributorAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(
AutoConfigurations.of(SslHealthContributorAutoConfiguration.class, SslAutoConfiguration.class))
.withConfiguration(AutoConfigurations.of(SslHealthContributorAutoConfiguration.class,
HealthContributorRegistryAutoConfiguration.class, SslAutoConfiguration.class))
.withPropertyValues("server.ssl.bundle=ssltest",
"spring.ssl.bundle.jks.ssltest.keystore.location=classpath:test.jks");

View File

@ -18,9 +18,9 @@ package org.springframework.boot.actuate.autoconfigure.system;
import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration;
import org.springframework.boot.actuate.system.DiskSpaceHealthIndicator;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.health.autoconfigure.contributor.HealthContributorAutoConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.util.unit.DataSize;

View File

@ -26,6 +26,7 @@ dependencies {
testImplementation(project(":spring-boot-project:spring-boot-actuator-autoconfigure"))
testImplementation(project(":spring-boot-project:spring-boot-cache"))
testImplementation(project(":spring-boot-project:spring-boot-flyway"))
testImplementation(project(":spring-boot-project:spring-boot-health"))
testImplementation(project(":spring-boot-project:spring-boot-http-converter"))
testImplementation(project(":spring-boot-project:spring-boot-integration"))
testImplementation(project(":spring-boot-project:spring-boot-jackson"))

View File

@ -29,23 +29,24 @@ import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.docs.MockMvcEndpointDocumentationTests;
import org.springframework.boot.actuate.endpoint.SecurityContext;
import org.springframework.boot.actuate.health.AdditionalHealthEndpointPath;
import org.springframework.boot.actuate.health.CompositeHealthContributor;
import org.springframework.boot.actuate.health.DefaultHealthContributorRegistry;
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.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;
import org.springframework.boot.actuate.health.SimpleStatusAggregator;
import org.springframework.boot.actuate.health.StatusAggregator;
import org.springframework.boot.actuate.system.DiskSpaceHealthIndicator;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.jdbc.actuate.health.DataSourceHealthIndicator;
import org.springframework.boot.health.autoconfigure.registry.HealthContributorNameGenerator;
import org.springframework.boot.health.contributor.CompositeHealthContributor;
import org.springframework.boot.health.contributor.Health;
import org.springframework.boot.health.contributor.HealthContributor;
import org.springframework.boot.health.contributor.HealthIndicator;
import org.springframework.boot.health.registry.DefaultHealthContributorRegistry;
import org.springframework.boot.health.registry.HealthContributorRegistry;
import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration;
import org.springframework.boot.jdbc.health.DataSourceHealthIndicator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
@ -105,11 +106,12 @@ class HealthEndpointDocumentationTests extends MockMvcEndpointDocumentationTests
static class TestConfiguration {
@Bean
HealthEndpoint healthEndpoint(Map<String, HealthContributor> healthContributors) {
HealthContributorRegistry registry = new DefaultHealthContributorRegistry(healthContributors);
HealthEndpoint healthEndpoint(Map<String, HealthContributor> contributors) {
HealthContributorRegistry registry = new DefaultHealthContributorRegistry(null,
HealthContributorNameGenerator.withoutStandardSuffixes().registrar(contributors));
HealthEndpointGroup primary = new TestHealthEndpointGroup();
HealthEndpointGroups groups = HealthEndpointGroups.of(primary, Collections.emptyMap());
return new HealthEndpoint(registry, groups, null);
return new HealthEndpoint(registry, null, groups, null);
}
@Bean

View File

@ -25,6 +25,7 @@ description = "Spring Boot Actuator Integration Tests"
dependencies {
testImplementation(project(":spring-boot-project:spring-boot-actuator"))
testImplementation(project(":spring-boot-project:spring-boot-autoconfigure"))
testImplementation(project(":spring-boot-project:spring-boot-health"))
testImplementation(project(":spring-boot-project:spring-boot-http-converter"))
testImplementation(project(":spring-boot-project:spring-boot-jackson"))
testImplementation(project(":spring-boot-project:spring-boot-jersey"))

View File

@ -17,16 +17,28 @@
package org.springframework.boot.actuate.health;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.endpoint.ApiVersion;
import org.springframework.boot.actuate.endpoint.web.test.WebEndpointTest;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.health.autoconfigure.registry.HealthContributorNameGenerator;
import org.springframework.boot.health.contributor.CompositeHealthContributor;
import org.springframework.boot.health.contributor.CompositeReactiveHealthContributor;
import org.springframework.boot.health.contributor.Health;
import org.springframework.boot.health.contributor.HealthContributor;
import org.springframework.boot.health.contributor.HealthIndicator;
import org.springframework.boot.health.contributor.ReactiveHealthContributor;
import org.springframework.boot.health.contributor.ReactiveHealthIndicator;
import org.springframework.boot.health.registry.DefaultHealthContributorRegistry;
import org.springframework.boot.health.registry.DefaultReactiveHealthContributorRegistry;
import org.springframework.boot.health.registry.HealthContributorRegistry;
import org.springframework.boot.health.registry.ReactiveHealthContributorRegistry;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -199,8 +211,7 @@ class HealthEndpointWebIntegrationTests {
}
}
private <R extends ContributorRegistry<?>> R getContributorRegistry(ApplicationContext context,
Class<R> registryType) {
private <R> R getContributorRegistry(ApplicationContext context, Class<R> registryType) {
return context.getBeanProvider(registryType).getIfAvailable();
}
@ -241,41 +252,44 @@ class HealthEndpointWebIntegrationTests {
static class TestConfiguration {
@Bean
HealthContributorRegistry healthContributorRegistry(Map<String, HealthContributor> healthContributorBeans) {
return new DefaultHealthContributorRegistry(healthContributorBeans);
HealthContributorRegistry healthContributorRegistry(Map<String, HealthContributor> contributorBeans) {
return new DefaultHealthContributorRegistry(null,
HealthContributorNameGenerator.withoutStandardSuffixes().registrar(contributorBeans));
}
@Bean
@ConditionalOnWebApplication(type = Type.REACTIVE)
ReactiveHealthContributorRegistry reactiveHealthContributorRegistry(
Map<String, HealthContributor> healthContributorBeans,
Map<String, ReactiveHealthContributor> reactiveHealthContributorBeans) {
Map<String, ReactiveHealthContributor> allIndicators = new LinkedHashMap<>(reactiveHealthContributorBeans);
healthContributorBeans.forEach((name, contributor) -> allIndicators.computeIfAbsent(name,
(key) -> ReactiveHealthContributor.adapt(contributor)));
return new DefaultReactiveHealthContributorRegistry(allIndicators);
Map<String, ReactiveHealthContributor> contributorBeans) {
return new DefaultReactiveHealthContributorRegistry(null,
HealthContributorNameGenerator.withoutStandardSuffixes().registrar(contributorBeans));
}
@Bean
HealthEndpoint healthEndpoint(HealthContributorRegistry healthContributorRegistry,
ObjectProvider<ReactiveHealthContributorRegistry> reactiveHealthContributorRegistry,
HealthEndpointGroups healthEndpointGroups) {
return new HealthEndpoint(healthContributorRegistry, healthEndpointGroups, null);
return new HealthEndpoint(healthContributorRegistry, reactiveHealthContributorRegistry.getIfAvailable(),
healthEndpointGroups, null);
}
@Bean
@ConditionalOnWebApplication(type = Type.SERVLET)
HealthEndpointWebExtension healthWebEndpointExtension(HealthContributorRegistry healthContributorRegistry,
ObjectProvider<ReactiveHealthContributorRegistry> reactiveHealthContributorRegistry,
HealthEndpointGroups healthEndpointGroups) {
return new HealthEndpointWebExtension(healthContributorRegistry, healthEndpointGroups, null);
return new HealthEndpointWebExtension(healthContributorRegistry,
reactiveHealthContributorRegistry.getIfAvailable(), healthEndpointGroups, null);
}
@Bean
@ConditionalOnWebApplication(type = Type.REACTIVE)
ReactiveHealthEndpointWebExtension reactiveHealthWebEndpointExtension(
ReactiveHealthContributorRegistry reactiveHealthContributorRegistry,
ObjectProvider<HealthContributorRegistry> healthContributorRegistry,
HealthEndpointGroups healthEndpointGroups) {
return new ReactiveHealthEndpointWebExtension(reactiveHealthContributorRegistry, healthEndpointGroups,
null);
return new ReactiveHealthEndpointWebExtension(reactiveHealthContributorRegistry,
healthContributorRegistry.getIfAvailable(), healthEndpointGroups, null);
}
@Bean

View File

@ -27,6 +27,7 @@ description = "Spring Boot Actuator"
dependencies {
api(project(":spring-boot-project:spring-boot"))
optional(project(":spring-boot-project:spring-boot-health"))
optional(project(":spring-boot-project:spring-boot-http-converter"))
optional(project(":spring-boot-project:spring-boot-jsonb"))
optional(project(":spring-boot-project:spring-boot-validation"))

View File

@ -21,12 +21,12 @@ import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
import org.springframework.boot.actuate.health.Health.Builder;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.Status;
import org.springframework.boot.availability.ApplicationAvailability;
import org.springframework.boot.availability.AvailabilityState;
import org.springframework.boot.health.contributor.AbstractHealthIndicator;
import org.springframework.boot.health.contributor.Health;
import org.springframework.boot.health.contributor.HealthIndicator;
import org.springframework.boot.health.contributor.Status;
import org.springframework.util.Assert;
/**
@ -76,7 +76,7 @@ public class AvailabilityStateHealthIndicator extends AbstractHealthIndicator {
}
@Override
protected void doHealthCheck(Builder builder) throws Exception {
protected void doHealthCheck(Health.Builder builder) throws Exception {
AvailabilityState state = getState(this.applicationAvailability);
Status status = this.statusMappings.get(state);
if (status == null) {

View File

@ -16,11 +16,11 @@
package org.springframework.boot.actuate.availability;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.Status;
import org.springframework.boot.availability.ApplicationAvailability;
import org.springframework.boot.availability.AvailabilityState;
import org.springframework.boot.availability.LivenessState;
import org.springframework.boot.health.contributor.HealthIndicator;
import org.springframework.boot.health.contributor.Status;
/**
* A {@link HealthIndicator} that checks the {@link LivenessState} of the application.

View File

@ -16,11 +16,11 @@
package org.springframework.boot.actuate.availability;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.Status;
import org.springframework.boot.availability.ApplicationAvailability;
import org.springframework.boot.availability.AvailabilityState;
import org.springframework.boot.availability.ReadinessState;
import org.springframework.boot.health.contributor.HealthIndicator;
import org.springframework.boot.health.contributor.Status;
/**
* A {@link HealthIndicator} that checks the {@link ReadinessState} of the application.

View File

@ -1,66 +0,0 @@
/*
* Copyright 2012-present 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.Iterator;
import org.springframework.util.Assert;
/**
* Adapts a {@link CompositeHealthContributor} to a
* {@link CompositeReactiveHealthContributor} so that it can be safely invoked in a
* reactive environment.
*
* @author Phillip Webb
* @see ReactiveHealthContributor#adapt(HealthContributor)
*/
class CompositeHealthContributorReactiveAdapter implements CompositeReactiveHealthContributor {
private final CompositeHealthContributor delegate;
CompositeHealthContributorReactiveAdapter(CompositeHealthContributor delegate) {
Assert.notNull(delegate, "'delegate' must not be null");
this.delegate = delegate;
}
@Override
public Iterator<NamedContributor<ReactiveHealthContributor>> iterator() {
Iterator<NamedContributor<HealthContributor>> iterator = this.delegate.iterator();
return new Iterator<>() {
@Override
public boolean hasNext() {
return iterator.hasNext();
}
@Override
public NamedContributor<ReactiveHealthContributor> next() {
NamedContributor<HealthContributor> namedContributor = iterator.next();
return NamedContributor.of(namedContributor.getName(),
ReactiveHealthContributor.adapt(namedContributor.getContributor()));
}
};
}
@Override
public ReactiveHealthContributor getContributor(String name) {
HealthContributor contributor = this.delegate.getContributor(name);
return (contributor != null) ? ReactiveHealthContributor.adapt(contributor) : null;
}
}

View File

@ -21,37 +21,32 @@ import java.util.TreeMap;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.springframework.boot.actuate.endpoint.ApiVersion;
import org.springframework.boot.health.contributor.Status;
import org.springframework.util.Assert;
/**
* A {@link HealthComponent} that is composed of other {@link HealthComponent} instances.
* Used to provide a unified view of related components. For example, a database health
* indicator may be a composite containing the {@link Health} of each datasource
* connection.
* Description of health that is composed of other {@link HealthDescriptor health
* descriptors}.
*
* @author Phillip Webb
* @since 2.2.0
* @since 4.0.0
*/
public class CompositeHealth extends HealthComponent {
public sealed class CompositeHealthDescriptor extends HealthDescriptor permits SystemHealthDescriptor {
private final ApiVersion apiVersion;
private final Status status;
private final Map<String, HealthComponent> components;
private final Map<String, HealthDescriptor> components;
private final Map<String, HealthComponent> details;
CompositeHealth(ApiVersion apiVersion, Status status, Map<String, HealthComponent> components) {
CompositeHealthDescriptor(ApiVersion apiVersion, Status status, Map<String, HealthDescriptor> components) {
Assert.notNull(apiVersion, "'apiVersion' must not be null");
Assert.notNull(status, "'status' must not be null");
this.apiVersion = apiVersion;
this.status = status;
this.components = (apiVersion != ApiVersion.V3) ? null : sort(components);
this.details = (apiVersion != ApiVersion.V2) ? null : sort(components);
}
private Map<String, HealthComponent> sort(Map<String, HealthComponent> components) {
return (components != null) ? new TreeMap<>(components) : components;
this.components = (components != null) ? new TreeMap<>(components) : components;
}
@Override
@ -60,14 +55,13 @@ public class CompositeHealth extends HealthComponent {
}
@JsonInclude(Include.NON_EMPTY)
public Map<String, HealthComponent> getComponents() {
return this.components;
public Map<String, HealthDescriptor> getComponents() {
return (this.apiVersion == ApiVersion.V3) ? this.components : null;
}
@JsonInclude(Include.NON_EMPTY)
@JsonProperty
public Map<String, HealthComponent> getDetails() {
return this.details;
public Map<String, HealthDescriptor> getDetails() {
return (this.apiVersion == ApiVersion.V2) ? this.components : null;
}
}

View File

@ -0,0 +1,196 @@
/*
* Copyright 2012-present 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.Iterator;
import reactor.core.publisher.Mono;
import org.springframework.boot.health.contributor.Health;
import org.springframework.boot.health.contributor.HealthContributor;
import org.springframework.boot.health.contributor.HealthContributors;
import org.springframework.boot.health.contributor.HealthIndicator;
import org.springframework.boot.health.contributor.ReactiveHealthContributor;
import org.springframework.boot.health.contributor.ReactiveHealthContributors;
import org.springframework.boot.health.contributor.ReactiveHealthIndicator;
import org.springframework.boot.health.registry.HealthContributorRegistry;
import org.springframework.boot.health.registry.ReactiveHealthContributorRegistry;
import org.springframework.util.StringUtils;
/**
* Allows {@link HealthEndpointSupport} to access blocking or reactive contributors and
* registries in a uniform way.
*
* @param <H> the health type
* @param <D> the descriptor type
* @author Phillip Webb
*/
sealed interface Contributor<H, D> extends Iterable<Contributor.Child<H, D>> {
/**
* Return if this contributor is a composite and may have children.
* @return if the contributor is a composite
*/
boolean isComposite();
/**
* Get the child with the given name. Must only be called if {@link #isComposite()}
* returns {@code true}.
* @param name the child name
* @return the child or {@code null}
*/
Contributor<H, D> getChild(String name);
/**
* Get the health. Must only be called if {@link #isComposite()} returns
* {@code false}.
* @param includeDetails if details are to be included.
* @return the health
*/
D getDescriptor(boolean includeDetails);
/**
* Return an identifier for logging purposes.
* @param name the name if known
* @return an identifier
*/
default String getIdentifier(String name) {
String className = getContributorClassName();
return (!StringUtils.hasLength(name)) ? className : className + " (" + name + ")";
}
/**
* Return the class name of the underlying contributor.
* @return the contributor class name
*/
String getContributorClassName();
/**
* Factory method to create a blocking {@link Contributor} from the given registries.
* @param registry the source registry
* @param fallbackRegistry the fallback registry or {@code null}
* @return a new {@link Contributor}
*/
static Blocking blocking(HealthContributorRegistry registry, ReactiveHealthContributorRegistry fallbackRegistry) {
return new Blocking((fallbackRegistry != null)
? HealthContributors.of(registry, fallbackRegistry.asHealthContributors()) : registry);
}
/**
* Factory method to create a reactive {@link Contributor} from the given registries.
* @param registry the registry
* @param fallbackRegistry the fallback registry or {@code null}
* @return a new {@link Contributor}
*/
static Reactive reactive(ReactiveHealthContributorRegistry registry, HealthContributorRegistry fallbackRegistry) {
return new Reactive((fallbackRegistry != null)
? ReactiveHealthContributors.of(registry, ReactiveHealthContributors.adapt(fallbackRegistry))
: registry);
}
/**
* A child consisting of a name and a contributor.
*
* @param <H> the health type
* @param <D> the descriptor type
* @param name the child name
* @param contributor the contributor
*/
record Child<H, D>(String name, Contributor<H, D> contributor) {
}
/**
* {@link Contributor} to adapt the blocking {@link HealthContributor} and
* {@link HealthContributors} types.
*
* @param contributor the underlying contributor
*/
record Blocking(Object contributor) implements Contributor<Health, HealthDescriptor> {
@Override
public boolean isComposite() {
return contributor() instanceof HealthContributors;
}
@Override
public Blocking getChild(String name) {
HealthContributor child = ((HealthContributors) contributor()).getContributor(name);
return (child != null) ? new Blocking(child) : null;
}
@Override
public Iterator<Child<Health, HealthDescriptor>> iterator() {
return ((HealthContributors) contributor()).stream()
.map((entry) -> new Child<>(entry.name(), new Blocking(entry.contributor())))
.iterator();
}
@Override
public HealthDescriptor getDescriptor(boolean includeDetails) {
Health health = ((HealthIndicator) contributor()).health(includeDetails);
return (health != null) ? new IndicatedHealthDescriptor(health) : null;
}
@Override
public String getContributorClassName() {
return contributor().getClass().getName();
}
}
/**
* {@link Contributor} to adapt the reactive {@link ReactiveHealthContributor} and
* {@link ReactiveHealthContributors} types.
*
* @param contributor the underlying contributor
*/
record Reactive(
Object contributor) implements Contributor<Mono<? extends Health>, Mono<? extends HealthDescriptor>> {
@Override
public boolean isComposite() {
return contributor() instanceof ReactiveHealthContributors;
}
@Override
public Reactive getChild(String name) {
ReactiveHealthContributor child = ((ReactiveHealthContributors) contributor()).getContributor(name);
return (child != null) ? new Reactive(child) : null;
}
@Override
public Iterator<Child<Mono<? extends Health>, Mono<? extends HealthDescriptor>>> iterator() {
return ((ReactiveHealthContributors) contributor()).stream()
.map((entry) -> new Child<>(entry.name(), new Reactive(entry.contributor())))
.iterator();
}
@Override
public Mono<? extends HealthDescriptor> getDescriptor(boolean includeDetails) {
Mono<Health> health = ((ReactiveHealthIndicator) this.contributor).health(includeDetails);
return health.map(IndicatedHealthDescriptor::new);
}
@Override
public String getContributorClassName() {
return contributor().getClass().getName();
}
}
}

View File

@ -1,114 +0,0 @@
/*
* Copyright 2012-present 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 java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Function;
import org.springframework.util.Assert;
/**
* Default {@link ContributorRegistry} implementation.
*
* @param <C> the health contributor type
* @author Phillip Webb
* @see DefaultHealthContributorRegistry
* @see DefaultReactiveHealthContributorRegistry
*/
class DefaultContributorRegistry<C> implements ContributorRegistry<C> {
private final Function<String, String> nameFactory;
private final Object monitor = new Object();
private volatile Map<String, C> contributors;
DefaultContributorRegistry() {
this(Collections.emptyMap());
}
DefaultContributorRegistry(Map<String, C> contributors) {
this(contributors, HealthContributorNameFactory.INSTANCE);
}
DefaultContributorRegistry(Map<String, C> contributors, Function<String, String> nameFactory) {
Assert.notNull(contributors, "'contributors' must not be null");
Assert.notNull(nameFactory, "'nameFactory' must not be null");
this.nameFactory = nameFactory;
Map<String, C> namedContributors = new LinkedHashMap<>();
contributors.forEach((name, contributor) -> namedContributors.put(nameFactory.apply(name), contributor));
this.contributors = Collections.unmodifiableMap(namedContributors);
}
@Override
public void registerContributor(String name, C contributor) {
Assert.notNull(name, "'name' must not be null");
Assert.notNull(contributor, "'contributor' must not be null");
String adaptedName = this.nameFactory.apply(name);
synchronized (this.monitor) {
Assert.state(!this.contributors.containsKey(adaptedName),
() -> "A contributor named \"" + adaptedName + "\" has already been registered");
Map<String, C> contributors = new LinkedHashMap<>(this.contributors);
contributors.put(adaptedName, contributor);
this.contributors = Collections.unmodifiableMap(contributors);
}
}
@Override
public C unregisterContributor(String name) {
Assert.notNull(name, "'name' must not be null");
String adaptedName = this.nameFactory.apply(name);
synchronized (this.monitor) {
C unregistered = this.contributors.get(adaptedName);
if (unregistered != null) {
Map<String, C> contributors = new LinkedHashMap<>(this.contributors);
contributors.remove(adaptedName);
this.contributors = Collections.unmodifiableMap(contributors);
}
return unregistered;
}
}
@Override
public C getContributor(String name) {
return this.contributors.get(name);
}
@Override
public Iterator<NamedContributor<C>> iterator() {
Iterator<Map.Entry<String, C>> iterator = this.contributors.entrySet().iterator();
return new Iterator<>() {
@Override
public boolean hasNext() {
return iterator.hasNext();
}
@Override
public NamedContributor<C> next() {
Entry<String, C> entry = iterator.next();
return NamedContributor.of(entry.getKey(), entry.getValue());
}
};
}
}

View File

@ -1,43 +0,0 @@
/*
* Copyright 2012-present 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.function.Function;
/**
* Default {@link HealthContributorRegistry} implementation.
*
* @author Phillip Webb
* @since 2.2.0
*/
public class DefaultHealthContributorRegistry extends DefaultContributorRegistry<HealthContributor>
implements HealthContributorRegistry {
public DefaultHealthContributorRegistry() {
}
public DefaultHealthContributorRegistry(Map<String, HealthContributor> contributors) {
super(contributors);
}
public DefaultHealthContributorRegistry(Map<String, HealthContributor> contributors,
Function<String, String> nameFactory) {
super(contributors, nameFactory);
}
}

View File

@ -1,43 +0,0 @@
/*
* Copyright 2012-present 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.function.Function;
/**
* Default {@link ReactiveHealthContributorRegistry} implementation.
*
* @author Phillip Webb
* @since 2.0.0
*/
public class DefaultReactiveHealthContributorRegistry extends DefaultContributorRegistry<ReactiveHealthContributor>
implements ReactiveHealthContributorRegistry {
public DefaultReactiveHealthContributorRegistry() {
}
public DefaultReactiveHealthContributorRegistry(Map<String, ReactiveHealthContributor> contributors) {
super(contributors);
}
public DefaultReactiveHealthContributorRegistry(Map<String, ReactiveHealthContributor> contributors,
Function<String, String> nameFactory) {
super(contributors, nameFactory);
}
}

View File

@ -1,48 +0,0 @@
/*
* Copyright 2012-present 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.Locale;
import java.util.function.Function;
/**
* Generate a sensible health indicator name based on its bean name.
*
* @author Stephane Nicoll
* @author Phillip Webb
* @since 2.0.0
*/
public class HealthContributorNameFactory implements Function<String, String> {
private static final String[] SUFFIXES = { "healthindicator", "healthcontributor" };
/**
* A shared singleton {@link HealthContributorNameFactory} instance.
*/
public static final HealthContributorNameFactory INSTANCE = new HealthContributorNameFactory();
@Override
public String apply(String name) {
for (String suffix : SUFFIXES) {
if (name != null && name.toLowerCase(Locale.ENGLISH).endsWith(suffix)) {
return name.substring(0, name.length() - suffix.length());
}
}
return name;
}
}

View File

@ -19,19 +19,16 @@ package org.springframework.boot.actuate.health;
import com.fasterxml.jackson.annotation.JsonUnwrapped;
import org.springframework.boot.actuate.endpoint.OperationResponseBody;
import org.springframework.boot.health.contributor.Status;
/**
* A component that contributes data to results returned from the {@link HealthEndpoint}.
* Description of health including a status.
*
* @author Phillip Webb
* @since 2.2.0
* @see Health
* @see CompositeHealth
* @since 4.0.0
*/
public abstract class HealthComponent implements OperationResponseBody {
HealthComponent() {
}
public abstract sealed class HealthDescriptor implements OperationResponseBody
permits IndicatedHealthDescriptor, CompositeHealthDescriptor {
/**
* Return the status of the component.

View File

@ -27,6 +27,9 @@ import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.Selector;
import org.springframework.boot.actuate.endpoint.annotation.Selector.Match;
import org.springframework.boot.health.contributor.Health;
import org.springframework.boot.health.registry.HealthContributorRegistry;
import org.springframework.boot.health.registry.ReactiveHealthContributorRegistry;
/**
* {@link Endpoint @Endpoint} to expose application health information.
@ -39,53 +42,47 @@ import org.springframework.boot.actuate.endpoint.annotation.Selector.Match;
* @since 2.0.0
*/
@Endpoint(id = "health")
public class HealthEndpoint extends HealthEndpointSupport<HealthContributor, HealthComponent> {
public class HealthEndpoint extends HealthEndpointSupport<Health, HealthDescriptor> {
/**
* Health endpoint id.
*/
public static final EndpointId ID = EndpointId.of("health");
private static final String[] EMPTY_PATH = {};
/**
* Create a new {@link HealthEndpoint} instance.
* @param registry the health contributor registry
* @param fallbackRegistry the fallback registry or {@code null}
* @param groups the health endpoint groups
* @param slowIndicatorLoggingThreshold duration after which slow health indicator
* @param slowContributorLoggingThreshold duration after which slow health indicator
* logging should occur
* @since 2.6.9
* @since 4.0.0
*/
public HealthEndpoint(HealthContributorRegistry registry, HealthEndpointGroups groups,
Duration slowIndicatorLoggingThreshold) {
super(registry, groups, slowIndicatorLoggingThreshold);
public HealthEndpoint(HealthContributorRegistry registry, ReactiveHealthContributorRegistry fallbackRegistry,
HealthEndpointGroups groups, Duration slowContributorLoggingThreshold) {
super(Contributor.blocking(registry, fallbackRegistry), groups, slowContributorLoggingThreshold);
}
@ReadOperation
public HealthComponent health() {
HealthComponent health = health(ApiVersion.V3, EMPTY_PATH);
return (health != null) ? health : DEFAULT_HEALTH;
public HealthDescriptor health() {
HealthDescriptor health = health(ApiVersion.V3, EMPTY_PATH);
return (health != null) ? health : IndicatedHealthDescriptor.UP;
}
@ReadOperation
public HealthComponent healthForPath(@Selector(match = Match.ALL_REMAINING) String... path) {
public HealthDescriptor healthForPath(@Selector(match = Match.ALL_REMAINING) String... path) {
return health(ApiVersion.V3, path);
}
private HealthComponent health(ApiVersion apiVersion, String... path) {
HealthResult<HealthComponent> result = getHealth(apiVersion, null, SecurityContext.NONE, true, path);
return (result != null) ? result.getHealth() : null;
private HealthDescriptor health(ApiVersion apiVersion, String... path) {
Result<HealthDescriptor> result = getResult(apiVersion, null, SecurityContext.NONE, true, path);
return (result != null) ? result.descriptor() : null;
}
@Override
protected HealthComponent getHealth(HealthContributor contributor, boolean includeDetails) {
return ((HealthIndicator) contributor).getHealth(includeDetails);
}
@Override
protected HealthComponent aggregateContributions(ApiVersion apiVersion, Map<String, HealthComponent> contributions,
protected HealthDescriptor aggregateDescriptors(ApiVersion apiVersion, Map<String, HealthDescriptor> contributions,
StatusAggregator statusAggregator, boolean showComponents, Set<String> groupNames) {
return getCompositeHealth(apiVersion, contributions, statusAggregator, showComponents, groupNames);
return getCompositeDescriptor(apiVersion, contributions, statusAggregator, showComponents, groupNames);
}
}

View File

@ -17,6 +17,8 @@
package org.springframework.boot.actuate.health;
import org.springframework.boot.actuate.endpoint.SecurityContext;
import org.springframework.boot.health.contributor.Health;
import org.springframework.boot.health.contributor.HealthContributor;
/**
* A logical grouping of {@link HealthContributor health contributors} that can be exposed
@ -36,8 +38,8 @@ public interface HealthEndpointGroup {
boolean isMember(String name);
/**
* Returns if {@link CompositeHealth#getComponents() health components} should be
* shown in the response.
* Returns if {@link CompositeHealthDescriptor#getComponents() health components}
* should be shown in the response.
* @param securityContext the endpoint security context
* @return {@code true} to shown details or {@code false} to hide them
*/

View File

@ -21,6 +21,7 @@ import java.time.Instant;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
@ -30,6 +31,7 @@ import org.springframework.boot.actuate.endpoint.ApiVersion;
import org.springframework.boot.actuate.endpoint.SecurityContext;
import org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
import org.springframework.boot.convert.DurationStyle;
import org.springframework.boot.health.contributor.Status;
import org.springframework.core.log.LogMessage;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@ -37,62 +39,59 @@ import org.springframework.util.StringUtils;
/**
* Base class for health endpoints and health endpoint extensions.
*
* @param <C> the contributor type
* @param <T> the contributed health component type
* @param <H> the health type
* @param <D> the descriptor type
* @author Phillip Webb
* @author Scott Frederick
*/
abstract class HealthEndpointSupport<C, T> {
abstract class HealthEndpointSupport<H, D> {
static final String[] EMPTY_PATH = {};
private static final Log logger = LogFactory.getLog(HealthEndpointSupport.class);
static final Health DEFAULT_HEALTH = Health.up().build();
private final ContributorRegistry<C> registry;
private final Contributor<H, D> rootContributor;
private final HealthEndpointGroups groups;
private final Duration slowIndicatorLoggingThreshold;
private final Duration slowContributorLoggingThreshold;
/**
* Create a new {@link HealthEndpointSupport} instance.
* @param registry the health contributor registry
* @param rootContributor the health contributor registry
* @param groups the health endpoint groups
* @param slowIndicatorLoggingThreshold duration after which slow health indicator
* @param slowContributorLoggingThreshold duration after which slow health contributor
* logging should occur
*/
HealthEndpointSupport(ContributorRegistry<C> registry, HealthEndpointGroups groups,
Duration slowIndicatorLoggingThreshold) {
Assert.notNull(registry, "'registry' must not be null");
HealthEndpointSupport(Contributor<H, D> rootContributor, HealthEndpointGroups groups,
Duration slowContributorLoggingThreshold) {
Assert.notNull(rootContributor, "'rootContributor' must not be null");
Assert.notNull(groups, "'groups' must not be null");
this.registry = registry;
this.rootContributor = rootContributor;
this.groups = groups;
this.slowIndicatorLoggingThreshold = slowIndicatorLoggingThreshold;
this.slowContributorLoggingThreshold = slowContributorLoggingThreshold;
}
HealthResult<T> getHealth(ApiVersion apiVersion, WebServerNamespace serverNamespace,
SecurityContext securityContext, boolean showAll, String... path) {
if (path.length > 0) {
HealthEndpointGroup group = getHealthGroup(serverNamespace, path);
if (group != null) {
return getHealth(apiVersion, group, securityContext, showAll, path, 1);
}
Result<D> getResult(ApiVersion apiVersion, WebServerNamespace serverNamespace, SecurityContext securityContext,
boolean showAll, String... path) {
HealthEndpointGroup group = (path.length > 0) ? getGroup(serverNamespace, path) : null;
if (group != null) {
return getResult(apiVersion, group, securityContext, showAll, path, 1);
}
return getHealth(apiVersion, this.groups.getPrimary(), securityContext, showAll, path, 0);
return getResult(apiVersion, this.groups.getPrimary(), securityContext, showAll, path, 0);
}
private HealthEndpointGroup getHealthGroup(WebServerNamespace serverNamespace, String... path) {
private HealthEndpointGroup getGroup(WebServerNamespace serverNamespace, String... path) {
if (this.groups.get(path[0]) != null) {
return this.groups.get(path[0]);
}
if (serverNamespace != null) {
AdditionalHealthEndpointPath additionalPath = AdditionalHealthEndpointPath.of(serverNamespace, path[0]);
return this.groups.get(additionalPath);
return this.groups.get(AdditionalHealthEndpointPath.of(serverNamespace, path[0]));
}
return null;
}
private HealthResult<T> getHealth(ApiVersion apiVersion, HealthEndpointGroup group, SecurityContext securityContext,
private Result<D> getResult(ApiVersion apiVersion, HealthEndpointGroup group, SecurityContext securityContext,
boolean showAll, String[] path, int pathOffset) {
boolean showComponents = showAll || group.showComponents(securityContext);
boolean showDetails = showAll || group.showDetails(securityContext);
@ -101,24 +100,23 @@ abstract class HealthEndpointSupport<C, T> {
if (!showComponents && !isRoot) {
return null;
}
Object contributor = getContributor(path, pathOffset);
Contributor<H, D> contributor = getContributor(path, pathOffset);
if (contributor == null) {
return null;
}
String name = getName(path, pathOffset);
Set<String> groupNames = isSystemHealth ? this.groups.getNames() : null;
T health = getContribution(apiVersion, group, name, contributor, showComponents, showDetails, groupNames);
return (health != null) ? new HealthResult<>(health, group) : null;
Set<String> groupNames = (!isSystemHealth) ? null : new TreeSet<>(this.groups.getNames());
D descriptor = getDescriptor(apiVersion, group, name, contributor, showComponents, showDetails, groupNames);
return (descriptor != null) ? new Result<>(descriptor, group) : null;
}
@SuppressWarnings("unchecked")
private Object getContributor(String[] path, int pathOffset) {
Object contributor = this.registry;
private Contributor<H, D> getContributor(String[] path, int pathOffset) {
Contributor<H, D> contributor = this.rootContributor;
while (pathOffset < path.length) {
if (!(contributor instanceof NamedContributors)) {
if (!contributor.isComposite()) {
return null;
}
contributor = ((NamedContributors<C>) contributor).getContributor(path[pathOffset]);
contributor = contributor.getChild(path[pathOffset]);
pathOffset++;
}
return contributor;
@ -134,100 +132,77 @@ abstract class HealthEndpointSupport<C, T> {
return name.toString();
}
@SuppressWarnings("unchecked")
private T getContribution(ApiVersion apiVersion, HealthEndpointGroup group, String name, Object contributor,
boolean showComponents, boolean showDetails, Set<String> groupNames) {
if (contributor instanceof NamedContributors) {
return getAggregateContribution(apiVersion, group, name, (NamedContributors<C>) contributor, showComponents,
showDetails, groupNames);
private D getDescriptor(ApiVersion apiVersion, HealthEndpointGroup group, String name,
Contributor<H, D> contributor, boolean showComponents, boolean showDetails, Set<String> groupNames) {
if (contributor.isComposite()) {
return getAggregateDescriptor(apiVersion, group, name, contributor, showComponents, showDetails,
groupNames);
}
if (contributor != null && (name.isEmpty() || group.isMember(name))) {
return getLoggedHealth((C) contributor, name, showDetails);
if (name.isEmpty() || group.isMember(name)) {
return getDescriptorAndLogIfSlow(contributor, name, showDetails);
}
return null;
}
private T getAggregateContribution(ApiVersion apiVersion, HealthEndpointGroup group, String name,
NamedContributors<C> namedContributors, boolean showComponents, boolean showDetails,
Set<String> groupNames) {
private D getAggregateDescriptor(ApiVersion apiVersion, HealthEndpointGroup group, String name,
Contributor<H, D> contributor, boolean showComponents, boolean showDetails, Set<String> groupNames) {
String prefix = (StringUtils.hasText(name)) ? name + "/" : "";
Map<String, T> contributions = new LinkedHashMap<>();
for (NamedContributor<C> child : namedContributors) {
T contribution = getContribution(apiVersion, group, prefix + child.getName(), child.getContributor(),
showComponents, showDetails, null);
if (contribution != null) {
contributions.put(child.getName(), contribution);
Map<String, D> descriptors = new LinkedHashMap<>();
for (Contributor.Child<H, D> child : contributor) {
String childName = child.name();
D descriptor = getDescriptor(apiVersion, group, prefix + childName, child.contributor(), showComponents,
showDetails, null);
if (descriptor != null) {
descriptors.put(childName, descriptor);
}
}
if (contributions.isEmpty()) {
if (descriptors.isEmpty()) {
return null;
}
return aggregateContributions(apiVersion, contributions, group.getStatusAggregator(), showComponents,
groupNames);
return aggregateDescriptors(apiVersion, descriptors, group.getStatusAggregator(), showComponents, groupNames);
}
private T getLoggedHealth(C contributor, String name, boolean showDetails) {
private D getDescriptorAndLogIfSlow(Contributor<H, D> contributor, String name, boolean showDetails) {
Instant start = Instant.now();
try {
return getHealth(contributor, showDetails);
return contributor.getDescriptor(showDetails);
}
finally {
if (logger.isWarnEnabled() && this.slowIndicatorLoggingThreshold != null) {
if (logger.isWarnEnabled() && this.slowContributorLoggingThreshold != null) {
Duration duration = Duration.between(start, Instant.now());
if (duration.compareTo(this.slowIndicatorLoggingThreshold) > 0) {
String contributorClassName = contributor.getClass().getName();
Object contributorIdentifier = (!StringUtils.hasLength(name)) ? contributorClassName
: contributorClassName + " (" + name + ")";
logger.warn(LogMessage.format("Health contributor %s took %s to respond", contributorIdentifier,
DurationStyle.SIMPLE.print(duration)));
if (duration.compareTo(this.slowContributorLoggingThreshold) > 0) {
logger.warn(LogMessage.format("Health contributor %s took %s to respond",
contributor.getIdentifier(name), DurationStyle.SIMPLE.print(duration)));
}
}
}
}
protected abstract T getHealth(C contributor, boolean includeDetails);
protected abstract T aggregateContributions(ApiVersion apiVersion, Map<String, T> contributions,
abstract D aggregateDescriptors(ApiVersion apiVersion, Map<String, D> descriptors,
StatusAggregator statusAggregator, boolean showComponents, Set<String> groupNames);
protected final CompositeHealth getCompositeHealth(ApiVersion apiVersion, Map<String, HealthComponent> components,
StatusAggregator statusAggregator, boolean showComponents, Set<String> groupNames) {
final CompositeHealthDescriptor getCompositeDescriptor(ApiVersion apiVersion,
Map<String, HealthDescriptor> descriptors, StatusAggregator statusAggregator, boolean showComponents,
Set<String> groupNames) {
Status status = statusAggregator
.getAggregateStatus(components.values().stream().map(this::getStatus).collect(Collectors.toSet()));
Map<String, HealthComponent> instances = showComponents ? components : null;
if (groupNames != null) {
return new SystemHealth(apiVersion, status, instances, groupNames);
}
return new CompositeHealth(apiVersion, status, instances);
.getAggregateStatus(descriptors.values().stream().map(this::getStatus).collect(Collectors.toSet()));
descriptors = (!showComponents) ? null : descriptors;
return (groupNames != null) ? new SystemHealthDescriptor(apiVersion, status, descriptors, groupNames)
: new CompositeHealthDescriptor(apiVersion, status, descriptors);
}
private Status getStatus(HealthComponent component) {
private Status getStatus(HealthDescriptor component) {
return (component != null) ? component.getStatus() : Status.UNKNOWN;
}
/**
* A health result containing health and the group that created it.
* A health result containing descriptor and the group that created it.
*
* @param <T> the contributed health component
* @param descriptor the health descriptor
* @param group the group used to create the health
* @param <D> the details type
*/
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;
}
record Result<D>(D descriptor, HealthEndpointGroup group) {
}

View File

@ -29,6 +29,9 @@ import org.springframework.boot.actuate.endpoint.annotation.Selector.Match;
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
import org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension;
import org.springframework.boot.health.contributor.Health;
import org.springframework.boot.health.registry.HealthContributorRegistry;
import org.springframework.boot.health.registry.ReactiveHealthContributorRegistry;
import org.springframework.context.annotation.ImportRuntimeHints;
/**
@ -46,58 +49,53 @@ import org.springframework.context.annotation.ImportRuntimeHints;
*/
@EndpointWebExtension(endpoint = HealthEndpoint.class)
@ImportRuntimeHints(HealthEndpointWebExtensionRuntimeHints.class)
public class HealthEndpointWebExtension extends HealthEndpointSupport<HealthContributor, HealthComponent> {
private static final String[] NO_PATH = {};
public class HealthEndpointWebExtension extends HealthEndpointSupport<Health, HealthDescriptor> {
/**
* Create a new {@link HealthEndpointWebExtension} instance.
* @param registry the health contributor registry
* @param fallbackRegistry the fallback registry or {@code null}
* @param groups the health endpoint groups
* @param slowIndicatorLoggingThreshold duration after which slow health indicator
* @param slowContributorLoggingThreshold duration after which slow health indicator
* logging should occur
* @since 2.6.9
* @since 4.0.0
*/
public HealthEndpointWebExtension(HealthContributorRegistry registry, HealthEndpointGroups groups,
Duration slowIndicatorLoggingThreshold) {
super(registry, groups, slowIndicatorLoggingThreshold);
public HealthEndpointWebExtension(HealthContributorRegistry registry,
ReactiveHealthContributorRegistry fallbackRegistry, HealthEndpointGroups groups,
Duration slowContributorLoggingThreshold) {
super(Contributor.blocking(registry, fallbackRegistry), groups, slowContributorLoggingThreshold);
}
@ReadOperation
public WebEndpointResponse<HealthComponent> health(ApiVersion apiVersion, WebServerNamespace serverNamespace,
public WebEndpointResponse<HealthDescriptor> health(ApiVersion apiVersion, WebServerNamespace serverNamespace,
SecurityContext securityContext) {
return health(apiVersion, serverNamespace, securityContext, false, NO_PATH);
return health(apiVersion, serverNamespace, securityContext, false, EMPTY_PATH);
}
@ReadOperation
public WebEndpointResponse<HealthComponent> health(ApiVersion apiVersion, WebServerNamespace serverNamespace,
public WebEndpointResponse<HealthDescriptor> health(ApiVersion apiVersion, WebServerNamespace serverNamespace,
SecurityContext securityContext, @Selector(match = Match.ALL_REMAINING) String... path) {
return health(apiVersion, serverNamespace, securityContext, false, path);
}
public WebEndpointResponse<HealthComponent> health(ApiVersion apiVersion, WebServerNamespace serverNamespace,
public WebEndpointResponse<HealthDescriptor> health(ApiVersion apiVersion, WebServerNamespace serverNamespace,
SecurityContext securityContext, boolean showAll, String... path) {
HealthResult<HealthComponent> result = getHealth(apiVersion, serverNamespace, securityContext, showAll, path);
Result<HealthDescriptor> result = getResult(apiVersion, serverNamespace, securityContext, showAll, path);
if (result == null) {
return (Arrays.equals(path, NO_PATH))
? new WebEndpointResponse<>(DEFAULT_HEALTH, WebEndpointResponse.STATUS_OK)
return (Arrays.equals(path, EMPTY_PATH))
? new WebEndpointResponse<>(IndicatedHealthDescriptor.UP, WebEndpointResponse.STATUS_OK)
: new WebEndpointResponse<>(WebEndpointResponse.STATUS_NOT_FOUND);
}
HealthComponent health = result.getHealth();
HealthEndpointGroup group = result.getGroup();
int statusCode = group.getHttpCodeStatusMapper().getStatusCode(health.getStatus());
return new WebEndpointResponse<>(health, statusCode);
HealthDescriptor descriptor = result.descriptor();
HealthEndpointGroup group = result.group();
int statusCode = group.getHttpCodeStatusMapper().getStatusCode(descriptor.getStatus());
return new WebEndpointResponse<>(descriptor, statusCode);
}
@Override
protected HealthComponent getHealth(HealthContributor contributor, boolean includeDetails) {
return ((HealthIndicator) contributor).getHealth(includeDetails);
}
@Override
protected HealthComponent aggregateContributions(ApiVersion apiVersion, Map<String, HealthComponent> contributions,
protected HealthDescriptor aggregateDescriptors(ApiVersion apiVersion, Map<String, HealthDescriptor> contributions,
StatusAggregator statusAggregator, boolean showComponents, Set<String> groupNames) {
return getCompositeHealth(apiVersion, contributions, statusAggregator, showComponents, groupNames);
return getCompositeDescriptor(apiVersion, contributions, statusAggregator, showComponents, groupNames);
}
}

View File

@ -32,8 +32,8 @@ class HealthEndpointWebExtensionRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
this.bindingRegistrar.registerReflectionHints(hints.reflection(), Health.class, SystemHealth.class,
CompositeHealth.class);
this.bindingRegistrar.registerReflectionHints(hints.reflection(), IndicatedHealthDescriptor.class,
CompositeHealthDescriptor.class, SystemHealthDescriptor.class);
}
}

View File

@ -16,6 +16,8 @@
package org.springframework.boot.actuate.health;
import org.springframework.boot.health.contributor.Status;
/**
* Strategy used to map a {@link Status health status} to an HTTP status code.
*

View File

@ -0,0 +1,56 @@
/*
* Copyright 2012-present 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 com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import org.springframework.boot.health.contributor.Health;
import org.springframework.boot.health.contributor.HealthIndicator;
import org.springframework.boot.health.contributor.ReactiveHealthIndicator;
import org.springframework.boot.health.contributor.Status;
/**
* Description of health obtained from a {@link HealthIndicator} or
* {@link ReactiveHealthIndicator}.
*
* @author Phillip Webb
* @since 4.0.0
*/
public final class IndicatedHealthDescriptor extends HealthDescriptor {
static final IndicatedHealthDescriptor UP = new IndicatedHealthDescriptor(Health.up().build());
private final Health health;
IndicatedHealthDescriptor(Health health) {
this.health = health;
}
@Override
public Status getStatus() {
return this.health.getStatus();
}
@JsonInclude(Include.NON_EMPTY)
public Map<String, Object> getDetails() {
return this.health.getDetails();
}
}

View File

@ -1,62 +0,0 @@
/*
* Copyright 2012-present 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.util.Assert;
/**
* A single named health endpoint contributors (either {@link HealthContributor} or
* {@link ReactiveHealthContributor}).
*
* @param <C> the contributor type
* @author Phillip Webb
* @since 2.0.0
* @see NamedContributors
*/
public interface NamedContributor<C> {
/**
* Returns the name of the contributor.
* @return the contributor name
*/
String getName();
/**
* Returns the contributor instance.
* @return the contributor instance
*/
C getContributor();
static <C> NamedContributor<C> of(String name, C contributor) {
Assert.notNull(name, "'name' must not be null");
Assert.notNull(contributor, "'contributor' must not be null");
return new NamedContributor<>() {
@Override
public String getName() {
return name;
}
@Override
public C getContributor() {
return contributor;
}
};
}
}

View File

@ -1,48 +0,0 @@
/*
* Copyright 2012-present 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.stream.Stream;
import java.util.stream.StreamSupport;
/**
* A collection of named health endpoint contributors (either {@link HealthContributor} or
* {@link ReactiveHealthContributor}).
*
* @param <C> the contributor type
* @author Phillip Webb
* @since 2.0.0
* @see NamedContributor
*/
public interface NamedContributors<C> extends Iterable<NamedContributor<C>> {
/**
* Return the contributor with the given name.
* @param name the name of the contributor
* @return a contributor instance or {@code null}
*/
C getContributor(String name);
/**
* Return a stream of the {@link NamedContributor named contributors}.
* @return the stream of named contributors
*/
default Stream<NamedContributor<C>> stream() {
return StreamSupport.stream(spliterator(), false);
}
}

View File

@ -1,89 +0,0 @@
/*
* Copyright 2012-present 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 java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Function;
import org.springframework.util.Assert;
/**
* {@link NamedContributors} backed by a map with values adapted as necessary.
*
* @param <V> the value type
* @param <C> the contributor type
* @author Phillip Webb
* @author Guirong Hu
* @see CompositeHealthContributorMapAdapter
* @see CompositeReactiveHealthContributorMapAdapter
*/
abstract class NamedContributorsMapAdapter<V, C> implements NamedContributors<C> {
private final Map<String, C> map;
NamedContributorsMapAdapter(Map<String, V> map, Function<V, ? extends C> valueAdapter) {
Assert.notNull(map, "'map' must not be null");
Assert.notNull(valueAdapter, "'valueAdapter' must not be null");
map.keySet().forEach(this::validateMapKey);
this.map = Collections.unmodifiableMap(map.entrySet().stream().collect(LinkedHashMap::new, (result, entry) -> {
String key = entry.getKey();
C value = adaptMapValue(entry.getValue(), valueAdapter);
result.put(key, value);
}, Map::putAll));
}
private void validateMapKey(String value) {
Assert.notNull(value, "'map' must not contain null keys");
Assert.isTrue(!value.contains("/"), "'map' keys must not contain a '/'");
}
private C adaptMapValue(V value, Function<V, ? extends C> valueAdapter) {
C contributor = (value != null) ? valueAdapter.apply(value) : null;
Assert.notNull(contributor, "'map' must not contain null values");
return contributor;
}
@Override
public Iterator<NamedContributor<C>> iterator() {
Iterator<Entry<String, C>> iterator = this.map.entrySet().iterator();
return new Iterator<>() {
@Override
public boolean hasNext() {
return iterator.hasNext();
}
@Override
public NamedContributor<C> next() {
Entry<String, C> entry = iterator.next();
return NamedContributor.of(entry.getKey(), entry.getValue());
}
};
}
@Override
public C getContributor(String name) {
return this.map.get(name);
}
}

View File

@ -1,45 +0,0 @@
/*
* Copyright 2012-present 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.util.Assert;
/**
* Tagging interface for classes that contribute to {@link HealthComponent health
* components} to the results returned from the {@link HealthEndpoint}. A contributor must
* be either a {@link ReactiveHealthIndicator} or a
* {@link CompositeReactiveHealthContributor}.
*
* @author Phillip Webb
* @since 2.2.0
* @see ReactiveHealthIndicator
* @see CompositeReactiveHealthContributor
*/
public interface ReactiveHealthContributor {
static ReactiveHealthContributor adapt(HealthContributor healthContributor) {
Assert.notNull(healthContributor, "'healthContributor' must not be null");
if (healthContributor instanceof HealthIndicator healthIndicator) {
return new HealthIndicatorReactiveAdapter(healthIndicator);
}
if (healthContributor instanceof CompositeHealthContributor compositeHealthContributor) {
return new CompositeHealthContributorReactiveAdapter(compositeHealthContributor);
}
throw new IllegalStateException("Unknown HealthContributor type");
}
}

View File

@ -32,6 +32,9 @@ import org.springframework.boot.actuate.endpoint.annotation.Selector.Match;
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
import org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension;
import org.springframework.boot.health.contributor.Health;
import org.springframework.boot.health.registry.HealthContributorRegistry;
import org.springframework.boot.health.registry.ReactiveHealthContributorRegistry;
import org.springframework.context.annotation.ImportRuntimeHints;
/**
@ -46,94 +49,76 @@ import org.springframework.context.annotation.ImportRuntimeHints;
@EndpointWebExtension(endpoint = HealthEndpoint.class)
@ImportRuntimeHints(HealthEndpointWebExtensionRuntimeHints.class)
public class ReactiveHealthEndpointWebExtension
extends HealthEndpointSupport<ReactiveHealthContributor, Mono<? extends HealthComponent>> {
private static final String[] NO_PATH = {};
extends HealthEndpointSupport<Mono<? extends Health>, Mono<? extends HealthDescriptor>> {
/**
* Create a new {@link ReactiveHealthEndpointWebExtension} instance.
* @param registry the health contributor registry
* @param fallbackRegistry the fallback registry or {@code null}
* @param groups the health endpoint groups
* @param slowIndicatorLoggingThreshold duration after which slow health indicator
* @param slowContributorLoggingThreshold duration after which slow health indicator
* logging should occur
* @since 2.6.9
* @since 4.0.0
*/
public ReactiveHealthEndpointWebExtension(ReactiveHealthContributorRegistry registry, HealthEndpointGroups groups,
Duration slowIndicatorLoggingThreshold) {
super(registry, groups, slowIndicatorLoggingThreshold);
public ReactiveHealthEndpointWebExtension(ReactiveHealthContributorRegistry registry,
HealthContributorRegistry fallbackRegistry, HealthEndpointGroups groups,
Duration slowContributorLoggingThreshold) {
super(Contributor.reactive(registry, fallbackRegistry), groups, slowContributorLoggingThreshold);
}
@ReadOperation
public Mono<WebEndpointResponse<? extends HealthComponent>> health(ApiVersion apiVersion,
public Mono<WebEndpointResponse<? extends HealthDescriptor>> health(ApiVersion apiVersion,
WebServerNamespace serverNamespace, SecurityContext securityContext) {
return health(apiVersion, serverNamespace, securityContext, false, NO_PATH);
return health(apiVersion, serverNamespace, securityContext, false, EMPTY_PATH);
}
@ReadOperation
public Mono<WebEndpointResponse<? extends HealthComponent>> health(ApiVersion apiVersion,
public Mono<WebEndpointResponse<? extends HealthDescriptor>> health(ApiVersion apiVersion,
WebServerNamespace serverNamespace, SecurityContext securityContext,
@Selector(match = Match.ALL_REMAINING) String... path) {
return health(apiVersion, serverNamespace, securityContext, false, path);
}
public Mono<WebEndpointResponse<? extends HealthComponent>> health(ApiVersion apiVersion,
public Mono<WebEndpointResponse<? extends HealthDescriptor>> health(ApiVersion apiVersion,
WebServerNamespace serverNamespace, SecurityContext securityContext, boolean showAll, String... path) {
HealthResult<Mono<? extends HealthComponent>> result = getHealth(apiVersion, serverNamespace, securityContext,
Result<Mono<? extends HealthDescriptor>> result = getResult(apiVersion, serverNamespace, securityContext,
showAll, path);
if (result == null) {
return (Arrays.equals(path, NO_PATH))
? Mono.just(new WebEndpointResponse<>(DEFAULT_HEALTH, WebEndpointResponse.STATUS_OK))
return (Arrays.equals(path, EMPTY_PATH))
? Mono.just(new WebEndpointResponse<>(IndicatedHealthDescriptor.UP, WebEndpointResponse.STATUS_OK))
: Mono.just(new WebEndpointResponse<>(WebEndpointResponse.STATUS_NOT_FOUND));
}
HealthEndpointGroup group = result.getGroup();
return result.getHealth().map((health) -> {
HealthEndpointGroup group = result.group();
return result.descriptor().map((health) -> {
int statusCode = group.getHttpCodeStatusMapper().getStatusCode(health.getStatus());
return new WebEndpointResponse<>(health, statusCode);
});
}
@Override
protected Mono<? extends HealthComponent> getHealth(ReactiveHealthContributor contributor, boolean includeDetails) {
return ((ReactiveHealthIndicator) contributor).getHealth(includeDetails);
}
@Override
protected Mono<? extends HealthComponent> aggregateContributions(ApiVersion apiVersion,
Map<String, Mono<? extends HealthComponent>> contributions, StatusAggregator statusAggregator,
protected Mono<? extends HealthDescriptor> aggregateDescriptors(ApiVersion apiVersion,
Map<String, Mono<? extends HealthDescriptor>> contributions, StatusAggregator statusAggregator,
boolean showComponents, Set<String> groupNames) {
return Flux.fromIterable(contributions.entrySet())
.flatMap(NamedHealthComponent::create)
.collectMap(NamedHealthComponent::getName, NamedHealthComponent::getHealth)
.map((components) -> this.getCompositeHealth(apiVersion, components, statusAggregator, showComponents,
.flatMap(NamedHealthDescriptor::create)
.collectMap(NamedHealthDescriptor::name, NamedHealthDescriptor::descriptor)
.map((components) -> this.getCompositeDescriptor(apiVersion, components, statusAggregator, showComponents,
groupNames));
}
/**
* A named {@link HealthComponent}.
* A named {@link HealthDescriptor}.
*/
private static final class NamedHealthComponent {
private record NamedHealthDescriptor(String name, HealthDescriptor descriptor) {
private final String name;
private final HealthComponent health;
private NamedHealthComponent(Object... pair) {
this.name = (String) pair[0];
this.health = (HealthComponent) pair[1];
}
String getName() {
return this.name;
}
HealthComponent getHealth() {
return this.health;
}
static Mono<NamedHealthComponent> create(Map.Entry<String, Mono<? extends HealthComponent>> entry) {
static Mono<NamedHealthDescriptor> create(Map.Entry<String, Mono<? extends HealthDescriptor>> entry) {
Mono<String> name = Mono.just(entry.getKey());
Mono<? extends HealthComponent> health = entry.getValue();
return Mono.zip(NamedHealthComponent::new, name, health);
Mono<? extends HealthDescriptor> health = entry.getValue();
return Mono.zip(NamedHealthDescriptor::ofPair, name, health);
}
private static NamedHealthDescriptor ofPair(Object... pair) {
return new NamedHealthDescriptor((String) pair[0], (HealthDescriptor) pair[1]);
}
}

View File

@ -22,6 +22,7 @@ import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
import org.springframework.boot.health.contributor.Status;
import org.springframework.util.CollectionUtils;
/**

View File

@ -24,6 +24,7 @@ import java.util.List;
import java.util.Set;
import java.util.stream.Stream;
import org.springframework.boot.health.contributor.Status;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

View File

@ -20,6 +20,9 @@ import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;
import org.springframework.boot.health.contributor.Health;
import org.springframework.boot.health.contributor.Status;
/**
* Strategy used to aggregate {@link Status} instances.
* <p>

View File

@ -18,27 +18,27 @@ 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;
import org.springframework.boot.actuate.endpoint.ApiVersion;
import org.springframework.boot.health.contributor.Status;
/**
* A {@link HealthComponent} that represents the overall system health and the available
* groups.
* Description of overall system health.
*
* @author Phillip Webb
* @since 2.2.0
* @since 4.0.0
*/
public final class SystemHealth extends CompositeHealth {
public final class SystemHealthDescriptor extends CompositeHealthDescriptor {
private final Set<String> groups;
SystemHealth(ApiVersion apiVersion, Status status, Map<String, HealthComponent> instances, Set<String> groups) {
super(apiVersion, status, instances);
this.groups = (groups != null) ? new TreeSet<>(groups) : null;
SystemHealthDescriptor(ApiVersion apiVersion, Status status, Map<String, HealthDescriptor> components,
Set<String> groups) {
super(apiVersion, status, components);
this.groups = groups;
}
@JsonInclude(Include.NON_EMPTY)

View File

@ -22,10 +22,10 @@ import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
import org.springframework.boot.actuate.health.Health.Builder;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.Status;
import org.springframework.boot.health.contributor.AbstractHealthIndicator;
import org.springframework.boot.health.contributor.Health;
import org.springframework.boot.health.contributor.HealthIndicator;
import org.springframework.boot.health.contributor.Status;
import org.springframework.boot.info.SslInfo;
import org.springframework.boot.info.SslInfo.BundleInfo;
import org.springframework.boot.info.SslInfo.CertificateChainInfo;
@ -54,7 +54,7 @@ public class SslHealthIndicator extends AbstractHealthIndicator {
}
@Override
protected void doHealthCheck(Builder builder) throws Exception {
protected void doHealthCheck(Health.Builder builder) throws Exception {
List<CertificateChainInfo> validCertificateChains = new ArrayList<>();
List<CertificateChainInfo> invalidCertificateChains = new ArrayList<>();
List<CertificateChainInfo> expiringCerificateChains = new ArrayList<>();

View File

@ -21,10 +21,10 @@ import java.io.File;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.Status;
import org.springframework.boot.health.contributor.AbstractHealthIndicator;
import org.springframework.boot.health.contributor.Health;
import org.springframework.boot.health.contributor.HealthIndicator;
import org.springframework.boot.health.contributor.Status;
import org.springframework.core.log.LogMessage;
import org.springframework.util.unit.DataSize;

View File

@ -21,10 +21,10 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.boot.actuate.health.Status;
import org.springframework.boot.availability.ApplicationAvailability;
import org.springframework.boot.availability.AvailabilityState;
import org.springframework.boot.availability.LivenessState;
import org.springframework.boot.health.contributor.Status;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
@ -82,7 +82,7 @@ class AvailabilityStateHealthIndicatorTests {
statusMappings.add(LivenessState.BROKEN, Status.DOWN);
});
given(this.applicationAvailability.getState(LivenessState.class)).willReturn(LivenessState.BROKEN);
assertThat(indicator.getHealth(false).getStatus()).isEqualTo(Status.DOWN);
assertThat(indicator.health(false).getStatus()).isEqualTo(Status.DOWN);
}
@Test
@ -93,7 +93,7 @@ class AvailabilityStateHealthIndicatorTests {
statusMappings.addDefaultStatus(Status.UNKNOWN);
});
given(this.applicationAvailability.getState(LivenessState.class)).willReturn(LivenessState.BROKEN);
assertThat(indicator.getHealth(false).getStatus()).isEqualTo(Status.UNKNOWN);
assertThat(indicator.health(false).getStatus()).isEqualTo(Status.UNKNOWN);
}
@Test
@ -104,7 +104,7 @@ class AvailabilityStateHealthIndicatorTests {
statusMappings.addDefaultStatus(Status.DOWN);
});
given(this.applicationAvailability.getState(TestAvailabilityState.class)).willReturn(TestAvailabilityState.TWO);
assertThat(indicator.getHealth(false).getStatus()).isEqualTo(Status.DOWN);
assertThat(indicator.health(false).getStatus()).isEqualTo(Status.DOWN);
}
static class TestAvailabilityState implements AvailabilityState {

View File

@ -19,9 +19,9 @@ package org.springframework.boot.actuate.availability;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.health.Status;
import org.springframework.boot.availability.ApplicationAvailability;
import org.springframework.boot.availability.LivenessState;
import org.springframework.boot.health.contributor.Status;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;

View File

@ -19,9 +19,9 @@ package org.springframework.boot.actuate.availability;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.health.Status;
import org.springframework.boot.availability.ApplicationAvailability;
import org.springframework.boot.availability.ReadinessState;
import org.springframework.boot.health.contributor.Status;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;

View File

@ -0,0 +1,95 @@
/*
* Copyright 2012-present 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 java.util.LinkedHashMap;
import java.util.Map;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper;
import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.endpoint.ApiVersion;
import org.springframework.boot.health.contributor.Health;
import org.springframework.boot.health.contributor.Status;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link CompositeHealthDescriptor}.
*
* @author Phillip Webb
*/
class CompositeHealthDescriptorTests {
@Test
void createWhenApiVersionIsNullThrowsException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new CompositeHealthDescriptor(null, Status.UP, Collections.emptyMap()))
.withMessage("'apiVersion' must not be null");
}
@Test
void createWhenStatusIsNullThrowsException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new CompositeHealthDescriptor(ApiVersion.V3, null, Collections.emptyMap()))
.withMessage("'status' must not be null");
}
@Test
void getStatusReturnsStatus() {
CompositeHealthDescriptor descriptor = new CompositeHealthDescriptor(ApiVersion.V3, Status.UP,
Collections.emptyMap());
assertThat(descriptor.getStatus()).isEqualTo(Status.UP);
}
@Test
void getComponentReturnsComponents() {
Map<String, HealthDescriptor> components = new LinkedHashMap<>();
components.put("a", new IndicatedHealthDescriptor(Health.up().build()));
CompositeHealthDescriptor descriptor = new CompositeHealthDescriptor(ApiVersion.V3, Status.UP, components);
assertThat(descriptor.getComponents()).isEqualTo(components);
}
@Test
void serializeV3WithJacksonReturnsValidJson() throws Exception {
Map<String, HealthDescriptor> components = new LinkedHashMap<>();
components.put("db1", new IndicatedHealthDescriptor(Health.up().build()));
components.put("db2", new IndicatedHealthDescriptor(Health.down().withDetail("a", "b").build()));
CompositeHealthDescriptor descriptor = new CompositeHealthDescriptor(ApiVersion.V3, Status.UP, components);
ObjectMapper mapper = JsonMapper.builder().enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY).build();
String json = mapper.writeValueAsString(descriptor);
assertThat(json).isEqualTo("""
{"components":{"db1":{"status":"UP"},"db2":{"details":{"a":"b"},"status":"DOWN"}},"status":"UP"}""");
}
@Test
void serializeV2WithJacksonReturnsValidJson() throws Exception {
Map<String, HealthDescriptor> components = new LinkedHashMap<>();
components.put("db1", new IndicatedHealthDescriptor(Health.up().build()));
components.put("db2", new IndicatedHealthDescriptor(Health.down().withDetail("a", "b").build()));
CompositeHealthDescriptor descriptor = new CompositeHealthDescriptor(ApiVersion.V2, Status.UP, components);
ObjectMapper mapper = JsonMapper.builder().enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY).build();
String json = mapper.writeValueAsString(descriptor);
assertThat(json).isEqualTo("""
{"details":{"db1":{"status":"UP"},"db2":{"details":{"a":"b"},"status":"DOWN"}},"status":"UP"}""");
}
}

View File

@ -1,97 +0,0 @@
/*
* Copyright 2012-present 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 java.util.LinkedHashMap;
import java.util.Map;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper;
import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.endpoint.ApiVersion;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Test for {@link CompositeHealth}.
*
* @author Phillip Webb
*/
class CompositeHealthTests {
@Test
void createWhenStatusIsNullThrowsException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new CompositeHealth(ApiVersion.V3, null, Collections.emptyMap()))
.withMessage("'status' must not be null");
}
@Test
void getStatusReturnsStatus() {
CompositeHealth health = new CompositeHealth(ApiVersion.V3, Status.UP, Collections.emptyMap());
assertThat(health.getStatus()).isEqualTo(Status.UP);
}
@Test
void getComponentReturnsComponents() {
Map<String, HealthComponent> components = new LinkedHashMap<>();
components.put("a", Health.up().build());
CompositeHealth health = new CompositeHealth(ApiVersion.V3, Status.UP, components);
assertThat(health.getComponents()).isEqualTo(components);
}
@Test
void serializeV3WithJacksonReturnsValidJson() throws Exception {
Map<String, HealthComponent> components = new LinkedHashMap<>();
components.put("db1", Health.up().build());
components.put("db2", Health.down().withDetail("a", "b").build());
CompositeHealth health = new CompositeHealth(ApiVersion.V3, Status.UP, components);
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(health);
assertThat(json).isEqualTo("{\"status\":\"UP\",\"components\":{\"db1\":{\"status\":\"UP\"},"
+ "\"db2\":{\"status\":\"DOWN\",\"details\":{\"a\":\"b\"}}}}");
}
@Test
void serializeV2WithJacksonReturnsValidJson() throws Exception {
Map<String, HealthComponent> components = new LinkedHashMap<>();
components.put("db1", Health.up().build());
components.put("db2", Health.down().withDetail("a", "b").build());
CompositeHealth health = new CompositeHealth(ApiVersion.V2, Status.UP, components);
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(health);
assertThat(json).isEqualTo("{\"status\":\"UP\",\"details\":{\"db1\":{\"status\":\"UP\"},"
+ "\"db2\":{\"status\":\"DOWN\",\"details\":{\"a\":\"b\"}}}}");
}
@Test // gh-26797
void serializeV2WithJacksonAndDisabledCanOverrideAccessModifiersReturnsValidJson() throws Exception {
Map<String, HealthComponent> components = new LinkedHashMap<>();
components.put("db1", Health.up().build());
components.put("db2", Health.down().withDetail("a", "b").build());
CompositeHealth health = new CompositeHealth(ApiVersion.V2, Status.UP, components);
JsonMapper mapper = JsonMapper.builder().disable(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS).build();
String json = mapper.writeValueAsString(health);
assertThat(json).isEqualTo("{\"status\":\"UP\",\"details\":{\"db1\":{\"status\":\"UP\"},"
+ "\"db2\":{\"status\":\"DOWN\",\"details\":{\"a\":\"b\"}}}}");
}
}

View File

@ -1,165 +0,0 @@
/*
* Copyright 2012-present 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 java.util.Iterator;
import org.junit.jupiter.api.BeforeEach;
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.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link DefaultContributorRegistry}.
*
* @author Phillip Webb
* @author Vedran Pavic
* @author Stephane Nicoll
*/
abstract class DefaultContributorRegistryTests {
private final HealthIndicator one = mock(HealthIndicator.class);
private final HealthIndicator two = mock(HealthIndicator.class);
private ContributorRegistry<HealthIndicator> registry;
@BeforeEach
void setUp() {
given(this.one.health()).willReturn(new Health.Builder().unknown().withDetail("1", "1").build());
given(this.two.health()).willReturn(new Health.Builder().unknown().withDetail("2", "2").build());
this.registry = new DefaultContributorRegistry<>();
}
@Test
void createWhenContributorsIsNullThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() -> new DefaultContributorRegistry<>(null))
.withMessage("Contributors must not be null");
}
@Test
void createWhenNameFactoryIsNullThrowsException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new DefaultContributorRegistry<>(Collections.emptyMap(), null))
.withMessage("NameFactory must not be null");
}
@Test
void createUsesHealthIndicatorNameFactoryByDefault() {
this.registry = new DefaultContributorRegistry<>(Collections.singletonMap("oneHealthIndicator", this.one));
assertThat(this.registry.getContributor("oneHealthIndicator")).isNull();
assertThat(this.registry.getContributor("one")).isNotNull();
}
@Test
void createWithCustomNameFactoryAppliesFunctionToName() {
this.registry = new DefaultContributorRegistry<>(Collections.singletonMap("one", this.one), this::reverse);
assertThat(this.registry.getContributor("one")).isNull();
assertThat(this.registry.getContributor("eno")).isNotNull();
}
@Test
void registerContributorWhenNameIsNullThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() -> this.registry.registerContributor(null, this.one))
.withMessage("Name must not be null");
}
@Test
void registerContributorWhenContributorIsNullThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() -> this.registry.registerContributor("one", null))
.withMessage("Contributor must not be null");
}
@Test
void registerContributorRegistersContributors() {
this.registry.registerContributor("one", this.one);
this.registry.registerContributor("two", this.two);
assertThat(this.registry).hasSize(2);
assertThat(this.registry.getContributor("one")).isSameAs(this.one);
assertThat(this.registry.getContributor("two")).isSameAs(this.two);
}
@Test
void registerContributorWhenNameAlreadyUsedThrowsException() {
this.registry.registerContributor("one", this.one);
assertThatIllegalStateException().isThrownBy(() -> this.registry.registerContributor("one", this.two))
.withMessageContaining("A contributor named \"one\" has already been registered");
}
@Test
void registerContributorUsesNameFactory() {
this.registry.registerContributor("oneHealthIndicator", this.one);
assertThat(this.registry.getContributor("oneHealthIndicator")).isNull();
assertThat(this.registry.getContributor("one")).isNotNull();
}
@Test
void unregisterContributorUnregistersContributor() {
this.registry.registerContributor("one", this.one);
this.registry.registerContributor("two", this.two);
assertThat(this.registry).hasSize(2);
HealthIndicator two = this.registry.unregisterContributor("two");
assertThat(two).isSameAs(this.two);
assertThat(this.registry).hasSize(1);
}
@Test
void unregisterContributorWhenUnknownReturnsNull() {
this.registry.registerContributor("one", this.one);
assertThat(this.registry).hasSize(1);
HealthIndicator two = this.registry.unregisterContributor("two");
assertThat(two).isNull();
assertThat(this.registry).hasSize(1);
}
@Test
void unregisterContributorUsesNameFactory() {
this.registry.registerContributor("oneHealthIndicator", this.one);
assertThat(this.registry.getContributor("oneHealthIndicator")).isNull();
assertThat(this.registry.getContributor("one")).isNotNull();
}
@Test
void getContributorReturnsContributor() {
this.registry.registerContributor("one", this.one);
assertThat(this.registry.getContributor("one")).isEqualTo(this.one);
}
@Test
void iteratorIteratesContributors() {
this.registry.registerContributor("one", this.one);
this.registry.registerContributor("two", this.two);
Iterator<NamedContributor<HealthIndicator>> iterator = this.registry.iterator();
NamedContributor<HealthIndicator> first = iterator.next();
NamedContributor<HealthIndicator> second = iterator.next();
assertThat(iterator.hasNext()).isFalse();
assertThat(first.getName()).isEqualTo("one");
assertThat(first.getContributor()).isEqualTo(this.one);
assertThat(second.getName()).isEqualTo("two");
assertThat(second.getContributor()).isEqualTo(this.two);
}
private String reverse(String name) {
return new StringBuilder(name).reverse().toString();
}
}

View File

@ -1,55 +0,0 @@
/*
* Copyright 2012-present 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.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link HealthContributorNameFactory}.
*
* @author Phillip Webb
*/
class HealthContributorNameFactoryTests {
@Test
void applyWhenNameDoesNotEndWithSuffixReturnsName() {
assertThat(HealthContributorNameFactory.INSTANCE.apply("test")).isEqualTo("test");
}
@Test
void applyWhenNameEndsWithSuffixReturnsNewName() {
assertThat(HealthContributorNameFactory.INSTANCE.apply("testHealthIndicator")).isEqualTo("test");
assertThat(HealthContributorNameFactory.INSTANCE.apply("testHealthContributor")).isEqualTo("test");
}
@Test
void applyWhenNameEndsWithSuffixInDifferentReturnsNewName() {
assertThat(HealthContributorNameFactory.INSTANCE.apply("testHEALTHindicator")).isEqualTo("test");
assertThat(HealthContributorNameFactory.INSTANCE.apply("testHEALTHcontributor")).isEqualTo("test");
}
@Test
void applyWhenNameContainsSuffixReturnsName() {
assertThat(HealthContributorNameFactory.INSTANCE.apply("testHealthIndicatorTest"))
.isEqualTo("testHealthIndicatorTest");
assertThat(HealthContributorNameFactory.INSTANCE.apply("testHealthContributorTest"))
.isEqualTo("testHealthContributorTest");
}
}

View File

@ -20,6 +20,8 @@ import java.time.Duration;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Predicate;
import org.junit.jupiter.api.Test;
@ -27,24 +29,24 @@ import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.endpoint.ApiVersion;
import org.springframework.boot.actuate.endpoint.SecurityContext;
import org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
import org.springframework.boot.actuate.health.HealthEndpointSupport.HealthResult;
import org.springframework.boot.actuate.health.HealthEndpointSupport.Result;
import org.springframework.boot.health.contributor.Health;
import org.springframework.boot.health.contributor.Status;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Base class for {@link HealthEndpointSupport} tests.
*
* @param <S> the support type
* @param <E> the endpoint type;
* @param <H> the health type
* @param <D> the descriptor type
* @param <R> the registry type
* @param <C> the contributor type
* @param <T> the contributed health component type
* @author Phillip Webb
* @author Madhura Bhave
*/
abstract class HealthEndpointSupportTests<S extends HealthEndpointSupport<C, T>, R extends ContributorRegistry<C>, C, T> {
final R registry;
abstract class HealthEndpointSupportTests<E extends HealthEndpointSupport<H, D>, H, D, R, C> {
final Health up = Health.up().withDetail("spring", "boot").build();
@ -54,217 +56,227 @@ abstract class HealthEndpointSupportTests<S extends HealthEndpointSupport<C, T>,
final TestHealthEndpointGroup allTheAs = new TestHealthEndpointGroup((name) -> name.startsWith("a"));
final HealthEndpointGroups groups = HealthEndpointGroups.of(this.primaryGroup,
Collections.singletonMap("alltheas", this.allTheAs));
final HealthEndpointGroups groups = HealthEndpointGroups.of(this.primaryGroup, Map.of("alltheas", this.allTheAs));
HealthEndpointSupportTests() {
this.registry = createRegistry();
@Test
void getResultWhenPathIsEmptyUsesPrimaryGroup() {
R registry = createRegistry("test", createContributor(this.up));
E support = create(registry, this.groups);
Result<D> result = support.getResult(ApiVersion.V3, null, SecurityContext.NONE, false);
assertThat(result.group()).isEqualTo(this.primaryGroup);
SystemHealthDescriptor descriptor = (SystemHealthDescriptor) getDescriptor(result);
assertThat(descriptor.getStatus()).isEqualTo(Status.UP);
assertThat(descriptor.getComponents()).containsKey("test");
assertThat(descriptor.getDetails()).isNull();
}
@Test
void createWhenRegistryIsNullThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() -> create(null, this.groups))
.withMessage("'registry' must not be null");
void getResultWhenPathIsNotGroupReturnsResultFromPrimaryGroup() {
R registry = createRegistry("test", createContributor(this.up));
E endpoint = create(registry, this.groups);
Result<D> result = endpoint.getResult(ApiVersion.V3, null, SecurityContext.NONE, false, "test");
assertThat(result.group()).isEqualTo(this.primaryGroup);
IndicatedHealthDescriptor descriptor = (IndicatedHealthDescriptor) getDescriptor(result);
assertThat(descriptor.getStatus()).isEqualTo(Status.UP);
assertThat(descriptor.getDetails()).containsEntry("spring", "boot");
}
@Test
void createWhenGroupsIsNullThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() -> create(this.registry, null))
.withMessage("'groups' must not be null");
void getResultWhenPathIsGroupReturnsResultFromGroup() {
R registry = createRegistry("atest", createContributor(this.up));
E endpoint = create(registry, this.groups);
Result<D> result = endpoint.getResult(ApiVersion.V3, null, SecurityContext.NONE, false, "alltheas", "atest");
assertThat(result.group()).isEqualTo(this.allTheAs);
IndicatedHealthDescriptor descriptor = (IndicatedHealthDescriptor) getDescriptor(result);
assertThat(descriptor.getStatus()).isEqualTo(Status.UP);
assertThat(descriptor.getDetails()).containsEntry("spring", "boot");
}
@Test
void getHealthWhenPathIsEmptyUsesPrimaryGroup() {
this.registry.registerContributor("test", createContributor(this.up));
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, null, SecurityContext.NONE,
false);
assertThat(result.getGroup()).isEqualTo(this.primaryGroup);
assertThat(getHealth(result)).isNotSameAs(this.up);
assertThat(getHealth(result).getStatus()).isEqualTo(Status.UP);
}
@Test
void getHealthWhenPathIsNotGroupReturnsResultFromPrimaryGroup() {
this.registry.registerContributor("test", createContributor(this.up));
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, null, SecurityContext.NONE,
false, "test");
assertThat(result.getGroup()).isEqualTo(this.primaryGroup);
assertThat(getHealth(result)).isEqualTo(this.up);
}
@Test
void getHealthWhenPathIsGroupReturnsResultFromGroup() {
this.registry.registerContributor("atest", createContributor(this.up));
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, null, SecurityContext.NONE,
false, "alltheas", "atest");
assertThat(result.getGroup()).isEqualTo(this.allTheAs);
assertThat(getHealth(result)).isEqualTo(this.up);
}
@Test
void getHealthWhenAlwaysShowIsFalseAndGroupIsTrueShowsComponents() {
void getResultWhenAlwaysShowIsFalseAndGroupIsTrueShowsComponents() {
C contributor = createContributor(this.up);
C compositeContributor = createCompositeContributor(Collections.singletonMap("spring", contributor));
this.registry.registerContributor("test", compositeContributor);
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, null, SecurityContext.NONE,
false, "test");
CompositeHealth health = (CompositeHealth) getHealth(result);
assertThat(health.getComponents()).containsKey("spring");
C compositeContributor = createCompositeContributor(Map.of("spring", contributor));
R registry = createRegistry("test", compositeContributor);
E endpoint = create(registry, this.groups);
Result<D> result = endpoint.getResult(ApiVersion.V3, null, SecurityContext.NONE, false, "test");
CompositeHealthDescriptor descriptor = (CompositeHealthDescriptor) getDescriptor(result);
assertThat(descriptor.getComponents()).containsKey("spring");
}
@Test
void getHealthWhenAlwaysShowIsFalseAndGroupIsFalseCannotAccessComponent() {
void getResultWhenAlwaysShowIsFalseAndGroupIsFalseCannotAccessComponent() {
this.primaryGroup.setShowComponents(false);
C contributor = createContributor(this.up);
C compositeContributor = createCompositeContributor(Collections.singletonMap("spring", contributor));
this.registry.registerContributor("test", compositeContributor);
HealthEndpointSupport<C, T> endpoint = create(this.registry, this.groups);
HealthResult<T> rootResult = endpoint.getHealth(ApiVersion.V3, null, SecurityContext.NONE, false);
assertThat(((CompositeHealth) getHealth(rootResult)).getComponents()).isNullOrEmpty();
HealthResult<T> componentResult = endpoint.getHealth(ApiVersion.V3, null, SecurityContext.NONE, false, "test");
C compositeContributor = createCompositeContributor(Map.of("spring", contributor));
R registry = createRegistry("test", compositeContributor);
E endpoint = create(registry, this.groups);
Result<D> rootResult = endpoint.getResult(ApiVersion.V3, null, SecurityContext.NONE, false);
CompositeHealthDescriptor rootDescriptor = (CompositeHealthDescriptor) getDescriptor(rootResult);
assertThat(rootDescriptor.getComponents()).isNullOrEmpty();
Result<D> componentResult = endpoint.getResult(ApiVersion.V3, null, SecurityContext.NONE, false, "test");
assertThat(componentResult).isNull();
}
@Test
void getHealthWhenAlwaysShowIsTrueShowsComponents() {
void getResultWhenAlwaysShowIsTrueShowsComponents() {
this.primaryGroup.setShowComponents(true);
C contributor = createContributor(this.up);
C compositeContributor = createCompositeContributor(Collections.singletonMap("spring", contributor));
this.registry.registerContributor("test", compositeContributor);
HealthEndpointSupport<C, T> endpoint = create(this.registry, this.groups);
HealthResult<T> rootResult = endpoint.getHealth(ApiVersion.V3, null, SecurityContext.NONE, false);
assertThat(((CompositeHealth) getHealth(rootResult)).getComponents()).containsKey("test");
HealthResult<T> componentResult = endpoint.getHealth(ApiVersion.V3, null, SecurityContext.NONE, false, "test");
assertThat(((CompositeHealth) getHealth(componentResult)).getComponents()).containsKey("spring");
C compositeContributor = createCompositeContributor(Map.of("spring", contributor));
R registry = createRegistry("test", compositeContributor);
E endpoint = create(registry, this.groups);
Result<D> rootResult = endpoint.getResult(ApiVersion.V3, null, SecurityContext.NONE, false);
CompositeHealthDescriptor rootDescriptor = (CompositeHealthDescriptor) getDescriptor(rootResult);
assertThat(rootDescriptor.getComponents()).containsKey("test");
Result<D> componentResult = endpoint.getResult(ApiVersion.V3, null, SecurityContext.NONE, false, "test");
CompositeHealthDescriptor componentDescriptor = (CompositeHealthDescriptor) getDescriptor(componentResult);
assertThat(componentDescriptor.getComponents()).containsKey("spring");
}
@Test
void getHealthWhenAlwaysShowIsFalseAndGroupIsTrueShowsDetails() {
this.registry.registerContributor("test", createContributor(this.up));
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, null, SecurityContext.NONE,
false, "test");
assertThat(((Health) getHealth(result)).getDetails()).containsEntry("spring", "boot");
void getResultWhenAlwaysShowIsFalseAndGroupIsTrueShowsDetails() {
R registry = createRegistry("test", createContributor(this.up));
E endpoint = create(registry, this.groups);
Result<D> result = endpoint.getResult(ApiVersion.V3, null, SecurityContext.NONE, false, "test");
IndicatedHealthDescriptor descriptor = (IndicatedHealthDescriptor) getDescriptor(result);
assertThat(descriptor.getDetails()).containsEntry("spring", "boot");
}
@Test
void getHealthWhenAlwaysShowIsFalseAndGroupIsFalseShowsNoDetails() {
void getResultWhenAlwaysShowIsFalseAndGroupIsFalseShowsNoDetails() {
this.primaryGroup.setShowDetails(false);
this.registry.registerContributor("test", createContributor(this.up));
HealthEndpointSupport<C, T> endpoint = create(this.registry, this.groups);
HealthResult<T> rootResult = endpoint.getHealth(ApiVersion.V3, null, SecurityContext.NONE, false);
HealthResult<T> componentResult = endpoint.getHealth(ApiVersion.V3, null, SecurityContext.NONE, false, "test");
assertThat(((CompositeHealth) getHealth(rootResult)).getStatus()).isEqualTo(Status.UP);
R registry = createRegistry("test", createContributor(this.up));
E endpoint = create(registry, this.groups);
Result<D> rootResult = endpoint.getResult(ApiVersion.V3, null, SecurityContext.NONE, false);
Result<D> componentResult = endpoint.getResult(ApiVersion.V3, null, SecurityContext.NONE, false, "test");
assertThat(getDescriptor(rootResult).getStatus()).isEqualTo(Status.UP);
assertThat(componentResult).isNull();
}
@Test
void getHealthWhenAlwaysShowIsTrueShowsDetails() {
void getResultWhenAlwaysShowIsTrueShowsDetails() {
this.primaryGroup.setShowDetails(false);
this.registry.registerContributor("test", createContributor(this.up));
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, null, SecurityContext.NONE,
true, "test");
assertThat(((Health) getHealth(result)).getDetails()).containsEntry("spring", "boot");
R registry = createRegistry("test", createContributor(this.up));
E endpoint = create(registry, this.groups);
Result<D> result = endpoint.getResult(ApiVersion.V3, null, SecurityContext.NONE, true, "test");
IndicatedHealthDescriptor descriptor = (IndicatedHealthDescriptor) getDescriptor(result);
assertThat(descriptor.getDetails()).containsEntry("spring", "boot");
}
@Test
void getHealthWhenCompositeReturnsAggregateResult() {
void getResultWhenCompositeReturnsAggregateResult() {
Map<String, C> contributors = new LinkedHashMap<>();
contributors.put("a", createContributor(this.up));
contributors.put("b", createContributor(this.down));
this.registry.registerContributor("test", createCompositeContributor(contributors));
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, null, SecurityContext.NONE,
false);
CompositeHealth root = (CompositeHealth) getHealth(result);
CompositeHealth component = (CompositeHealth) root.getComponents().get("test");
R registry = createRegistry("test", createCompositeContributor(contributors));
E endpoint = create(registry, this.groups);
Result<D> result = endpoint.getResult(ApiVersion.V3, null, SecurityContext.NONE, false);
CompositeHealthDescriptor root = (CompositeHealthDescriptor) getDescriptor(result);
CompositeHealthDescriptor component = (CompositeHealthDescriptor) root.getComponents().get("test");
assertThat(root.getStatus()).isEqualTo(Status.DOWN);
assertThat(component.getStatus()).isEqualTo(Status.DOWN);
assertThat(component.getComponents()).containsOnlyKeys("a", "b");
}
@Test
void getHealthWhenPathDoesNotExistReturnsNull() {
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, null, SecurityContext.NONE,
false, "missing");
void getResultWhenPathDoesNotExistReturnsNull() {
R registry = createRegistry("test", createCompositeContributor(Collections.emptyMap()));
Result<D> result = create(registry, this.groups).getResult(ApiVersion.V3, null, SecurityContext.NONE, false,
"missing");
assertThat(result).isNull();
}
@Test
void getHealthWhenPathIsEmptyIncludesGroups() {
this.registry.registerContributor("test", createContributor(this.up));
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, null, SecurityContext.NONE,
false);
assertThat(((SystemHealth) getHealth(result)).getGroups()).containsOnly("alltheas");
void getResultWhenPathIsEmptyIncludesGroups() {
R registry = createRegistry("test", createContributor(this.up));
E endpoint = create(registry, this.groups);
Result<D> result = endpoint.getResult(ApiVersion.V3, null, SecurityContext.NONE, false);
SystemHealthDescriptor descriptor = (SystemHealthDescriptor) getDescriptor(result);
assertThat(descriptor.getGroups()).containsOnly("alltheas");
}
@Test
void getHealthWhenPathIsGroupDoesNotIncludesGroups() {
this.registry.registerContributor("atest", createContributor(this.up));
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, null, SecurityContext.NONE,
false, "alltheas");
assertThat(getHealth(result)).isNotInstanceOf(SystemHealth.class);
void getResultWhenPathIsGroupDoesNotIncludesGroups() {
R registry = createRegistry("atest", createContributor(this.up));
E endpoint = create(registry, this.groups);
Result<D> result = endpoint.getResult(ApiVersion.V3, null, SecurityContext.NONE, false, "alltheas");
HealthDescriptor descriptor = getDescriptor(result);
assertThat(descriptor).isInstanceOf(CompositeHealthDescriptor.class);
assertThat(descriptor).isNotInstanceOf(SystemHealthDescriptor.class);
}
@Test
void getHealthWithEmptyCompositeReturnsNullResult() { // gh-18687
this.registry.registerContributor("test", createCompositeContributor(Collections.emptyMap()));
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, null, SecurityContext.NONE,
false);
void getResultWithEmptyCompositeReturnsNullResult() { // gh-18687
R registry = createRegistry("test", createCompositeContributor(Collections.emptyMap()));
E endpoint = create(registry, this.groups);
Result<D> result = endpoint.getResult(ApiVersion.V3, null, SecurityContext.NONE, false);
assertThat(result).isNull();
}
@Test
void getHealthWhenGroupContainsCompositeContributorReturnsHealth() {
void getResultWhenGroupContainsCompositeContributorReturnsHealth() {
C contributor = createContributor(this.up);
C compositeContributor = createCompositeContributor(Collections.singletonMap("spring", contributor));
this.registry.registerContributor("test", compositeContributor);
C compositeContributor = createCompositeContributor(Map.of("spring", contributor));
R registry = createRegistry("test", compositeContributor);
TestHealthEndpointGroup testGroup = new TestHealthEndpointGroup((name) -> name.startsWith("test"));
HealthEndpointGroups groups = HealthEndpointGroups.of(this.primaryGroup,
Collections.singletonMap("testGroup", testGroup));
HealthResult<T> result = create(this.registry, groups).getHealth(ApiVersion.V3, null, SecurityContext.NONE,
false, "testGroup");
CompositeHealth health = (CompositeHealth) getHealth(result);
assertThat(health.getComponents()).containsKey("test");
HealthEndpointGroups groups = HealthEndpointGroups.of(this.primaryGroup, Map.of("testGroup", testGroup));
E endpoint = create(registry, groups);
Result<D> result = endpoint.getResult(ApiVersion.V3, null, SecurityContext.NONE, false, "testGroup");
CompositeHealthDescriptor descriptor = (CompositeHealthDescriptor) getDescriptor(result);
assertThat(descriptor.getComponents()).containsKey("test");
}
@Test
void getHealthWhenGroupContainsComponentOfCompositeContributorReturnsHealth() {
CompositeHealth health = getCompositeHealth((name) -> name.equals("test/spring-1"));
assertThat(health.getComponents()).containsKey("test");
CompositeHealth test = (CompositeHealth) health.getComponents().get("test");
void getResultWhenGroupContainsComponentOfCompositeContributorReturnsHealth() {
CompositeHealthDescriptor descriptor = getCompositeHealthDescriptor((name) -> name.equals("test/spring-1"));
assertThat(descriptor.getComponents()).containsKey("test");
CompositeHealthDescriptor test = (CompositeHealthDescriptor) descriptor.getComponents().get("test");
assertThat(test.getComponents()).containsKey("spring-1");
assertThat(test.getComponents()).doesNotContainKey("spring-2");
assertThat(test.getComponents()).doesNotContainKey("test");
}
@Test
void getHealthWhenGroupExcludesComponentOfCompositeContributorReturnsHealth() {
CompositeHealth health = getCompositeHealth(
void getResultWhenGroupExcludesComponentOfCompositeContributorReturnsHealth() {
CompositeHealthDescriptor descriptor = getCompositeHealthDescriptor(
(name) -> name.startsWith("test/") && !name.equals("test/spring-2"));
assertThat(health.getComponents()).containsKey("test");
CompositeHealth test = (CompositeHealth) health.getComponents().get("test");
assertThat(descriptor.getComponents()).containsKey("test");
CompositeHealthDescriptor test = (CompositeHealthDescriptor) descriptor.getComponents().get("test");
assertThat(test.getComponents()).containsKey("spring-1");
assertThat(test.getComponents()).doesNotContainKey("spring-2");
}
private CompositeHealthDescriptor getCompositeHealthDescriptor(Predicate<String> memberPredicate) {
C contributor1 = createContributor(this.up);
C contributor2 = createContributor(this.down);
Map<String, C> contributors = new LinkedHashMap<>();
contributors.put("spring-1", contributor1);
contributors.put("spring-2", contributor2);
C compositeContributor = createCompositeContributor(contributors);
R registry = createRegistry("test", compositeContributor);
TestHealthEndpointGroup testGroup = new TestHealthEndpointGroup(memberPredicate);
HealthEndpointGroups groups = HealthEndpointGroups.of(this.primaryGroup, Map.of("testGroup", testGroup));
E endpoint = create(registry, groups);
Result<D> result = endpoint.getResult(ApiVersion.V3, null, SecurityContext.NONE, false, "testGroup");
return (CompositeHealthDescriptor) getDescriptor(result);
}
@Test
void getHealthForPathWhenGroupContainsComponentOfCompositeContributorReturnsHealth() {
void getResultForPathWhenGroupContainsComponentOfCompositeContributorReturnsHealth() {
Map<String, C> contributors = new LinkedHashMap<>();
contributors.put("spring-1", createNestedHealthContributor("spring-1"));
contributors.put("spring-2", createNestedHealthContributor("spring-2"));
C compositeContributor = createCompositeContributor(contributors);
this.registry.registerContributor("test", compositeContributor);
R registry = createRegistry("test", compositeContributor);
TestHealthEndpointGroup testGroup = new TestHealthEndpointGroup(
(name) -> name.startsWith("test") && !name.equals("test/spring-1/b"));
HealthEndpointGroups groups = HealthEndpointGroups.of(this.primaryGroup,
Collections.singletonMap("testGroup", testGroup));
HealthResult<T> result = create(this.registry, groups).getHealth(ApiVersion.V3, null, SecurityContext.NONE,
false, "testGroup", "test");
CompositeHealth health = (CompositeHealth) getHealth(result);
assertThat(health.getComponents()).containsKey("spring-1");
assertThat(health.getComponents()).containsKey("spring-2");
CompositeHealth spring1 = (CompositeHealth) health.getComponents().get("spring-1");
CompositeHealth spring2 = (CompositeHealth) health.getComponents().get("spring-2");
HealthEndpointGroups groups = HealthEndpointGroups.of(this.primaryGroup, Map.of("testGroup", testGroup));
E endpoint = create(registry, groups);
Result<D> result = endpoint.getResult(ApiVersion.V3, null, SecurityContext.NONE, false, "testGroup", "test");
CompositeHealthDescriptor descriptor = (CompositeHealthDescriptor) getDescriptor(result);
assertThat(descriptor.getComponents()).containsKey("spring-1");
assertThat(descriptor.getComponents()).containsKey("spring-2");
CompositeHealthDescriptor spring1 = (CompositeHealthDescriptor) descriptor.getComponents().get("spring-1");
CompositeHealthDescriptor spring2 = (CompositeHealthDescriptor) descriptor.getComponents().get("spring-2");
assertThat(spring1.getComponents()).containsKey("a");
assertThat(spring1.getComponents()).containsKey("c");
assertThat(spring1.getComponents()).doesNotContainKey("b");
@ -274,37 +286,21 @@ abstract class HealthEndpointSupportTests<S extends HealthEndpointSupport<C, T>,
}
@Test
void getHealthForComponentPathWhenNotPartOfGroup() {
void getResultForComponentPathWhenNotPartOfGroup() {
Map<String, C> contributors = new LinkedHashMap<>();
contributors.put("spring-1", createNestedHealthContributor("spring-1"));
contributors.put("spring-2", createNestedHealthContributor("spring-2"));
C compositeContributor = createCompositeContributor(contributors);
this.registry.registerContributor("test", compositeContributor);
R registry = createRegistry("test", compositeContributor);
TestHealthEndpointGroup testGroup = new TestHealthEndpointGroup(
(name) -> name.startsWith("test") && !name.equals("test/spring-1/b"));
HealthEndpointGroups groups = HealthEndpointGroups.of(this.primaryGroup,
Collections.singletonMap("testGroup", testGroup));
HealthResult<T> result = create(this.registry, groups).getHealth(ApiVersion.V3, null, SecurityContext.NONE,
false, "testGroup", "test", "spring-1", "b");
HealthEndpointGroups groups = HealthEndpointGroups.of(this.primaryGroup, Map.of("testGroup", testGroup));
E endpoint = create(registry, groups);
Result<D> result = endpoint.getResult(ApiVersion.V3, null, SecurityContext.NONE, false, "testGroup", "test",
"spring-1", "b");
assertThat(result).isNull();
}
private CompositeHealth getCompositeHealth(Predicate<String> memberPredicate) {
C contributor1 = createContributor(this.up);
C contributor2 = createContributor(this.down);
Map<String, C> contributors = new LinkedHashMap<>();
contributors.put("spring-1", contributor1);
contributors.put("spring-2", contributor2);
C compositeContributor = createCompositeContributor(contributors);
this.registry.registerContributor("test", compositeContributor);
TestHealthEndpointGroup testGroup = new TestHealthEndpointGroup(memberPredicate);
HealthEndpointGroups groups = HealthEndpointGroups.of(this.primaryGroup,
Collections.singletonMap("testGroup", testGroup));
HealthResult<T> result = create(this.registry, groups).getHealth(ApiVersion.V3, null, SecurityContext.NONE,
false, "testGroup");
return (CompositeHealth) getHealth(result);
}
private C createNestedHealthContributor(String name) {
Map<String, C> map = new LinkedHashMap<>();
map.put("a", createContributor(Health.up().withDetail("hello", name + "-a").build()));
@ -314,58 +310,62 @@ abstract class HealthEndpointSupportTests<S extends HealthEndpointSupport<C, T>,
}
@Test
void getHealthWhenGroupHasAdditionalPath() {
this.registry.registerContributor("test", createContributor(this.up));
void getResultWhenGroupHasAdditionalPath() {
R registry = createRegistry("test", createContributor(this.up));
TestHealthEndpointGroup testGroup = new TestHealthEndpointGroup((name) -> name.startsWith("test"));
testGroup.setAdditionalPath(AdditionalHealthEndpointPath.from("server:/healthz"));
HealthEndpointGroups groups = HealthEndpointGroups.of(this.primaryGroup,
Collections.singletonMap("testGroup", testGroup));
HealthResult<T> result = create(this.registry, groups).getHealth(ApiVersion.V3, WebServerNamespace.SERVER,
SecurityContext.NONE, false, "healthz");
CompositeHealth health = (CompositeHealth) getHealth(result);
assertThat(health.getComponents()).containsKey("test");
HealthEndpointGroups groups = HealthEndpointGroups.of(this.primaryGroup, Map.of("testGroup", testGroup));
E endpoint = create(registry, groups);
Result<D> result = endpoint.getResult(ApiVersion.V3, WebServerNamespace.SERVER, SecurityContext.NONE, false,
"healthz");
CompositeHealthDescriptor descriptor = (CompositeHealthDescriptor) getDescriptor(result);
assertThat(descriptor.getComponents()).containsKey("test");
}
@Test
void getHealthWhenGroupHasAdditionalPathAndShowComponentsFalse() {
this.registry.registerContributor("test", createContributor(this.up));
void getResultWhenGroupHasAdditionalPathAndShowComponentsFalse() {
R registry = createRegistry("test", createContributor(this.up));
TestHealthEndpointGroup testGroup = new TestHealthEndpointGroup((name) -> name.startsWith("test"));
testGroup.setAdditionalPath(AdditionalHealthEndpointPath.from("server:/healthz"));
testGroup.setShowComponents(false);
HealthEndpointGroups groups = HealthEndpointGroups.of(this.primaryGroup,
Collections.singletonMap("testGroup", testGroup));
HealthResult<T> result = create(this.registry, groups).getHealth(ApiVersion.V3, WebServerNamespace.SERVER,
SecurityContext.NONE, false, "healthz");
CompositeHealth health = (CompositeHealth) getHealth(result);
assertThat(health.getStatus().getCode()).isEqualTo("UP");
assertThat(health.getComponents()).isNull();
HealthEndpointGroups groups = HealthEndpointGroups.of(this.primaryGroup, Map.of("testGroup", testGroup));
E endpoint = create(registry, groups);
Result<D> result = endpoint.getResult(ApiVersion.V3, WebServerNamespace.SERVER, SecurityContext.NONE, false,
"healthz");
CompositeHealthDescriptor descriptor = (CompositeHealthDescriptor) getDescriptor(result);
assertThat(descriptor.getStatus().getCode()).isEqualTo("UP");
assertThat(descriptor.getComponents()).isNull();
}
@Test
void getComponentHealthWhenGroupHasAdditionalPathAndShowComponentsFalse() {
this.registry.registerContributor("test", createContributor(this.up));
void getResultWithPathWhenGroupHasAdditionalPathAndShowComponentsFalse() {
R registry = createRegistry("test", createContributor(this.up));
TestHealthEndpointGroup testGroup = new TestHealthEndpointGroup((name) -> name.startsWith("test"));
testGroup.setAdditionalPath(AdditionalHealthEndpointPath.from("server:/healthz"));
testGroup.setShowComponents(false);
HealthEndpointGroups groups = HealthEndpointGroups.of(this.primaryGroup,
Collections.singletonMap("testGroup", testGroup));
HealthResult<T> result = create(this.registry, groups).getHealth(ApiVersion.V3, WebServerNamespace.SERVER,
SecurityContext.NONE, false, "healthz", "test");
HealthEndpointGroups groups = HealthEndpointGroups.of(this.primaryGroup, Map.of("testGroup", testGroup));
E endpoint = create(registry, groups);
Result<D> result = endpoint.getResult(ApiVersion.V3, WebServerNamespace.SERVER, SecurityContext.NONE, false,
"healthz", "test");
assertThat(result).isNull();
}
protected final S create(R registry, HealthEndpointGroups groups) {
protected final E create(R registry, HealthEndpointGroups groups) {
return create(registry, groups, null);
}
protected abstract S create(R registry, HealthEndpointGroups groups, Duration slowIndicatorLoggingThreshold);
protected abstract E create(R registry, HealthEndpointGroups groups, Duration slowContributorLoggingThreshold);
protected abstract R createRegistry();
protected final R createRegistry(String name, C contributor) {
return createRegistry((registrations) -> registrations.accept(name, contributor));
}
protected abstract R createRegistry(Consumer<BiConsumer<String, C>> intialRegistrations);
protected abstract C createContributor(Health health);
protected abstract C createCompositeContributor(Map<String, C> contributors);
protected abstract HealthComponent getHealth(HealthResult<T> result);
protected abstract HealthDescriptor getDescriptor(Result<D> result);
}

View File

@ -19,11 +19,20 @@ package org.springframework.boot.actuate.health;
import java.time.Duration;
import java.util.Collections;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.actuate.health.HealthEndpointSupport.HealthResult;
import org.springframework.boot.actuate.health.HealthEndpointSupport.Result;
import org.springframework.boot.health.contributor.CompositeHealthContributor;
import org.springframework.boot.health.contributor.Health;
import org.springframework.boot.health.contributor.HealthContributor;
import org.springframework.boot.health.contributor.HealthIndicator;
import org.springframework.boot.health.contributor.Status;
import org.springframework.boot.health.registry.DefaultHealthContributorRegistry;
import org.springframework.boot.health.registry.HealthContributorRegistry;
import org.springframework.boot.test.system.CapturedOutput;
import org.springframework.boot.test.system.OutputCaptureExtension;
@ -38,38 +47,42 @@ import static org.mockito.Mockito.mock;
*/
@ExtendWith(OutputCaptureExtension.class)
class HealthEndpointTests extends
HealthEndpointSupportTests<HealthEndpoint, HealthContributorRegistry, HealthContributor, HealthComponent> {
HealthEndpointSupportTests<HealthEndpoint, Health, HealthDescriptor, HealthContributorRegistry, HealthContributor> {
@Test
void healthReturnsSystemHealth() {
this.registry.registerContributor("test", createContributor(this.up));
HealthComponent health = create(this.registry, this.groups).health();
assertThat(health.getStatus()).isEqualTo(Status.UP);
assertThat(health).isInstanceOf(SystemHealth.class);
HealthContributorRegistry registry = createRegistry("test", createContributor(this.up));
HealthEndpoint endpoint = create(registry, this.groups);
HealthDescriptor descriptor = endpoint.health();
assertThat(descriptor.getStatus()).isEqualTo(Status.UP);
assertThat(descriptor).isInstanceOf(SystemHealthDescriptor.class);
}
@Test
void healthWithNoContributorReturnsUp() {
assertThat(this.registry).isEmpty();
HealthComponent health = create(this.registry,
HealthEndpointGroups.of(mock(HealthEndpointGroup.class), Collections.emptyMap()))
.health();
assertThat(health.getStatus()).isEqualTo(Status.UP);
assertThat(health).isInstanceOf(Health.class);
HealthContributorRegistry registry = createRegistry(null);
HealthEndpointGroups groups = HealthEndpointGroups.of(mock(HealthEndpointGroup.class), Collections.emptyMap());
HealthEndpoint endpoint = create(registry, groups);
HealthDescriptor descriptor = endpoint.health();
assertThat(descriptor.getStatus()).isEqualTo(Status.UP);
assertThat(descriptor).isInstanceOf(IndicatedHealthDescriptor.class);
}
@Test
void healthWhenPathDoesNotExistReturnsNull() {
this.registry.registerContributor("test", createContributor(this.up));
HealthComponent health = create(this.registry, this.groups).healthForPath("missing");
assertThat(health).isNull();
HealthContributorRegistry registry = createRegistry("test", createContributor(this.up));
HealthEndpoint endpoint = create(registry, this.groups);
HealthDescriptor descriptor = endpoint.healthForPath("missing");
assertThat(descriptor).isNull();
}
@Test
void healthWhenPathExistsReturnsHealth() {
this.registry.registerContributor("test", createContributor(this.up));
HealthComponent health = create(this.registry, this.groups).healthForPath("test");
assertThat(health).isEqualTo(this.up);
HealthContributorRegistry registry = createRegistry("test", createContributor(this.up));
HealthEndpoint endpoint = create(registry, this.groups);
IndicatedHealthDescriptor descriptor = (IndicatedHealthDescriptor) endpoint.healthForPath("test");
assertThat(descriptor.getStatus()).isEqualTo(Status.UP);
assertThat(descriptor.getDetails()).containsEntry("spring", "boot");
}
@Test
@ -83,22 +96,23 @@ class HealthEndpointTests extends
}
return this.up;
};
this.registry.registerContributor("test", indicator);
create(this.registry, this.groups, Duration.ofMillis(10)).health();
HealthContributorRegistry registry = createRegistry("test", indicator);
HealthEndpoint endpoint = create(registry, this.groups, Duration.ofMillis(10));
endpoint.health();
assertThat(output).contains("Health contributor");
assertThat(output).contains("to respond");
}
@Override
protected HealthEndpoint create(HealthContributorRegistry registry, HealthEndpointGroups groups,
Duration slowIndicatorLoggingThreshold) {
return new HealthEndpoint(registry, groups, slowIndicatorLoggingThreshold);
Duration slowContributorLoggingThreshold) {
return new HealthEndpoint(registry, null, groups, slowContributorLoggingThreshold);
}
@Override
protected HealthContributorRegistry createRegistry() {
return new DefaultHealthContributorRegistry();
protected HealthContributorRegistry createRegistry(
Consumer<BiConsumer<String, HealthContributor>> intialRegistrations) {
return new DefaultHealthContributorRegistry(Collections.emptyList(), intialRegistrations);
}
@Override
@ -112,8 +126,8 @@ class HealthEndpointTests extends
}
@Override
protected HealthComponent getHealth(HealthResult<HealthComponent> result) {
return result.getHealth();
protected HealthDescriptor getDescriptor(Result<HealthDescriptor> result) {
return result.descriptor();
}
}

View File

@ -37,7 +37,8 @@ class HealthEndpointWebExtensionRuntimeHintsTests {
void shouldRegisterHints() {
RuntimeHints runtimeHints = new RuntimeHints();
new HealthEndpointWebExtensionRuntimeHints().registerHints(runtimeHints, getClass().getClassLoader());
Set<Class<?>> bindingTypes = Set.of(Health.class, SystemHealth.class, CompositeHealth.class);
Set<Class<?>> bindingTypes = Set.of(IndicatedHealthDescriptor.class, SystemHealthDescriptor.class,
CompositeHealthDescriptor.class);
for (Class<?> bindingType : bindingTypes) {
assertThat(RuntimeHintsPredicates.reflection()
.onType(bindingType)

View File

@ -19,6 +19,8 @@ package org.springframework.boot.actuate.health;
import java.time.Duration;
import java.util.Collections;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import org.junit.jupiter.api.Test;
@ -26,7 +28,14 @@ import org.springframework.boot.actuate.endpoint.ApiVersion;
import org.springframework.boot.actuate.endpoint.SecurityContext;
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
import org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
import org.springframework.boot.actuate.health.HealthEndpointSupport.HealthResult;
import org.springframework.boot.actuate.health.HealthEndpointSupport.Result;
import org.springframework.boot.health.contributor.CompositeHealthContributor;
import org.springframework.boot.health.contributor.Health;
import org.springframework.boot.health.contributor.HealthContributor;
import org.springframework.boot.health.contributor.HealthIndicator;
import org.springframework.boot.health.contributor.Status;
import org.springframework.boot.health.registry.DefaultHealthContributorRegistry;
import org.springframework.boot.health.registry.HealthContributorRegistry;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
@ -38,58 +47,65 @@ import static org.mockito.Mockito.mock;
* @author Scott Frederick
*/
class HealthEndpointWebExtensionTests extends
HealthEndpointSupportTests<HealthEndpointWebExtension, HealthContributorRegistry, HealthContributor, HealthComponent> {
HealthEndpointSupportTests<HealthEndpointWebExtension, Health, HealthDescriptor, HealthContributorRegistry, HealthContributor> {
@Test
void healthReturnsSystemHealth() {
this.registry.registerContributor("test", createContributor(this.up));
WebEndpointResponse<HealthComponent> response = create(this.registry, this.groups).health(ApiVersion.LATEST,
WebServerNamespace.SERVER, SecurityContext.NONE);
HealthComponent health = response.getBody();
assertThat(health.getStatus()).isEqualTo(Status.UP);
assertThat(health).isInstanceOf(SystemHealth.class);
HealthContributorRegistry registry = createRegistry("test", createContributor(this.up));
HealthEndpointWebExtension endpoint = create(registry, this.groups);
WebEndpointResponse<HealthDescriptor> response = endpoint.health(ApiVersion.LATEST, WebServerNamespace.SERVER,
SecurityContext.NONE);
HealthDescriptor descriptor = response.getBody();
assertThat(descriptor.getStatus()).isEqualTo(Status.UP);
assertThat(descriptor).isInstanceOf(SystemHealthDescriptor.class);
assertThat(response.getStatus()).isEqualTo(200);
}
@Test
void healthWithNoContributorReturnsUp() {
assertThat(this.registry).isEmpty();
WebEndpointResponse<HealthComponent> response = create(this.registry,
HealthEndpointGroups.of(mock(HealthEndpointGroup.class), Collections.emptyMap()))
.health(ApiVersion.LATEST, WebServerNamespace.SERVER, SecurityContext.NONE);
HealthContributorRegistry registry = createRegistry(null);
HealthEndpointGroups groups = HealthEndpointGroups.of(mock(HealthEndpointGroup.class), Collections.emptyMap());
HealthEndpointWebExtension endpoint = create(registry, groups);
WebEndpointResponse<HealthDescriptor> response = endpoint.health(ApiVersion.LATEST, WebServerNamespace.SERVER,
SecurityContext.NONE);
assertThat(response.getStatus()).isEqualTo(200);
HealthComponent health = response.getBody();
assertThat(health.getStatus()).isEqualTo(Status.UP);
assertThat(health).isInstanceOf(Health.class);
HealthDescriptor descriptor = response.getBody();
assertThat(descriptor.getStatus()).isEqualTo(Status.UP);
assertThat(descriptor).isInstanceOf(IndicatedHealthDescriptor.class);
}
@Test
void healthWhenPathDoesNotExistReturnsHttp404() {
this.registry.registerContributor("test", createContributor(this.up));
WebEndpointResponse<HealthComponent> response = create(this.registry, this.groups).health(ApiVersion.LATEST,
WebServerNamespace.SERVER, SecurityContext.NONE, "missing");
HealthContributorRegistry registry = createRegistry("test", createContributor(this.up));
HealthEndpointWebExtension endpoint = create(registry, this.groups);
WebEndpointResponse<HealthDescriptor> response = endpoint.health(ApiVersion.LATEST, WebServerNamespace.SERVER,
SecurityContext.NONE, "missing");
assertThat(response.getBody()).isNull();
assertThat(response.getStatus()).isEqualTo(404);
}
@Test
void healthWhenPathExistsReturnsHealth() {
this.registry.registerContributor("test", createContributor(this.up));
WebEndpointResponse<HealthComponent> response = create(this.registry, this.groups).health(ApiVersion.LATEST,
WebServerNamespace.SERVER, SecurityContext.NONE, "test");
assertThat(response.getBody()).isEqualTo(this.up);
HealthContributorRegistry registry = createRegistry("test", createContributor(this.up));
HealthEndpointWebExtension endpoint = create(registry, this.groups);
WebEndpointResponse<HealthDescriptor> response = endpoint.health(ApiVersion.LATEST, WebServerNamespace.SERVER,
SecurityContext.NONE, "test");
IndicatedHealthDescriptor descriptor = (IndicatedHealthDescriptor) response.getBody();
assertThat(descriptor.getStatus()).isEqualTo(Status.UP);
assertThat(descriptor.getDetails()).containsEntry("spring", "boot");
assertThat(response.getStatus()).isEqualTo(200);
}
@Override
protected HealthEndpointWebExtension create(HealthContributorRegistry registry, HealthEndpointGroups groups,
Duration slowIndicatorLoggingThreshold) {
return new HealthEndpointWebExtension(registry, groups, slowIndicatorLoggingThreshold);
return new HealthEndpointWebExtension(registry, null, groups, slowIndicatorLoggingThreshold);
}
@Override
protected HealthContributorRegistry createRegistry() {
return new DefaultHealthContributorRegistry();
protected HealthContributorRegistry createRegistry(
Consumer<BiConsumer<String, HealthContributor>> intialRegistrations) {
return new DefaultHealthContributorRegistry(Collections.emptyList(), intialRegistrations);
}
@Override
@ -103,8 +119,8 @@ class HealthEndpointWebExtensionTests extends
}
@Override
protected HealthComponent getHealth(HealthResult<HealthComponent> result) {
return result.getHealth();
protected HealthDescriptor getDescriptor(Result<HealthDescriptor> result) {
return result.descriptor();
}
}

View File

@ -0,0 +1,54 @@
/*
* Copyright 2012-present 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 com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper;
import org.junit.jupiter.api.Test;
import org.springframework.boot.health.contributor.Health;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link IndicatedHealthDescriptor}.
*
* @author Phillip Webb
*/
class IndicatedHealthDescriptorTests {
@Test
void serializeWithJacksonReturnsValidJson() throws Exception {
IndicatedHealthDescriptor descriptor = new IndicatedHealthDescriptor(
Health.outOfService().withDetail("spring", "boot").build());
ObjectMapper mapper = JsonMapper.builder().enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY).build();
String json = mapper.writeValueAsString(descriptor);
assertThat(json).isEqualTo("""
{"details":{"spring":"boot"},"status":"OUT_OF_SERVICE"}""");
}
@Test
void serializeWithJacksonWhenEmptyDetailsReturnsValidJson() throws Exception {
IndicatedHealthDescriptor descriptor = new IndicatedHealthDescriptor(Health.outOfService().build());
ObjectMapper mapper = JsonMapper.builder().enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY).build();
String json = mapper.writeValueAsString(descriptor);
assertThat(json).isEqualTo("""
{"status":"OUT_OF_SERVICE"}""");
}
}

View File

@ -1,51 +0,0 @@
/*
* Copyright 2012-present 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.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link NamedContributor}.
*
* @author Phillip Webb
*/
class NamedContributorTests {
@Test
void ofNameAndContributorCreatesContributor() {
NamedContributor<String> contributor = NamedContributor.of("one", "two");
assertThat(contributor.getName()).isEqualTo("one");
assertThat(contributor.getContributor()).isEqualTo("two");
}
@Test
void ofWhenNameIsNullThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() -> NamedContributor.of(null, "two"))
.withMessage("'name' must not be null");
}
@Test
void ofWhenContributorIsNullThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() -> NamedContributor.of("one", null))
.withMessage("'contributor' must not be null");
}
}

View File

@ -19,6 +19,8 @@ package org.springframework.boot.actuate.health;
import java.time.Duration;
import java.util.Collections;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
@ -26,7 +28,14 @@ import reactor.core.publisher.Mono;
import org.springframework.boot.actuate.endpoint.ApiVersion;
import org.springframework.boot.actuate.endpoint.SecurityContext;
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
import org.springframework.boot.actuate.health.HealthEndpointSupport.HealthResult;
import org.springframework.boot.actuate.health.HealthEndpointSupport.Result;
import org.springframework.boot.health.contributor.CompositeReactiveHealthContributor;
import org.springframework.boot.health.contributor.Health;
import org.springframework.boot.health.contributor.ReactiveHealthContributor;
import org.springframework.boot.health.contributor.ReactiveHealthIndicator;
import org.springframework.boot.health.contributor.Status;
import org.springframework.boot.health.registry.DefaultReactiveHealthContributorRegistry;
import org.springframework.boot.health.registry.ReactiveHealthContributorRegistry;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
@ -38,37 +47,40 @@ import static org.mockito.Mockito.mock;
* @author Scott Frederick
*/
class ReactiveHealthEndpointWebExtensionTests extends
HealthEndpointSupportTests<ReactiveHealthEndpointWebExtension, ReactiveHealthContributorRegistry, ReactiveHealthContributor, Mono<? extends HealthComponent>> {
HealthEndpointSupportTests<ReactiveHealthEndpointWebExtension, Mono<? extends Health>, Mono<? extends HealthDescriptor>, ReactiveHealthContributorRegistry, ReactiveHealthContributor> {
@Test
void healthReturnsSystemHealth() {
this.registry.registerContributor("test", createContributor(this.up));
WebEndpointResponse<? extends HealthComponent> response = create(this.registry, this.groups)
ReactiveHealthContributorRegistry registry = createRegistry("test", createContributor(this.up));
ReactiveHealthEndpointWebExtension endpoint = create(registry, this.groups);
WebEndpointResponse<? extends HealthDescriptor> response = endpoint
.health(ApiVersion.LATEST, null, SecurityContext.NONE)
.block();
HealthComponent health = response.getBody();
assertThat(health.getStatus()).isEqualTo(Status.UP);
assertThat(health).isInstanceOf(SystemHealth.class);
HealthDescriptor descriptor = response.getBody();
assertThat(descriptor.getStatus()).isEqualTo(Status.UP);
assertThat(descriptor).isInstanceOf(SystemHealthDescriptor.class);
assertThat(response.getStatus()).isEqualTo(200);
}
@Test
void healthWithNoContributorReturnsUp() {
assertThat(this.registry).isEmpty();
WebEndpointResponse<? extends HealthComponent> response = create(this.registry,
HealthEndpointGroups.of(mock(HealthEndpointGroup.class), Collections.emptyMap()))
ReactiveHealthContributorRegistry registry = createRegistry(null);
HealthEndpointGroups groups = HealthEndpointGroups.of(mock(HealthEndpointGroup.class), Collections.emptyMap());
ReactiveHealthEndpointWebExtension endpoint = create(registry, groups);
WebEndpointResponse<? extends HealthDescriptor> response = endpoint
.health(ApiVersion.LATEST, null, SecurityContext.NONE)
.block();
assertThat(response.getStatus()).isEqualTo(200);
HealthComponent health = response.getBody();
assertThat(health.getStatus()).isEqualTo(Status.UP);
assertThat(health).isInstanceOf(Health.class);
HealthDescriptor descriptor = response.getBody();
assertThat(descriptor.getStatus()).isEqualTo(Status.UP);
assertThat(descriptor).isInstanceOf(IndicatedHealthDescriptor.class);
}
@Test
void healthWhenPathDoesNotExistReturnsHttp404() {
this.registry.registerContributor("test", createContributor(this.up));
WebEndpointResponse<? extends HealthComponent> response = create(this.registry, this.groups)
ReactiveHealthContributorRegistry registry = createRegistry("test", createContributor(this.up));
ReactiveHealthEndpointWebExtension endpoint = create(registry, this.groups);
WebEndpointResponse<? extends HealthDescriptor> response = endpoint
.health(ApiVersion.LATEST, null, SecurityContext.NONE, "missing")
.block();
assertThat(response.getBody()).isNull();
@ -77,23 +89,27 @@ class ReactiveHealthEndpointWebExtensionTests extends
@Test
void healthWhenPathExistsReturnsHealth() {
this.registry.registerContributor("test", createContributor(this.up));
WebEndpointResponse<? extends HealthComponent> response = create(this.registry, this.groups)
ReactiveHealthContributorRegistry registry = createRegistry("test", createContributor(this.up));
ReactiveHealthEndpointWebExtension endpoint = create(registry, this.groups);
WebEndpointResponse<? extends HealthDescriptor> response = endpoint
.health(ApiVersion.LATEST, null, SecurityContext.NONE, "test")
.block();
assertThat(response.getBody()).isEqualTo(this.up);
IndicatedHealthDescriptor descriptor = (IndicatedHealthDescriptor) response.getBody();
assertThat(descriptor.getStatus()).isEqualTo(Status.UP);
assertThat(descriptor.getDetails()).containsEntry("spring", "boot");
assertThat(response.getStatus()).isEqualTo(200);
}
@Override
protected ReactiveHealthEndpointWebExtension create(ReactiveHealthContributorRegistry registry,
HealthEndpointGroups groups, Duration slowIndicatorLoggingThreshold) {
return new ReactiveHealthEndpointWebExtension(registry, groups, slowIndicatorLoggingThreshold);
HealthEndpointGroups groups, Duration slowContributorLoggingThreshold) {
return new ReactiveHealthEndpointWebExtension(registry, null, groups, slowContributorLoggingThreshold);
}
@Override
protected ReactiveHealthContributorRegistry createRegistry() {
return new DefaultReactiveHealthContributorRegistry();
protected ReactiveHealthContributorRegistry createRegistry(
Consumer<BiConsumer<String, ReactiveHealthContributor>> intialRegistrations) {
return new DefaultReactiveHealthContributorRegistry(Collections.emptyList(), intialRegistrations);
}
@Override
@ -108,8 +124,8 @@ class ReactiveHealthEndpointWebExtensionTests extends
}
@Override
protected HealthComponent getHealth(HealthResult<Mono<? extends HealthComponent>> result) {
return result.getHealth().block();
protected HealthDescriptor getDescriptor(Result<Mono<? extends HealthDescriptor>> result) {
return result.descriptor().block();
}
}

View File

@ -23,7 +23,8 @@ import org.junit.jupiter.api.extension.ExtendWith;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.boot.actuate.health.Health.Builder;
import org.springframework.boot.health.contributor.AbstractReactiveHealthIndicator;
import org.springframework.boot.health.contributor.Health;
import org.springframework.boot.test.system.CapturedOutput;
import org.springframework.boot.test.system.OutputCaptureExtension;
@ -73,7 +74,7 @@ class ReactiveHealthIndicatorImplementationTests {
}
@Override
protected Mono<Health> doHealthCheck(Builder builder) {
protected Mono<Health> doHealthCheck(Health.Builder builder) {
return Mono.just(builder.up().build());
}
@ -86,7 +87,7 @@ class ReactiveHealthIndicatorImplementationTests {
}
@Override
protected Mono<Health> doHealthCheck(Builder builder) {
protected Mono<Health> doHealthCheck(Health.Builder builder) {
return Mono.error(new UnsupportedOperationException());
}
@ -100,7 +101,7 @@ class ReactiveHealthIndicatorImplementationTests {
}
@Override
protected Mono<Health> doHealthCheck(Builder builder) {
protected Mono<Health> doHealthCheck(Health.Builder builder) {
throw new RuntimeException();
}

View File

@ -22,6 +22,7 @@ import java.util.Map;
import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
import org.springframework.boot.health.contributor.Status;
import static org.assertj.core.api.Assertions.assertThat;

View File

@ -18,6 +18,8 @@ package org.springframework.boot.actuate.health;
import org.junit.jupiter.api.Test;
import org.springframework.boot.health.contributor.Status;
import static org.assertj.core.api.Assertions.assertThat;
/**

View File

@ -0,0 +1,84 @@
/*
* Copyright 2012-present 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.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper;
import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.endpoint.ApiVersion;
import org.springframework.boot.health.contributor.Health;
import org.springframework.boot.health.contributor.Status;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link SystemHealthDescriptor}.
*
* @author Phillip Webb
*/
class SystemHealthDescriptorTests {
@Test
void serializeWithJacksonReturnsValidJson() throws Exception {
Map<String, HealthDescriptor> components = new LinkedHashMap<>();
components.put("db1", new IndicatedHealthDescriptor(Health.up().build()));
components.put("db2", new IndicatedHealthDescriptor(Health.down().withDetail("a", "b").build()));
Set<String> groups = new LinkedHashSet<>(Arrays.asList("liveness", "readiness"));
SystemHealthDescriptor descriptor = new SystemHealthDescriptor(ApiVersion.V3, Status.UP, components, groups);
ObjectMapper mapper = JsonMapper.builder().enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY).build();
String json = mapper.writeValueAsString(descriptor);
assertThat(json).isEqualTo(
"""
{"components":{"db1":{"status":"UP"},"db2":{"details":{"a":"b"},"status":"DOWN"}},"groups":["liveness","readiness"],"status":"UP"}""");
}
@Test
void serializeWhenNoGroupsWithJacksonReturnsValidJson() throws Exception {
Map<String, HealthDescriptor> components = new LinkedHashMap<>();
components.put("db1", new IndicatedHealthDescriptor(Health.up().build()));
components.put("db2", new IndicatedHealthDescriptor(Health.down().withDetail("a", "b").build()));
SystemHealthDescriptor descriptor = new SystemHealthDescriptor(ApiVersion.V3, Status.UP, components, null);
ObjectMapper mapper = JsonMapper.builder().enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY).build();
String json = mapper.writeValueAsString(descriptor);
assertThat(json).isEqualTo("""
{"components":{"db1":{"status":"UP"},"db2":{"details":{"a":"b"},"status":"DOWN"}},"status":"UP"}""");
}
@Test // gh-26797
void serializeV2WithJacksonAndDisabledCanOverrideAccessModifiersReturnsValidJson() throws Exception {
Map<String, HealthDescriptor> components = new LinkedHashMap<>();
components.put("db1", new IndicatedHealthDescriptor(Health.up().build()));
components.put("db2", new IndicatedHealthDescriptor(Health.down().withDetail("a", "b").build()));
SystemHealthDescriptor descriptor = new SystemHealthDescriptor(ApiVersion.V2, Status.UP, components, null);
ObjectMapper mapper = JsonMapper.builder()
.enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY)
.disable(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS)
.build();
String json = mapper.writeValueAsString(descriptor);
assertThat(json).isEqualTo("""
{"details":{"db1":{"status":"UP"},"db2":{"details":{"a":"b"},"status":"DOWN"}},"status":"UP"}""");
}
}

View File

@ -1,53 +0,0 @@
/*
* Copyright 2012-present 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 org.springframework.boot.actuate.endpoint.ApiVersion;
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(ApiVersion.V3, Status.UP, components, groups);
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(health);
assertThat(json).isEqualTo("{\"status\":\"UP\",\"components\":{\"db1\":{\"status\":\"UP\"},"
+ "\"db2\":{\"status\":\"DOWN\",\"details\":{\"a\":\"b\"}}},"
+ "\"groups\":[\"liveness\",\"readiness\"]}");
}
}

View File

@ -23,8 +23,8 @@ import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.Status;
import org.springframework.boot.health.contributor.Health;
import org.springframework.boot.health.contributor.Status;
import org.springframework.boot.info.SslInfo;
import org.springframework.boot.info.SslInfo.BundleInfo;
import org.springframework.boot.info.SslInfo.CertificateChainInfo;

View File

@ -24,9 +24,9 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.Status;
import org.springframework.boot.health.contributor.Health;
import org.springframework.boot.health.contributor.HealthIndicator;
import org.springframework.boot.health.contributor.Status;
import org.springframework.util.unit.DataSize;
import static org.assertj.core.api.Assertions.assertThat;

View File

@ -35,9 +35,9 @@ dependencies {
implementation(project(":spring-boot-project:spring-boot-tx"))
optional(project(":spring-boot-project:spring-boot-actuator-autoconfigure"))
optional(project(":spring-boot-project:spring-boot-autoconfigure"))
optional(project(":spring-boot-project:spring-boot-docker-compose"))
optional(project(":spring-boot-project:spring-boot-health"))
optional(project(":spring-boot-project:spring-boot-metrics"))
optional(project(":spring-boot-project:spring-boot-testcontainers"))
optional("io.micrometer:micrometer-core")

View File

@ -18,16 +18,16 @@ package org.springframework.boot.amqp.autoconfigure.health;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.actuate.autoconfigure.health.CompositeHealthContributorConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator;
import org.springframework.boot.actuate.health.HealthContributor;
import org.springframework.boot.amqp.actuate.health.RabbitHealthIndicator;
import org.springframework.boot.amqp.autoconfigure.RabbitAutoConfiguration;
import org.springframework.boot.amqp.health.RabbitHealthIndicator;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.health.autoconfigure.contributor.CompositeHealthContributorConfiguration;
import org.springframework.boot.health.autoconfigure.contributor.ConditionalOnEnabledHealthIndicator;
import org.springframework.boot.health.contributor.HealthContributor;
import org.springframework.context.annotation.Bean;
/**

View File

@ -14,12 +14,12 @@
* limitations under the License.
*/
package org.springframework.boot.amqp.actuate.health;
package org.springframework.boot.amqp.health;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.health.contributor.AbstractHealthIndicator;
import org.springframework.boot.health.contributor.Health;
import org.springframework.boot.health.contributor.HealthIndicator;
import org.springframework.util.Assert;
/**

View File

@ -17,4 +17,4 @@
/**
* Health integration for AMQP and RabbitMQ.
*/
package org.springframework.boot.amqp.actuate.health;
package org.springframework.boot.amqp.health;

View File

@ -18,10 +18,10 @@ package org.springframework.boot.amqp.autoconfigure.health;
import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration;
import org.springframework.boot.amqp.actuate.health.RabbitHealthIndicator;
import org.springframework.boot.amqp.autoconfigure.RabbitAutoConfiguration;
import org.springframework.boot.amqp.health.RabbitHealthIndicator;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.health.autoconfigure.contributor.HealthContributorAutoConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import static org.assertj.core.api.Assertions.assertThat;

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.amqp.actuate.health;
package org.springframework.boot.amqp.health;
import java.util.Collections;
@ -27,8 +27,8 @@ import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.amqp.rabbit.core.ChannelCallback;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.Status;
import org.springframework.boot.health.contributor.Health;
import org.springframework.boot.health.contributor.Status;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;

View File

@ -30,9 +30,9 @@ dependencies {
api(project(":spring-boot-project:spring-boot"))
api("org.apache.cassandra:java-driver-core")
optional(project(":spring-boot-project:spring-boot-actuator-autoconfigure"))
optional(project(":spring-boot-project:spring-boot-autoconfigure"))
optional(project(":spring-boot-project:spring-boot-docker-compose"))
optional(project(":spring-boot-project:spring-boot-health"))
optional(project(":spring-boot-project:spring-boot-testcontainers"))
optional("io.projectreactor:reactor-core")
optional("org.testcontainers:cassandra")

View File

@ -18,13 +18,13 @@ package org.springframework.boot.cassandra.autoconfigure.health;
import com.datastax.oss.driver.api.core.CqlSession;
import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.cassandra.actuate.health.CassandraDriverHealthIndicator;
import org.springframework.boot.cassandra.autoconfigure.CassandraAutoConfiguration;
import org.springframework.boot.cassandra.autoconfigure.health.CassandraHealthContributorConfigurations.CassandraDriverConfiguration;
import org.springframework.boot.cassandra.health.CassandraDriverHealthIndicator;
import org.springframework.boot.health.autoconfigure.contributor.ConditionalOnEnabledHealthIndicator;
import org.springframework.context.annotation.Import;
/**

View File

@ -21,14 +21,14 @@ import java.util.Map;
import com.datastax.oss.driver.api.core.CqlSession;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.actuate.autoconfigure.health.CompositeHealthContributorConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.CompositeReactiveHealthContributorConfiguration;
import org.springframework.boot.actuate.health.HealthContributor;
import org.springframework.boot.actuate.health.ReactiveHealthContributor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.cassandra.actuate.health.CassandraDriverHealthIndicator;
import org.springframework.boot.cassandra.actuate.health.CassandraDriverReactiveHealthIndicator;
import org.springframework.boot.cassandra.health.CassandraDriverHealthIndicator;
import org.springframework.boot.cassandra.health.CassandraDriverReactiveHealthIndicator;
import org.springframework.boot.health.autoconfigure.contributor.CompositeHealthContributorConfiguration;
import org.springframework.boot.health.autoconfigure.contributor.CompositeReactiveHealthContributorConfiguration;
import org.springframework.boot.health.contributor.HealthContributor;
import org.springframework.boot.health.contributor.ReactiveHealthContributor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

View File

@ -19,13 +19,13 @@ package org.springframework.boot.cassandra.autoconfigure.health;
import com.datastax.oss.driver.api.core.CqlSession;
import reactor.core.publisher.Flux;
import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.cassandra.actuate.health.CassandraDriverReactiveHealthIndicator;
import org.springframework.boot.cassandra.autoconfigure.CassandraAutoConfiguration;
import org.springframework.boot.cassandra.autoconfigure.health.CassandraHealthContributorConfigurations.CassandraReactiveDriverConfiguration;
import org.springframework.boot.cassandra.health.CassandraDriverReactiveHealthIndicator;
import org.springframework.boot.health.autoconfigure.contributor.ConditionalOnEnabledHealthIndicator;
import org.springframework.context.annotation.Import;
/**

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.cassandra.actuate.health;
package org.springframework.boot.cassandra.health;
import java.util.Collection;
import java.util.Optional;
@ -23,10 +23,10 @@ import com.datastax.oss.driver.api.core.CqlSession;
import com.datastax.oss.driver.api.core.metadata.Node;
import com.datastax.oss.driver.api.core.metadata.NodeState;
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.Status;
import org.springframework.boot.health.contributor.AbstractHealthIndicator;
import org.springframework.boot.health.contributor.Health;
import org.springframework.boot.health.contributor.HealthIndicator;
import org.springframework.boot.health.contributor.Status;
import org.springframework.util.Assert;
/**

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.cassandra.actuate.health;
package org.springframework.boot.cassandra.health;
import java.util.Collection;
import java.util.Optional;
@ -24,10 +24,10 @@ import com.datastax.oss.driver.api.core.metadata.Node;
import com.datastax.oss.driver.api.core.metadata.NodeState;
import reactor.core.publisher.Mono;
import org.springframework.boot.actuate.health.AbstractReactiveHealthIndicator;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.ReactiveHealthIndicator;
import org.springframework.boot.actuate.health.Status;
import org.springframework.boot.health.contributor.AbstractReactiveHealthIndicator;
import org.springframework.boot.health.contributor.Health;
import org.springframework.boot.health.contributor.ReactiveHealthIndicator;
import org.springframework.boot.health.contributor.Status;
import org.springframework.util.Assert;
/**

View File

@ -17,4 +17,4 @@
/**
* Health integration for Cassandra.
*/
package org.springframework.boot.cassandra.actuate.health;
package org.springframework.boot.cassandra.health;

View File

@ -19,9 +19,9 @@ package org.springframework.boot.cassandra.autoconfigure.health;
import com.datastax.oss.driver.api.core.CqlSession;
import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.cassandra.actuate.health.CassandraDriverHealthIndicator;
import org.springframework.boot.cassandra.health.CassandraDriverHealthIndicator;
import org.springframework.boot.health.autoconfigure.contributor.HealthContributorAutoConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import static org.assertj.core.api.Assertions.assertThat;

View File

@ -19,9 +19,9 @@ package org.springframework.boot.cassandra.autoconfigure.health;
import com.datastax.oss.driver.api.core.CqlSession;
import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.cassandra.actuate.health.CassandraDriverReactiveHealthIndicator;
import org.springframework.boot.cassandra.health.CassandraDriverReactiveHealthIndicator;
import org.springframework.boot.health.autoconfigure.contributor.HealthContributorAutoConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import static org.assertj.core.api.Assertions.assertThat;

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.cassandra.actuate.health;
package org.springframework.boot.cassandra.health;
import java.util.ArrayList;
import java.util.Collections;
@ -31,8 +31,8 @@ import com.datastax.oss.driver.api.core.metadata.Node;
import com.datastax.oss.driver.api.core.metadata.NodeState;
import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.Status;
import org.springframework.boot.health.contributor.Health;
import org.springframework.boot.health.contributor.Status;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.cassandra.actuate.health;
package org.springframework.boot.cassandra.health;
import java.time.Duration;
import java.util.ArrayList;
@ -34,8 +34,8 @@ import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.Status;
import org.springframework.boot.health.contributor.Health;
import org.springframework.boot.health.contributor.Status;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;

Some files were not shown because too many files have changed in this diff Show More