diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/CompositeHealthIndicatorConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/CompositeHealthIndicatorConfiguration.java new file mode 100644 index 00000000000..0257b358431 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/CompositeHealthIndicatorConfiguration.java @@ -0,0 +1,69 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.health; + +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.health.CompositeHealthIndicator; +import org.springframework.boot.actuate.health.DefaultHealthIndicatorRegistry; +import org.springframework.boot.actuate.health.HealthAggregator; +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.boot.actuate.health.HealthIndicatorRegistry; +import org.springframework.core.ResolvableType; + +/** + * Base class for configurations that can combine source beans using a + * {@link CompositeHealthIndicator}. + * + * @param the health indicator type + * @param the bean source type + * @author Stephane Nicoll + * @since 2.0.0 + * @deprecated since 2.2.0 in favor of {@link CompositeHealthContributorConfiguration} + */ +@Deprecated +public abstract class CompositeHealthIndicatorConfiguration { + + @Autowired + private HealthAggregator healthAggregator; + + protected HealthIndicator createHealthIndicator(Map beans) { + if (beans.size() == 1) { + return createHealthIndicator(beans.values().iterator().next()); + } + HealthIndicatorRegistry registry = new DefaultHealthIndicatorRegistry(); + beans.forEach((name, source) -> registry.register(name, createHealthIndicator(source))); + return new CompositeHealthIndicator(this.healthAggregator, registry); + } + + @SuppressWarnings("unchecked") + protected H createHealthIndicator(S source) { + Class[] generics = ResolvableType.forClass(CompositeHealthIndicatorConfiguration.class, getClass()) + .resolveGenerics(); + Class indicatorClass = (Class) generics[0]; + Class sourceClass = (Class) generics[1]; + try { + return indicatorClass.getConstructor(sourceClass).newInstance(source); + } + catch (Exception ex) { + throw new IllegalStateException( + "Unable to create indicator " + indicatorClass + " for source " + sourceClass, ex); + } + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/CompositeReactiveHealthIndicatorConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/CompositeReactiveHealthIndicatorConfiguration.java new file mode 100644 index 00000000000..d3ca609aeb9 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/CompositeReactiveHealthIndicatorConfiguration.java @@ -0,0 +1,69 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.health; + +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.health.CompositeReactiveHealthIndicator; +import org.springframework.boot.actuate.health.DefaultReactiveHealthIndicatorRegistry; +import org.springframework.boot.actuate.health.HealthAggregator; +import org.springframework.boot.actuate.health.ReactiveHealthIndicator; +import org.springframework.boot.actuate.health.ReactiveHealthIndicatorRegistry; +import org.springframework.core.ResolvableType; + +/** + * Reactive variant of {@link CompositeHealthIndicatorConfiguration}. + * + * @param the health indicator type + * @param the bean source type + * @author Stephane Nicoll + * @since 2.0.0 + * @deprecated since 2.2.0 in favor of + * {@link CompositeReactiveHealthContributorConfiguration} + */ +@Deprecated +public abstract class CompositeReactiveHealthIndicatorConfiguration { + + @Autowired + private HealthAggregator healthAggregator; + + protected ReactiveHealthIndicator createHealthIndicator(Map beans) { + if (beans.size() == 1) { + return createHealthIndicator(beans.values().iterator().next()); + } + ReactiveHealthIndicatorRegistry registry = new DefaultReactiveHealthIndicatorRegistry(); + beans.forEach((name, source) -> registry.register(name, createHealthIndicator(source))); + return new CompositeReactiveHealthIndicator(this.healthAggregator, registry); + } + + @SuppressWarnings("unchecked") + protected H createHealthIndicator(S source) { + Class[] generics = ResolvableType.forClass(CompositeReactiveHealthIndicatorConfiguration.class, getClass()) + .resolveGenerics(); + Class indicatorClass = (Class) generics[0]; + Class sourceClass = (Class) generics[1]; + try { + return indicatorClass.getConstructor(sourceClass).newInstance(source); + } + catch (Exception ex) { + throw new IllegalStateException( + "Unable to create indicator " + indicatorClass + " for source " + sourceClass, ex); + } + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthAggregatorStatusAggregatorAdapter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthAggregatorStatusAggregatorAdapter.java new file mode 100644 index 00000000000..19c002de4ab --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthAggregatorStatusAggregatorAdapter.java @@ -0,0 +1,59 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.health; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.HealthAggregator; +import org.springframework.boot.actuate.health.Status; +import org.springframework.boot.actuate.health.StatusAggregator; + +/** + * Adapter class to convert a legacy {@link HealthAggregator} to a + * {@link StatusAggregator}. + * + * @author Phillip Webb + */ +@SuppressWarnings("deprecation") +class HealthAggregatorStatusAggregatorAdapter implements StatusAggregator { + + private HealthAggregator healthAggregator; + + HealthAggregatorStatusAggregatorAdapter(HealthAggregator healthAggregator) { + this.healthAggregator = healthAggregator; + } + + @Override + public Status getAggregateStatus(Set statuses) { + int index = 0; + Map healths = new LinkedHashMap<>(); + for (Status status : statuses) { + index++; + healths.put("health" + index, asHealth(status)); + } + Health aggregate = this.healthAggregator.aggregate(healths); + return aggregate.getStatus(); + } + + private Health asHealth(Status status) { + return Health.status(status).build(); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthContributorRegistryHealthIndicatorRegistryAdapter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthContributorRegistryHealthIndicatorRegistryAdapter.java new file mode 100644 index 00000000000..e99f170b6f9 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthContributorRegistryHealthIndicatorRegistryAdapter.java @@ -0,0 +1,79 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.health; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.springframework.boot.actuate.health.HealthContributor; +import org.springframework.boot.actuate.health.HealthContributorRegistry; +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.boot.actuate.health.HealthIndicatorRegistry; +import org.springframework.boot.actuate.health.NamedContributor; +import org.springframework.util.Assert; + +/** + * Adapter class to convert a {@link HealthContributorRegistry} to a legacy + * {@link HealthIndicatorRegistry}. + * + * @author Phillip Webb + */ +@SuppressWarnings("deprecation") +class HealthContributorRegistryHealthIndicatorRegistryAdapter implements HealthIndicatorRegistry { + + private final HealthContributorRegistry contributorRegistry; + + HealthContributorRegistryHealthIndicatorRegistryAdapter(HealthContributorRegistry contributorRegistry) { + Assert.notNull(contributorRegistry, "ContributorRegistry must not be null"); + this.contributorRegistry = contributorRegistry; + } + + @Override + public void register(String name, HealthIndicator healthIndicator) { + this.contributorRegistry.registerContributor(name, healthIndicator); + } + + @Override + public HealthIndicator unregister(String name) { + HealthContributor contributor = this.contributorRegistry.unregisterContributor(name); + if (contributor instanceof HealthIndicator) { + return (HealthIndicator) contributor; + } + return null; + } + + @Override + public HealthIndicator get(String name) { + HealthContributor contributor = this.contributorRegistry.getContributor(name); + if (contributor instanceof HealthIndicator) { + return (HealthIndicator) contributor; + } + return null; + } + + @Override + public Map getAll() { + Map all = new LinkedHashMap<>(); + for (NamedContributor namedContributor : this.contributorRegistry) { + if (namedContributor.getContributor() instanceof HealthIndicator) { + all.put(namedContributor.getName(), (HealthIndicator) namedContributor.getContributor()); + } + } + return all; + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointAutoConfiguration.java index 096a212ecfa..972ee5d3a61 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import org.springframework.boot.actuate.autoconfigure.endpoint.condition.Conditi import org.springframework.boot.actuate.health.HealthEndpoint; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -29,14 +30,23 @@ import org.springframework.context.annotation.Import; * @author Andy Wilkinson * @author Stephane Nicoll * @author Phillip Webb - * @author Scott Frederick * @since 2.0.0 */ @Configuration(proxyBeanMethods = false) @ConditionalOnAvailableEndpoint(endpoint = HealthEndpoint.class) -@EnableConfigurationProperties(HealthEndpointProperties.class) -@Import({ HealthEndpointConfiguration.class, ReactiveHealthEndpointConfiguration.class, +@EnableConfigurationProperties +@Import({ LegacyHealthEndpointAdaptersConfiguration.class, LegacyHealthEndpointCompatibilityConfiguration.class, + HealthEndpointConfiguration.class, ReactiveHealthEndpointConfiguration.class, HealthEndpointWebExtensionConfiguration.class, HealthEndpointReactiveWebExtensionConfiguration.class }) public class HealthEndpointAutoConfiguration { + @Bean + @SuppressWarnings("deprecation") + HealthEndpointProperties healthEndpointProperties(HealthIndicatorProperties healthIndicatorProperties) { + HealthEndpointProperties healthEndpointProperties = new HealthEndpointProperties(); + healthEndpointProperties.getStatus().getOrder().addAll(healthIndicatorProperties.getOrder()); + healthEndpointProperties.getStatus().getHttpMapping().putAll(healthIndicatorProperties.getHttpMapping()); + return healthEndpointProperties; + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorAutoConfiguration.java new file mode 100644 index 00000000000..d6ef2959aae --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorAutoConfiguration.java @@ -0,0 +1,38 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.health; + +import org.springframework.boot.actuate.health.HealthContributor; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.context.annotation.Configuration; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for {@link HealthContributor health + * contributors}. + * + * @author Andy Wilkinson + * @author Stephane Nicoll + * @author Phillip Webb + * @author Vedran Pavic + * @since 2.0.0 + * @deprecated since 2.2.0 in favor of {@link HealthContributorAutoConfiguration} + */ +@Deprecated +@Configuration(proxyBeanMethods = false) +public class HealthIndicatorAutoConfiguration { + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorProperties.java new file mode 100644 index 00000000000..930707a7a4d --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorProperties.java @@ -0,0 +1,56 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.health; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.DeprecatedConfigurationProperty; + +/** + * Configuration properties for some health properties. + * + * @author Christian Dupuis + * @since 2.0.0 + * @deprecated since 2.2.0 in favor of {@link HealthEndpointProperties} + */ +@Deprecated +@ConfigurationProperties(prefix = "management.health.status") +public class HealthIndicatorProperties { + + private List order = new ArrayList<>(); + + private final Map httpMapping = new LinkedHashMap<>(); + + @DeprecatedConfigurationProperty(replacement = "management.endpoint.health.status.order") + public List getOrder() { + return this.order; + } + + public void setOrder(List order) { + this.order = order; + } + + @DeprecatedConfigurationProperty(replacement = "management.endpoint.health.status.http-mapping") + public Map getHttpMapping() { + return this.httpMapping; + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthStatusHttpMapperHttpCodeStatusMapperAdapter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthStatusHttpMapperHttpCodeStatusMapperAdapter.java new file mode 100644 index 00000000000..fcade05e3c5 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/HealthStatusHttpMapperHttpCodeStatusMapperAdapter.java @@ -0,0 +1,43 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.health; + +import org.springframework.boot.actuate.health.HealthStatusHttpMapper; +import org.springframework.boot.actuate.health.HttpCodeStatusMapper; +import org.springframework.boot.actuate.health.Status; + +/** + * Adapter class to convert a legacy {@link HealthStatusHttpMapper} to a + * {@link HttpCodeStatusMapper}. + * + * @author Phillip Webb + */ +@SuppressWarnings("deprecation") +class HealthStatusHttpMapperHttpCodeStatusMapperAdapter implements HttpCodeStatusMapper { + + private final HealthStatusHttpMapper healthStatusHttpMapper; + + HealthStatusHttpMapperHttpCodeStatusMapperAdapter(HealthStatusHttpMapper healthStatusHttpMapper) { + this.healthStatusHttpMapper = healthStatusHttpMapper; + } + + @Override + public int getStatusCode(Status status) { + return this.healthStatusHttpMapper.mapStatus(status); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/LegacyHealthEndpointAdaptersConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/LegacyHealthEndpointAdaptersConfiguration.java new file mode 100644 index 00000000000..e04f0a2c5af --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/LegacyHealthEndpointAdaptersConfiguration.java @@ -0,0 +1,49 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.health; + +import org.springframework.boot.actuate.health.HttpCodeStatusMapper; +import org.springframework.boot.actuate.health.StatusAggregator; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Configuration to adapt legacy deprecated health endpoint classes and interfaces. + * + * @author Phillip Webb + * @see HealthEndpointAutoConfiguration + */ +@Configuration(proxyBeanMethods = false) +@SuppressWarnings("deprecation") +class LegacyHealthEndpointAdaptersConfiguration { + + @Bean + @ConditionalOnBean(org.springframework.boot.actuate.health.HealthAggregator.class) + StatusAggregator healthAggregatorStatusAggregatorAdapter( + org.springframework.boot.actuate.health.HealthAggregator healthAggregator) { + return new HealthAggregatorStatusAggregatorAdapter(healthAggregator); + } + + @Bean + @ConditionalOnBean(org.springframework.boot.actuate.health.HealthStatusHttpMapper.class) + HttpCodeStatusMapper healthStatusHttpMapperHttpCodeStatusMapperAdapter( + org.springframework.boot.actuate.health.HealthStatusHttpMapper healthStatusHttpMapper) { + return new HealthStatusHttpMapperHttpCodeStatusMapperAdapter(healthStatusHttpMapper); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/LegacyHealthEndpointCompatibilityConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/LegacyHealthEndpointCompatibilityConfiguration.java new file mode 100644 index 00000000000..4c1b1add27d --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/LegacyHealthEndpointCompatibilityConfiguration.java @@ -0,0 +1,87 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.health; + +import reactor.core.publisher.Mono; + +import org.springframework.boot.actuate.health.HealthAggregator; +import org.springframework.boot.actuate.health.HealthContributorRegistry; +import org.springframework.boot.actuate.health.HealthIndicatorRegistry; +import org.springframework.boot.actuate.health.HealthStatusHttpMapper; +import org.springframework.boot.actuate.health.OrderedHealthAggregator; +import org.springframework.boot.actuate.health.ReactiveHealthContributorRegistry; +import org.springframework.boot.actuate.health.ReactiveHealthIndicatorRegistry; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.CollectionUtils; + +/** + * Configuration to adapt legacy deprecated health endpoint classes and interfaces. + * + * @author Phillip Webb + * @see HealthEndpointAutoConfiguration + */ +@Configuration(proxyBeanMethods = false) +@SuppressWarnings("deprecation") +@EnableConfigurationProperties(HealthIndicatorProperties.class) +class LegacyHealthEndpointCompatibilityConfiguration { + + @Bean + @ConditionalOnMissingBean + HealthAggregator healthAggregator(HealthIndicatorProperties healthIndicatorProperties) { + OrderedHealthAggregator aggregator = new OrderedHealthAggregator(); + if (!CollectionUtils.isEmpty(healthIndicatorProperties.getOrder())) { + aggregator.setStatusOrder(healthIndicatorProperties.getOrder()); + } + return aggregator; + } + + @Bean + @ConditionalOnMissingBean + HealthStatusHttpMapper healthStatusHttpMapper(HealthIndicatorProperties healthIndicatorProperties) { + HealthStatusHttpMapper mapper = new HealthStatusHttpMapper(); + if (!CollectionUtils.isEmpty(healthIndicatorProperties.getHttpMapping())) { + mapper.setStatusMapping(healthIndicatorProperties.getHttpMapping()); + } + return mapper; + } + + @Bean + @ConditionalOnMissingBean(HealthIndicatorRegistry.class) + HealthContributorRegistryHealthIndicatorRegistryAdapter healthIndicatorRegistry( + HealthContributorRegistry healthContributorRegistry) { + return new HealthContributorRegistryHealthIndicatorRegistryAdapter(healthContributorRegistry); + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(Mono.class) + static class LegacyReactiveHealthEndpointCompatibilityConfiguration { + + @Bean + @ConditionalOnMissingBean(ReactiveHealthIndicatorRegistry.class) + ReactiveHealthContributorRegistryReactiveHealthIndicatorRegistryAdapter reactiveHealthIndicatorRegistry( + ReactiveHealthContributorRegistry reactiveHealthContributorRegistry) { + return new ReactiveHealthContributorRegistryReactiveHealthIndicatorRegistryAdapter( + reactiveHealthContributorRegistry); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/ReactiveHealthContributorRegistryReactiveHealthIndicatorRegistryAdapter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/ReactiveHealthContributorRegistryReactiveHealthIndicatorRegistryAdapter.java new file mode 100644 index 00000000000..863edd6aea5 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/health/ReactiveHealthContributorRegistryReactiveHealthIndicatorRegistryAdapter.java @@ -0,0 +1,81 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.health; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.springframework.boot.actuate.health.NamedContributor; +import org.springframework.boot.actuate.health.ReactiveHealthContributor; +import org.springframework.boot.actuate.health.ReactiveHealthContributorRegistry; +import org.springframework.boot.actuate.health.ReactiveHealthIndicator; +import org.springframework.boot.actuate.health.ReactiveHealthIndicatorRegistry; +import org.springframework.util.Assert; + +/** + * Adapter class to convert a {@link ReactiveHealthContributorRegistry} to a legacy + * {@link ReactiveHealthIndicatorRegistry}. + * + * @author Phillip Webb + */ +@SuppressWarnings("deprecation") +class ReactiveHealthContributorRegistryReactiveHealthIndicatorRegistryAdapter + implements ReactiveHealthIndicatorRegistry { + + private final ReactiveHealthContributorRegistry contributorRegistry; + + ReactiveHealthContributorRegistryReactiveHealthIndicatorRegistryAdapter( + ReactiveHealthContributorRegistry contributorRegistry) { + Assert.notNull(contributorRegistry, "ContributorRegistry must not be null"); + this.contributorRegistry = contributorRegistry; + } + + @Override + public void register(String name, ReactiveHealthIndicator healthIndicator) { + this.contributorRegistry.registerContributor(name, healthIndicator); + } + + @Override + public ReactiveHealthIndicator unregister(String name) { + ReactiveHealthContributor contributor = this.contributorRegistry.unregisterContributor(name); + if (contributor instanceof ReactiveHealthIndicator) { + return (ReactiveHealthIndicator) contributor; + } + return null; + } + + @Override + public ReactiveHealthIndicator get(String name) { + ReactiveHealthContributor contributor = this.contributorRegistry.getContributor(name); + if (contributor instanceof ReactiveHealthIndicator) { + return (ReactiveHealthIndicator) contributor; + } + return null; + } + + @Override + public Map getAll() { + Map all = new LinkedHashMap<>(); + for (NamedContributor namedContributor : this.contributorRegistry) { + if (namedContributor.getContributor() instanceof ReactiveHealthIndicator) { + all.put(namedContributor.getName(), (ReactiveHealthIndicator) namedContributor.getContributor()); + } + } + return all; + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthAggregatorStatusAggregatorAdapterTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthAggregatorStatusAggregatorAdapterTests.java new file mode 100644 index 00000000000..6aa7be19c91 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthAggregatorStatusAggregatorAdapterTests.java @@ -0,0 +1,53 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.health; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.actuate.health.AbstractHealthAggregator; +import org.springframework.boot.actuate.health.Status; +import org.springframework.boot.actuate.health.StatusAggregator; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link HealthAggregatorStatusAggregatorAdapter}. + * + * @author Phillip Webb + */ +@SuppressWarnings("deprecation") +class HealthAggregatorStatusAggregatorAdapterTests { + + @Test + void getAggregateStatusDelegateToHealthAggregator() { + StatusAggregator adapter = new HealthAggregatorStatusAggregatorAdapter(new TestHealthAggregator()); + Status status = adapter.getAggregateStatus(Status.UP, Status.DOWN); + assertThat(status.getCode()).isEqualTo("called2"); + } + + private static class TestHealthAggregator extends AbstractHealthAggregator { + + @Override + protected Status aggregateStatus(List candidates) { + return new Status("called" + candidates.size()); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthContributorRegistryHealthIndicatorRegistryAdapterTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthContributorRegistryHealthIndicatorRegistryAdapterTests.java new file mode 100644 index 00000000000..9f93c231159 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthContributorRegistryHealthIndicatorRegistryAdapterTests.java @@ -0,0 +1,109 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.health; + +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.actuate.health.CompositeHealthContributor; +import org.springframework.boot.actuate.health.DefaultHealthContributorRegistry; +import org.springframework.boot.actuate.health.HealthContributorRegistry; +import org.springframework.boot.actuate.health.HealthIndicator; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.Assertions.entry; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link HealthContributorRegistryHealthIndicatorRegistryAdapter}. + * + * @author Phillip Webb + */ +class HealthContributorRegistryHealthIndicatorRegistryAdapterTests { + + private HealthContributorRegistry contributorRegistry = new DefaultHealthContributorRegistry(); + + private HealthContributorRegistryHealthIndicatorRegistryAdapter adapter = new HealthContributorRegistryHealthIndicatorRegistryAdapter( + this.contributorRegistry); + + @Test + void createWhenContributorRegistryIsNullThrowsException() { + assertThatIllegalArgumentException() + .isThrownBy(() -> new HealthContributorRegistryHealthIndicatorRegistryAdapter(null)) + .withMessage("ContributorRegistry must not be null"); + } + + @Test + void registerDelegatesToContributorRegistry() { + HealthIndicator healthIndicator = mock(HealthIndicator.class); + this.adapter.register("test", healthIndicator); + assertThat(this.contributorRegistry.getContributor("test")).isSameAs(healthIndicator); + } + + @Test + void unregisterDelegatesToContributorRegistry() { + HealthIndicator healthIndicator = mock(HealthIndicator.class); + this.contributorRegistry.registerContributor("test", healthIndicator); + HealthIndicator unregistered = this.adapter.unregister("test"); + assertThat(unregistered).isSameAs(healthIndicator); + assertThat(this.contributorRegistry.getContributor("test")).isNull(); + } + + @Test + void unregisterWhenContributorRegistryResultIsNotHealthIndicatorReturnsNull() { + CompositeHealthContributor healthContributor = mock(CompositeHealthContributor.class); + this.contributorRegistry.registerContributor("test", healthContributor); + HealthIndicator unregistered = this.adapter.unregister("test"); + assertThat(unregistered).isNull(); + assertThat(this.contributorRegistry.getContributor("test")).isNull(); + } + + @Test + void getDelegatesToContributorRegistry() { + HealthIndicator healthIndicator = mock(HealthIndicator.class); + this.contributorRegistry.registerContributor("test", healthIndicator); + assertThat(this.adapter.get("test")).isSameAs(healthIndicator); + } + + @Test + void getWhenContributorRegistryResultIsNotHealthIndicatorReturnsNull() { + CompositeHealthContributor healthContributor = mock(CompositeHealthContributor.class); + this.contributorRegistry.registerContributor("test", healthContributor); + assertThat(this.adapter.get("test")).isNull(); + } + + @Test + void getAllDelegatesToContributorRegistry() { + HealthIndicator healthIndicator = mock(HealthIndicator.class); + this.contributorRegistry.registerContributor("test", healthIndicator); + Map all = this.adapter.getAll(); + assertThat(all).containsOnly(entry("test", healthIndicator)); + } + + @Test + void getAllWhenContributorRegistryContainsNonHealthIndicatorInstancesReturnsFilteredMap() { + CompositeHealthContributor healthContributor = mock(CompositeHealthContributor.class); + this.contributorRegistry.registerContributor("test1", healthContributor); + HealthIndicator healthIndicator = mock(HealthIndicator.class); + this.contributorRegistry.registerContributor("test2", healthIndicator); + Map all = this.adapter.getAll(); + assertThat(all).containsOnly(entry("test2", healthIndicator)); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointAutoConfigurationTests.java index 5b7f83459bc..092d6cdee09 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthEndpointAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,9 @@ package org.springframework.boot.actuate.autoconfigure.health; import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; import org.junit.jupiter.api.Test; import reactor.core.publisher.Flux; @@ -25,20 +28,24 @@ import reactor.core.publisher.Mono; import org.springframework.boot.actuate.endpoint.SecurityContext; import org.springframework.boot.actuate.endpoint.http.ApiVersion; import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; +import org.springframework.boot.actuate.health.AbstractHealthAggregator; 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.HealthAggregator; import org.springframework.boot.actuate.health.HealthComponent; import org.springframework.boot.actuate.health.HealthContributorRegistry; import org.springframework.boot.actuate.health.HealthEndpoint; import org.springframework.boot.actuate.health.HealthEndpointGroups; import org.springframework.boot.actuate.health.HealthEndpointWebExtension; import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.boot.actuate.health.HealthStatusHttpMapper; 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.ReactiveHealthEndpointWebExtension; import org.springframework.boot.actuate.health.ReactiveHealthIndicator; +import org.springframework.boot.actuate.health.ReactiveHealthIndicatorRegistry; import org.springframework.boot.actuate.health.Status; import org.springframework.boot.actuate.health.StatusAggregator; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -59,6 +66,7 @@ import static org.mockito.Mockito.mock; * @author Andy Wilkinson * @author Stephane Nicoll */ +@SuppressWarnings("deprecation") class HealthEndpointAutoConfigurationTests { private WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() @@ -83,6 +91,22 @@ class HealthEndpointAutoConfigurationTests { }); } + @Test + void runWhenHasHealthAggregatorAdaptsToStatusAggregator() { + this.contextRunner.withUserConfiguration(HealthAggregatorConfiguration.class).run((context) -> { + StatusAggregator aggregator = context.getBean(StatusAggregator.class); + assertThat(aggregator.getAggregateStatus(Status.UP, Status.DOWN)).isEqualTo(Status.UNKNOWN); + }); + } + + @Test + void runWhenHasHealthStatusHttpMapperAdaptsToHttpCodeStatusMapper() { + this.contextRunner.withUserConfiguration(HealthStatusHttpMapperConfiguration.class).run((context) -> { + HttpCodeStatusMapper mapper = context.getBean(HttpCodeStatusMapper.class); + assertThat(mapper.getStatusCode(Status.UP)).isEqualTo(123); + }); + } + @Test void runCreatesStatusAggregatorFromProperties() { this.contextRunner.withPropertyValues("management.endpoint.health.status.order=up,down").run((context) -> { @@ -91,6 +115,14 @@ class HealthEndpointAutoConfigurationTests { }); } + @Test + void runWhenUsingDeprecatedPropertyCreatesStatusAggregatorFromProperties() { + this.contextRunner.withPropertyValues("management.health.status.order=up,down").run((context) -> { + StatusAggregator aggregator = context.getBean(StatusAggregator.class); + assertThat(aggregator.getAggregateStatus(Status.UP, Status.DOWN)).isEqualTo(Status.UP); + }); + } + @Test void runWhenHasStatusAggregatorBeanIgnoresProperties() { this.contextRunner.withUserConfiguration(StatusAggregatorConfiguration.class) @@ -109,6 +141,14 @@ class HealthEndpointAutoConfigurationTests { }); } + @Test + void runUsingDeprecatedPropertyCreatesHttpCodeStatusMapperFromProperties() { + this.contextRunner.withPropertyValues("management.health.status.http-mapping.up=123").run((context) -> { + HttpCodeStatusMapper mapper = context.getBean(HttpCodeStatusMapper.class); + assertThat(mapper.getStatusCode(Status.UP)).isEqualTo(123); + }); + } + @Test void runWhenHasHttpCodeStatusMapperBeanIgnoresProperties() { this.contextRunner.withUserConfiguration(HttpCodeStatusMapperConfiguration.class) @@ -246,6 +286,37 @@ class HealthEndpointAutoConfigurationTests { }); } + @Test // gh-18354 + void runCreatesLegacyHealthAggregator() { + this.contextRunner.run((context) -> { + HealthAggregator aggregator = context.getBean(HealthAggregator.class); + Map healths = new LinkedHashMap<>(); + healths.put("one", Health.up().build()); + healths.put("two", Health.down().build()); + Health result = aggregator.aggregate(healths); + assertThat(result.getStatus()).isEqualTo(Status.DOWN); + }); + } + + @Test // gh-18354 + void runCreatesLegacyHealthStatusHttpMapper() { + this.contextRunner.run((context) -> { + HealthStatusHttpMapper mapper = context.getBean(HealthStatusHttpMapper.class); + assertThat(mapper.mapStatus(Status.DOWN)).isEqualTo(503); + }); + } + + @Test + void runWhenReactorAvailableCreatesReactiveHealthIndicatorRegistryBean() { + this.contextRunner.run((context) -> assertThat(context).hasSingleBean(ReactiveHealthIndicatorRegistry.class)); + } + + @Test // gh-18570 + void runWhenReactorUnavailableDoesNotCreateReactiveHealthIndicatorRegistryBean() { + this.contextRunner.withClassLoader(new FilteredClassLoader(Mono.class.getPackage().getName())) + .run((context) -> assertThat(context).doesNotHaveBean(ReactiveHealthIndicatorRegistry.class)); + } + @Configuration(proxyBeanMethods = false) static class HealthIndicatorsConfiguration { @@ -266,6 +337,40 @@ class HealthEndpointAutoConfigurationTests { } + @Configuration(proxyBeanMethods = false) + static class HealthAggregatorConfiguration { + + @Bean + HealthAggregator healthAggregator() { + return new AbstractHealthAggregator() { + + @Override + protected Status aggregateStatus(List candidates) { + return Status.UNKNOWN; + } + + }; + } + + } + + @Configuration(proxyBeanMethods = false) + static class HealthStatusHttpMapperConfiguration { + + @Bean + HealthStatusHttpMapper healthStatusHttpMapper() { + return new HealthStatusHttpMapper() { + + @Override + public int mapStatus(Status status) { + return 123; + } + + }; + } + + } + @Configuration(proxyBeanMethods = false) static class StatusAggregatorConfiguration { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorRegistryInjectionIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorRegistryInjectionIntegrationTests.java new file mode 100644 index 00000000000..1b8d8f77c85 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorRegistryInjectionIntegrationTests.java @@ -0,0 +1,86 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.health; + +import io.micrometer.core.instrument.Gauge; +import io.micrometer.core.instrument.MeterRegistry; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; +import org.springframework.boot.actuate.health.CompositeHealthIndicator; +import org.springframework.boot.actuate.health.HealthAggregator; +import org.springframework.boot.actuate.health.HealthIndicatorRegistry; +import org.springframework.boot.actuate.health.Status; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration test to ensure that the legacy {@link HealthIndicatorRegistry} can still be + * injected. + * + * @author Phillip Webb + */ +@SuppressWarnings("deprecation") +@SpringBootTest(webEnvironment = WebEnvironment.NONE) +public class HealthIndicatorRegistryInjectionIntegrationTests { + + // gh-18194 + + @Test + void meterRegistryBeanHasBeenConfigured(@Autowired MeterRegistry meterRegistry) { + assertThat(meterRegistry).isNotNull(); + assertThat(meterRegistry.get("health").gauge()).isNotNull(); + } + + @Configuration + @ImportAutoConfiguration({ HealthEndpointAutoConfiguration.class, HealthContributorAutoConfiguration.class, + CompositeMeterRegistryAutoConfiguration.class, MetricsAutoConfiguration.class }) + static class Config { + + Config(HealthAggregator healthAggregator, HealthIndicatorRegistry healthIndicatorRegistry, + MeterRegistry registry) { + CompositeHealthIndicator healthIndicator = new CompositeHealthIndicator(healthAggregator, + healthIndicatorRegistry); + Gauge.builder("health", healthIndicator, this::getGuageValue) + .description("Spring boot health indicator. 3=UP, 2=OUT_OF_SERVICE, 1=DOWN, 0=UNKNOWN") + .strongReference(true).register(registry); + } + + private double getGuageValue(CompositeHealthIndicator health) { + Status status = health.health().getStatus(); + switch (status.getCode()) { + case "UP": + return 3; + case "OUT_OF_SERVICE": + return 2; + case "DOWN": + return 1; + case "UNKNOWN": + default: + return 0; + } + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthStatusHttpMapperHttpCodeStatusMapperAdapterTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthStatusHttpMapperHttpCodeStatusMapperAdapterTests.java new file mode 100644 index 00000000000..32bdeb8863a --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/HealthStatusHttpMapperHttpCodeStatusMapperAdapterTests.java @@ -0,0 +1,51 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.health; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.actuate.health.HealthStatusHttpMapper; +import org.springframework.boot.actuate.health.HttpCodeStatusMapper; +import org.springframework.boot.actuate.health.Status; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link HealthStatusHttpMapperHttpCodeStatusMapperAdapter}. + * + * @author Phillip Webb + */ +@SuppressWarnings("deprecation") +class HealthStatusHttpMapperHttpCodeStatusMapperAdapterTests { + + @Test + void getStatusCodeDelegatesToHealthStatusHttpMapper() { + HttpCodeStatusMapper adapter = new HealthStatusHttpMapperHttpCodeStatusMapperAdapter( + new TestHealthStatusHttpMapper()); + assertThat(adapter.getStatusCode(Status.UP)).isEqualTo(123); + } + + static class TestHealthStatusHttpMapper extends HealthStatusHttpMapper { + + @Override + public int mapStatus(Status status) { + return 123; + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/ReactiveHealthContributorRegistryReactiveHealthIndicatorRegistryAdapterTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/ReactiveHealthContributorRegistryReactiveHealthIndicatorRegistryAdapterTests.java new file mode 100644 index 00000000000..ef89e9b24a0 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/health/ReactiveHealthContributorRegistryReactiveHealthIndicatorRegistryAdapterTests.java @@ -0,0 +1,110 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.health; + +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.actuate.health.CompositeReactiveHealthContributor; +import org.springframework.boot.actuate.health.DefaultReactiveHealthContributorRegistry; +import org.springframework.boot.actuate.health.ReactiveHealthContributorRegistry; +import org.springframework.boot.actuate.health.ReactiveHealthIndicator; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.Assertions.entry; +import static org.mockito.Mockito.mock; + +/** + * Test for + * {@link ReactiveHealthContributorRegistryReactiveHealthIndicatorRegistryAdapter}. + * + * @author Phillip Webb + */ +class ReactiveHealthContributorRegistryReactiveHealthIndicatorRegistryAdapterTests { + + private ReactiveHealthContributorRegistry contributorRegistry = new DefaultReactiveHealthContributorRegistry(); + + private ReactiveHealthContributorRegistryReactiveHealthIndicatorRegistryAdapter adapter = new ReactiveHealthContributorRegistryReactiveHealthIndicatorRegistryAdapter( + this.contributorRegistry); + + @Test + void createWhenContributorRegistryIsNullThrowsException() { + assertThatIllegalArgumentException() + .isThrownBy(() -> new ReactiveHealthContributorRegistryReactiveHealthIndicatorRegistryAdapter(null)) + .withMessage("ContributorRegistry must not be null"); + } + + @Test + void registerDelegatesToContributorRegistry() { + ReactiveHealthIndicator healthIndicator = mock(ReactiveHealthIndicator.class); + this.adapter.register("test", healthIndicator); + assertThat(this.contributorRegistry.getContributor("test")).isSameAs(healthIndicator); + } + + @Test + void unregisterDelegatesToContributorRegistry() { + ReactiveHealthIndicator healthIndicator = mock(ReactiveHealthIndicator.class); + this.contributorRegistry.registerContributor("test", healthIndicator); + ReactiveHealthIndicator unregistered = this.adapter.unregister("test"); + assertThat(unregistered).isSameAs(healthIndicator); + assertThat(this.contributorRegistry.getContributor("test")).isNull(); + } + + @Test + void unregisterWhenContributorRegistryResultIsNotHealthIndicatorReturnsNull() { + CompositeReactiveHealthContributor healthContributor = mock(CompositeReactiveHealthContributor.class); + this.contributorRegistry.registerContributor("test", healthContributor); + ReactiveHealthIndicator unregistered = this.adapter.unregister("test"); + assertThat(unregistered).isNull(); + assertThat(this.contributorRegistry.getContributor("test")).isNull(); + } + + @Test + void getDelegatesToContributorRegistry() { + ReactiveHealthIndicator healthIndicator = mock(ReactiveHealthIndicator.class); + this.contributorRegistry.registerContributor("test", healthIndicator); + assertThat(this.adapter.get("test")).isSameAs(healthIndicator); + } + + @Test + void getWhenContributorRegistryResultIsNotHealthIndicatorReturnsNull() { + CompositeReactiveHealthContributor healthContributor = mock(CompositeReactiveHealthContributor.class); + this.contributorRegistry.registerContributor("test", healthContributor); + assertThat(this.adapter.get("test")).isNull(); + } + + @Test + void getAllDelegatesToContributorRegistry() { + ReactiveHealthIndicator healthIndicator = mock(ReactiveHealthIndicator.class); + this.contributorRegistry.registerContributor("test", healthIndicator); + Map all = this.adapter.getAll(); + assertThat(all).containsOnly(entry("test", healthIndicator)); + } + + @Test + void getAllWhenContributorRegistryContainsNonHealthIndicatorInstancesReturnsFilteredMap() { + CompositeReactiveHealthContributor healthContributor = mock(CompositeReactiveHealthContributor.class); + this.contributorRegistry.registerContributor("test1", healthContributor); + ReactiveHealthIndicator healthIndicator = mock(ReactiveHealthIndicator.class); + this.contributorRegistry.registerContributor("test2", healthIndicator); + Map all = this.adapter.getAll(); + assertThat(all).containsOnly(entry("test2", healthIndicator)); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/elasticsearch/ElasticsearchHealthIndicator.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/elasticsearch/ElasticsearchHealthIndicator.java new file mode 100644 index 00000000000..93dac44a3a6 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/elasticsearch/ElasticsearchHealthIndicator.java @@ -0,0 +1,100 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.elasticsearch; + +import java.util.List; + +import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest; +import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse; +import org.elasticsearch.client.Client; +import org.elasticsearch.client.Requests; + +import org.springframework.boot.actuate.health.AbstractHealthIndicator; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +/** + * {@link HealthIndicator} for an Elasticsearch cluster. + * + * @author Binwei Yang + * @author Andy Wilkinson + * @since 2.0.0 + * @deprecated since 2.2.0 as {@literal org.elasticsearch.client:transport} has been + * deprecated upstream + */ +@Deprecated +public class ElasticsearchHealthIndicator extends AbstractHealthIndicator { + + private static final String[] ALL_INDICES = { "_all" }; + + private final Client client; + + private final String[] indices; + + private final long responseTimeout; + + /** + * Create a new {@link ElasticsearchHealthIndicator} instance. + * @param client the Elasticsearch client + * @param responseTimeout the request timeout in milliseconds + * @param indices the indices to check + */ + public ElasticsearchHealthIndicator(Client client, long responseTimeout, List indices) { + this(client, responseTimeout, (indices != null) ? StringUtils.toStringArray(indices) : null); + } + + /** + * Create a new {@link ElasticsearchHealthIndicator} instance. + * @param client the Elasticsearch client + * @param responseTimeout the request timeout in milliseconds + * @param indices the indices to check + */ + public ElasticsearchHealthIndicator(Client client, long responseTimeout, String... indices) { + super("Elasticsearch health check failed"); + this.client = client; + this.responseTimeout = responseTimeout; + this.indices = indices; + } + + @Override + protected void doHealthCheck(Health.Builder builder) throws Exception { + ClusterHealthRequest request = Requests + .clusterHealthRequest(ObjectUtils.isEmpty(this.indices) ? ALL_INDICES : this.indices); + ClusterHealthResponse response = this.client.admin().cluster().health(request).actionGet(this.responseTimeout); + switch (response.getStatus()) { + case GREEN: + case YELLOW: + builder.up(); + break; + case RED: + default: + builder.down(); + break; + } + builder.withDetail("clusterName", response.getClusterName()); + builder.withDetail("numberOfNodes", response.getNumberOfNodes()); + builder.withDetail("numberOfDataNodes", response.getNumberOfDataNodes()); + builder.withDetail("activePrimaryShards", response.getActivePrimaryShards()); + builder.withDetail("activeShards", response.getActiveShards()); + builder.withDetail("relocatingShards", response.getRelocatingShards()); + builder.withDetail("initializingShards", response.getInitializingShards()); + builder.withDetail("unassignedShards", response.getUnassignedShards()); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/AbstractHealthAggregator.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/AbstractHealthAggregator.java new file mode 100644 index 00000000000..fbe099b4b81 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/AbstractHealthAggregator.java @@ -0,0 +1,63 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Base {@link HealthAggregator} implementation to allow subclasses to focus on + * aggregating the {@link Status} instances and not deal with contextual details etc. + * + * @author Christian Dupuis + * @author Vedran Pavic + * @since 1.1.0 + * @deprecated since 2.2.0 as {@link HealthAggregator} has been deprecated + */ +@Deprecated +public abstract class AbstractHealthAggregator implements HealthAggregator { + + @Override + public final Health aggregate(Map healths) { + List statusCandidates = healths.values().stream().map(Health::getStatus).collect(Collectors.toList()); + Status status = aggregateStatus(statusCandidates); + Map details = aggregateDetails(healths); + return new Health.Builder(status, details).build(); + } + + /** + * Return the single 'aggregate' status that should be used from the specified + * candidates. + * @param candidates the candidates + * @return a single status + */ + protected abstract Status aggregateStatus(List candidates); + + /** + * Return the map of 'aggregate' details that should be used from the specified + * healths. + * @param healths the health instances to aggregate + * @return a map of details + * @since 1.3.1 + */ + protected Map aggregateDetails(Map healths) { + return new LinkedHashMap<>(healths); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ApplicationHealthIndicator.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ApplicationHealthIndicator.java new file mode 100644 index 00000000000..242be3cb113 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ApplicationHealthIndicator.java @@ -0,0 +1,40 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +/** + * Default implementation of {@link HealthIndicator} that returns {@link Status#UP}. + * + * @author Dave Syer + * @author Christian Dupuis + * @since 1.2.0 + * @see Status#UP + * @deprecated since 2.2 in favor of {@link PingHealthIndicator}. + */ +@Deprecated +public class ApplicationHealthIndicator extends AbstractHealthIndicator { + + public ApplicationHealthIndicator() { + super("Application health check failed"); + } + + @Override + protected void doHealthCheck(Health.Builder builder) throws Exception { + builder.up(); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeHealthIndicator.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeHealthIndicator.java new file mode 100644 index 00000000000..6a40d818a44 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeHealthIndicator.java @@ -0,0 +1,77 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * {@link HealthIndicator} that returns health indications from all registered delegates. + * + * @author Tyler J. Frederick + * @author Phillip Webb + * @author Christian Dupuis + * @since 1.1.0 + * @deprecated since 2.2.0 in favor of a {@link CompositeHealthContributor} + */ +@Deprecated +public class CompositeHealthIndicator implements HealthIndicator { + + private final HealthIndicatorRegistry registry; + + private final HealthAggregator aggregator; + + /** + * Create a new {@link CompositeHealthIndicator} from the specified indicators. + * @param healthAggregator the health aggregator + * @param indicators a map of {@link HealthIndicator HealthIndicators} with the key + * being used as an indicator name. + */ + public CompositeHealthIndicator(HealthAggregator healthAggregator, Map indicators) { + this(healthAggregator, new DefaultHealthIndicatorRegistry(indicators)); + } + + /** + * Create a new {@link CompositeHealthIndicator} from the indicators in the given + * {@code registry}. + * @param healthAggregator the health aggregator + * @param registry the registry of {@link HealthIndicator HealthIndicators}. + */ + public CompositeHealthIndicator(HealthAggregator healthAggregator, HealthIndicatorRegistry registry) { + this.aggregator = healthAggregator; + this.registry = registry; + } + + /** + * Return the {@link HealthIndicatorRegistry} of this instance. + * @return the registry of nested {@link HealthIndicator health indicators} + * @since 2.1.0 + */ + public HealthIndicatorRegistry getRegistry() { + return this.registry; + } + + @Override + public Health health() { + Map healths = new LinkedHashMap<>(); + for (Map.Entry entry : this.registry.getAll().entrySet()) { + healths.put(entry.getKey(), entry.getValue().health()); + } + return this.aggregator.aggregate(healths); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeReactiveHealthIndicator.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeReactiveHealthIndicator.java new file mode 100644 index 00000000000..3b3d36a9164 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/CompositeReactiveHealthIndicator.java @@ -0,0 +1,89 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import java.time.Duration; +import java.util.function.Function; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.util.function.Tuple2; + +/** + * {@link ReactiveHealthIndicator} that returns health indications from all registered + * delegates. Provides an alternative {@link Health} for a delegate that reaches a + * configurable timeout. + * + * @author Stephane Nicoll + * @since 2.0.0 + * @deprecated since 2.2.0 in favor of a {@link CompositeReactiveHealthContributor} + */ +@Deprecated +public class CompositeReactiveHealthIndicator implements ReactiveHealthIndicator { + + private final ReactiveHealthIndicatorRegistry registry; + + private final HealthAggregator healthAggregator; + + private Long timeout; + + private Health timeoutHealth; + + private final Function, Mono> timeoutCompose; + + /** + * Create a new {@link CompositeReactiveHealthIndicator} from the indicators in the + * given {@code registry}. + * @param healthAggregator the health aggregator + * @param registry the registry of {@link ReactiveHealthIndicator HealthIndicators}. + */ + public CompositeReactiveHealthIndicator(HealthAggregator healthAggregator, + ReactiveHealthIndicatorRegistry registry) { + this.registry = registry; + this.healthAggregator = healthAggregator; + this.timeoutCompose = (mono) -> (this.timeout != null) + ? mono.timeout(Duration.ofMillis(this.timeout), Mono.just(this.timeoutHealth)) : mono; + } + + /** + * Specify an alternative timeout {@link Health} if a {@link HealthIndicator} failed + * to reply after specified {@code timeout}. + * @param timeout number of milliseconds to wait before using the + * {@code timeoutHealth} + * @param timeoutHealth the {@link Health} to use if an health indicator reached the + * {@code timeout} + * @return this instance + */ + public CompositeReactiveHealthIndicator timeoutStrategy(long timeout, Health timeoutHealth) { + this.timeout = timeout; + this.timeoutHealth = (timeoutHealth != null) ? timeoutHealth : Health.unknown().build(); + return this; + } + + ReactiveHealthIndicatorRegistry getRegistry() { + return this.registry; + } + + @Override + public Mono health() { + return Flux.fromIterable(this.registry.getAll().entrySet()) + .flatMap((entry) -> Mono.zip(Mono.just(entry.getKey()), + entry.getValue().health().transformDeferred(this.timeoutCompose))) + .collectMap(Tuple2::getT1, Tuple2::getT2).map(this.healthAggregator::aggregate); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/DefaultHealthIndicatorRegistry.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/DefaultHealthIndicatorRegistry.java new file mode 100644 index 00000000000..612b2a7b7bd --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/DefaultHealthIndicatorRegistry.java @@ -0,0 +1,92 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.springframework.util.Assert; + +/** + * Default implementation of {@link HealthIndicatorRegistry}. + * + * @author Vedran Pavic + * @author Stephane Nicoll + * @since 2.1.0 + * @deprecated since 2.2.0 in favor of {@link DefaultContributorRegistry} + */ +@Deprecated +public class DefaultHealthIndicatorRegistry implements HealthIndicatorRegistry { + + private final Object monitor = new Object(); + + private final Map healthIndicators; + + /** + * Create a new {@link DefaultHealthIndicatorRegistry}. + */ + public DefaultHealthIndicatorRegistry() { + this(new LinkedHashMap<>()); + } + + /** + * Create a new {@link DefaultHealthIndicatorRegistry} from the specified indicators. + * @param healthIndicators a map of {@link HealthIndicator}s with the key being used + * as an indicator name. + */ + public DefaultHealthIndicatorRegistry(Map healthIndicators) { + Assert.notNull(healthIndicators, "HealthIndicators must not be null"); + this.healthIndicators = new LinkedHashMap<>(healthIndicators); + } + + @Override + public void register(String name, HealthIndicator healthIndicator) { + Assert.notNull(healthIndicator, "HealthIndicator must not be null"); + Assert.notNull(name, "Name must not be null"); + synchronized (this.monitor) { + HealthIndicator existing = this.healthIndicators.putIfAbsent(name, healthIndicator); + if (existing != null) { + throw new IllegalStateException("HealthIndicator with name '" + name + "' already registered"); + } + } + } + + @Override + public HealthIndicator unregister(String name) { + Assert.notNull(name, "Name must not be null"); + synchronized (this.monitor) { + return this.healthIndicators.remove(name); + } + } + + @Override + public HealthIndicator get(String name) { + Assert.notNull(name, "Name must not be null"); + synchronized (this.monitor) { + return this.healthIndicators.get(name); + } + } + + @Override + public Map getAll() { + synchronized (this.monitor) { + return Collections.unmodifiableMap(new LinkedHashMap<>(this.healthIndicators)); + } + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/DefaultReactiveHealthIndicatorRegistry.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/DefaultReactiveHealthIndicatorRegistry.java new file mode 100644 index 00000000000..78c74d7c993 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/DefaultReactiveHealthIndicatorRegistry.java @@ -0,0 +1,93 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.springframework.util.Assert; + +/** + * Default implementation of {@link ReactiveHealthIndicatorRegistry}. + * + * @author Vedran Pavic + * @author Stephane Nicoll + * @since 2.1.0 + * @deprecated since 2.2.0 in favor of {@link DefaultContributorRegistry} + */ +@Deprecated +public class DefaultReactiveHealthIndicatorRegistry implements ReactiveHealthIndicatorRegistry { + + private final Object monitor = new Object(); + + private final Map healthIndicators; + + /** + * Create a new {@link DefaultReactiveHealthIndicatorRegistry}. + */ + public DefaultReactiveHealthIndicatorRegistry() { + this(new LinkedHashMap<>()); + } + + /** + * Create a new {@link DefaultReactiveHealthIndicatorRegistry} from the specified + * indicators. + * @param healthIndicators a map of {@link HealthIndicator}s with the key being used + * as an indicator name. + */ + public DefaultReactiveHealthIndicatorRegistry(Map healthIndicators) { + Assert.notNull(healthIndicators, "HealthIndicators must not be null"); + this.healthIndicators = new LinkedHashMap<>(healthIndicators); + } + + @Override + public void register(String name, ReactiveHealthIndicator healthIndicator) { + Assert.notNull(healthIndicator, "HealthIndicator must not be null"); + Assert.notNull(name, "Name must not be null"); + synchronized (this.monitor) { + ReactiveHealthIndicator existing = this.healthIndicators.putIfAbsent(name, healthIndicator); + if (existing != null) { + throw new IllegalStateException("HealthIndicator with name '" + name + "' already registered"); + } + } + } + + @Override + public ReactiveHealthIndicator unregister(String name) { + Assert.notNull(name, "Name must not be null"); + synchronized (this.monitor) { + return this.healthIndicators.remove(name); + } + } + + @Override + public ReactiveHealthIndicator get(String name) { + Assert.notNull(name, "Name must not be null"); + synchronized (this.monitor) { + return this.healthIndicators.get(name); + } + } + + @Override + public Map getAll() { + synchronized (this.monitor) { + return Collections.unmodifiableMap(new LinkedHashMap<>(this.healthIndicators)); + } + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthAggregator.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthAggregator.java new file mode 100644 index 00000000000..3049fe742ba --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthAggregator.java @@ -0,0 +1,48 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import java.util.Map; + +/** + * Strategy interface used to aggregate {@link Health} instances into a final one. + *

+ * This is especially useful to combine subsystem states expressed through + * {@link Health#getStatus()} into one state for the entire system. The default + * implementation {@link OrderedHealthAggregator} sorts {@link Status} instances based on + * a priority list. + *

+ * It is possible to add more complex {@link Status} types to the system. In that case + * either the {@link OrderedHealthAggregator} needs to be properly configured or users + * need to register a custom {@link HealthAggregator} as bean. + * + * @author Christian Dupuis + * @since 1.1.0 + * @deprecated since 2.2.0 in favor of {@link StatusAggregator} + */ +@FunctionalInterface +@Deprecated +public interface HealthAggregator { + + /** + * Aggregate several given {@link Health} instances into one. + * @param healths the health instances to aggregate + * @return the aggregated health + */ + Health aggregate(Map healths); + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpoint.java index c1c51d0d0b6..ce02cfabd90 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpoint.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,6 +40,17 @@ public class HealthEndpoint extends HealthEndpointSupport { private final HealthEndpointGroups groups; + /** + * Throw a new {@link IllegalStateException} to indicate a constructor has been + * deprecated. + * @deprecated since 2.2.0 in order to support deprecated subclass constructors + */ + @Deprecated + HealthEndpointSupport() { + throw new IllegalStateException("Unable to create " + getClass() + " using deprecated constructor"); + } + /** * Create a new {@link HealthEndpointSupport} instance. * @param registry the health contributor registry diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpointWebExtension.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpointWebExtension.java index 9a44309af16..034faf3a89f 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpointWebExtension.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthEndpointWebExtension.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,6 +45,17 @@ public class HealthEndpointWebExtension extends HealthEndpointSupport + * Implementations must be thread-safe. + * + * @author Andy Wilkinson + * @author Vedran Pavic + * @author Stephane Nicoll + * @since 2.1.0 + * @see HealthEndpoint + * @deprecated since 2.2.0 in favor of a {@link HealthContributorRegistry} + */ +@Deprecated +public interface HealthIndicatorRegistry { + + /** + * Registers the given {@link HealthIndicator}, associating it with the given + * {@code name}. + * @param name the name of the indicator + * @param healthIndicator the indicator + * @throws IllegalStateException if the indicator cannot be registered with the given + * {@code name}. + */ + void register(String name, HealthIndicator healthIndicator); + + /** + * Unregisters the {@link HealthIndicator} previously registered with the given + * {@code name}. + * @param name the name of the indicator + * @return the unregistered indicator, or {@code null} if no indicator was found in + * the registry for the given {@code name}. + */ + HealthIndicator unregister(String name); + + /** + * Returns the {@link HealthIndicator} registered with the given {@code name}. + * @param name the name of the indicator + * @return the health indicator, or {@code null} if no indicator was registered with + * the given {@code name}. + */ + HealthIndicator get(String name); + + /** + * Returns a snapshot of the registered health indicators and their names. The + * contents of the map do not reflect subsequent changes to the registry. + * @return the snapshot of registered health indicators + */ + Map getAll(); + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicatorRegistryFactory.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicatorRegistryFactory.java new file mode 100644 index 00000000000..5a34072e90c --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthIndicatorRegistryFactory.java @@ -0,0 +1,64 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import java.util.Map; +import java.util.function.Function; + +import org.springframework.util.Assert; + +/** + * Factory to create a {@link HealthIndicatorRegistry}. + * + * @author Stephane Nicoll + * @since 2.1.0 + * @deprecated since 2.2.0 in favor of {@link DefaultHealthIndicatorRegistry} + */ +@Deprecated +public class HealthIndicatorRegistryFactory { + + private final Function healthIndicatorNameFactory; + + public HealthIndicatorRegistryFactory(Function healthIndicatorNameFactory) { + this.healthIndicatorNameFactory = healthIndicatorNameFactory; + } + + public HealthIndicatorRegistryFactory() { + this(new HealthIndicatorNameFactory()); + } + + /** + * Create a {@link HealthIndicatorRegistry} based on the specified health indicators. + * @param healthIndicators the {@link HealthIndicator} instances mapped by name + * @return a {@link HealthIndicator} that delegates to the specified + * {@code healthIndicators}. + */ + public HealthIndicatorRegistry createHealthIndicatorRegistry(Map healthIndicators) { + Assert.notNull(healthIndicators, "HealthIndicators must not be null"); + return initialize(new DefaultHealthIndicatorRegistry(), healthIndicators); + } + + protected T initialize(T registry, + Map healthIndicators) { + for (Map.Entry entry : healthIndicators.entrySet()) { + String name = this.healthIndicatorNameFactory.apply(entry.getKey()); + registry.register(name, entry.getValue()); + } + return registry; + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthStatusHttpMapper.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthStatusHttpMapper.java new file mode 100644 index 00000000000..c38793ef9b4 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthStatusHttpMapper.java @@ -0,0 +1,127 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; +import org.springframework.util.Assert; + +/** + * Map a {@link Status} to an HTTP status code. + * + * @author Stephane Nicoll + * @since 2.0.0 + * @deprecated since 2.2.0 in favor of {@link HttpCodeStatusMapper} or + * {@link SimpleHttpCodeStatusMapper} + */ +@Deprecated +public class HealthStatusHttpMapper { + + private Map statusMapping = new HashMap<>(); + + /** + * Create a new instance. + */ + public HealthStatusHttpMapper() { + setupDefaultStatusMapping(); + } + + private void setupDefaultStatusMapping() { + addStatusMapping(Status.DOWN, WebEndpointResponse.STATUS_SERVICE_UNAVAILABLE); + addStatusMapping(Status.OUT_OF_SERVICE, WebEndpointResponse.STATUS_SERVICE_UNAVAILABLE); + } + + /** + * Set specific status mappings. + * @param statusMapping a map of health status code to HTTP status code + */ + public void setStatusMapping(Map statusMapping) { + Assert.notNull(statusMapping, "StatusMapping must not be null"); + this.statusMapping = new HashMap<>(statusMapping); + } + + /** + * Add specific status mappings to the existing set. + * @param statusMapping a map of health status code to HTTP status code + */ + public void addStatusMapping(Map statusMapping) { + Assert.notNull(statusMapping, "StatusMapping must not be null"); + this.statusMapping.putAll(statusMapping); + } + + /** + * Add a status mapping to the existing set. + * @param status the status to map + * @param httpStatus the http status + */ + public void addStatusMapping(Status status, Integer httpStatus) { + Assert.notNull(status, "Status must not be null"); + Assert.notNull(httpStatus, "HttpStatus must not be null"); + addStatusMapping(status.getCode(), httpStatus); + } + + /** + * Add a status mapping to the existing set. + * @param statusCode the status code to map + * @param httpStatus the http status + */ + public void addStatusMapping(String statusCode, Integer httpStatus) { + Assert.notNull(statusCode, "StatusCode must not be null"); + Assert.notNull(httpStatus, "HttpStatus must not be null"); + this.statusMapping.put(statusCode, httpStatus); + } + + /** + * Return an immutable view of the status mapping. + * @return the http status codes mapped by status name + */ + public Map getStatusMapping() { + return Collections.unmodifiableMap(this.statusMapping); + } + + /** + * Map the specified {@link Status} to an HTTP status code. + * @param status the health {@link Status} + * @return the corresponding HTTP status code + */ + public int mapStatus(Status status) { + String code = getUniformValue(status.getCode()); + if (code != null) { + return this.statusMapping.entrySet().stream() + .filter((entry) -> code.equals(getUniformValue(entry.getKey()))).map(Map.Entry::getValue) + .findFirst().orElse(WebEndpointResponse.STATUS_OK); + } + return WebEndpointResponse.STATUS_OK; + } + + private String getUniformValue(String code) { + if (code == null) { + return null; + } + StringBuilder builder = new StringBuilder(); + for (char ch : code.toCharArray()) { + if (Character.isAlphabetic(ch) || Character.isDigit(ch)) { + builder.append(Character.toLowerCase(ch)); + } + } + return builder.toString(); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthWebEndpointResponseMapper.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthWebEndpointResponseMapper.java new file mode 100644 index 00000000000..a6e8ec91d6d --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/HealthWebEndpointResponseMapper.java @@ -0,0 +1,140 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import java.security.Principal; +import java.util.Set; +import java.util.function.Supplier; + +import org.springframework.boot.actuate.endpoint.SecurityContext; +import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.util.ClassUtils; +import org.springframework.util.CollectionUtils; + +/** + * Maps a {@link Health} to a {@link WebEndpointResponse}. + * + * @author Andy Wilkinson + * @since 2.0.0 + * @deprecated since 2.2.0 in favor of {@link HealthEndpointWebExtension} or + * {@link ReactiveHealthEndpointWebExtension} + */ +@Deprecated +public class HealthWebEndpointResponseMapper { + + private final HealthStatusHttpMapper statusHttpMapper; + + private final ShowDetails showDetails; + + private final Set authorizedRoles; + + public HealthWebEndpointResponseMapper(HealthStatusHttpMapper statusHttpMapper, ShowDetails showDetails, + Set authorizedRoles) { + this.statusHttpMapper = statusHttpMapper; + this.showDetails = showDetails; + this.authorizedRoles = authorizedRoles; + } + + /** + * Maps the given {@code health} details to a {@link WebEndpointResponse}, honouring + * the mapper's default {@link ShowDetails} using the given {@code securityContext}. + *

+ * If the current user does not have the right to see the details, the + * {@link Supplier} is not invoked and a 404 response is returned instead. + * @param health the provider of health details, invoked if the current user has the + * right to see them + * @param securityContext the security context + * @return the mapped response + */ + public WebEndpointResponse mapDetails(Supplier health, SecurityContext securityContext) { + if (canSeeDetails(securityContext, this.showDetails)) { + Health healthDetails = health.get(); + if (healthDetails != null) { + return createWebEndpointResponse(healthDetails); + } + } + return new WebEndpointResponse<>(WebEndpointResponse.STATUS_NOT_FOUND); + } + + /** + * Maps the given {@code health} to a {@link WebEndpointResponse}, honouring the + * mapper's default {@link ShowDetails} using the given {@code securityContext}. + * @param health the health to map + * @param securityContext the security context + * @return the mapped response + */ + public WebEndpointResponse map(Health health, SecurityContext securityContext) { + return map(health, securityContext, this.showDetails); + } + + /** + * Maps the given {@code health} to a {@link WebEndpointResponse}, honouring the given + * {@code showDetails} using the given {@code securityContext}. + * @param health the health to map + * @param securityContext the security context + * @param showDetails when to show details in the response + * @return the mapped response + */ + public WebEndpointResponse map(Health health, SecurityContext securityContext, ShowDetails showDetails) { + if (!canSeeDetails(securityContext, showDetails)) { + health = Health.status(health.getStatus()).build(); + } + return createWebEndpointResponse(health); + } + + private WebEndpointResponse createWebEndpointResponse(Health health) { + int status = this.statusHttpMapper.mapStatus(health.getStatus()); + return new WebEndpointResponse<>(health, status); + } + + private boolean canSeeDetails(SecurityContext securityContext, ShowDetails showDetails) { + return showDetails != ShowDetails.NEVER && (showDetails != ShowDetails.WHEN_AUTHORIZED + || (securityContext.getPrincipal() != null && isUserInRole(securityContext))); + } + + private boolean isUserInRole(SecurityContext securityContext) { + if (CollectionUtils.isEmpty(this.authorizedRoles)) { + return true; + } + Principal principal = securityContext.getPrincipal(); + boolean checkAuthorities = isSpringSecurityAuthentication(principal); + for (String role : this.authorizedRoles) { + if (securityContext.isUserInRole(role)) { + return true; + } + if (checkAuthorities) { + Authentication authentication = (Authentication) principal; + for (GrantedAuthority authority : authentication.getAuthorities()) { + String name = authority.getAuthority(); + if (role.equals(name)) { + return true; + } + } + } + } + + return false; + } + + private boolean isSpringSecurityAuthentication(Principal principal) { + return ClassUtils.isPresent("org.springframework.security.core.Authentication", null) + && (principal instanceof Authentication); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/OrderedHealthAggregator.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/OrderedHealthAggregator.java new file mode 100644 index 00000000000..a3396f56b75 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/OrderedHealthAggregator.java @@ -0,0 +1,108 @@ +/* + * Copyright 2012-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + +import org.springframework.util.Assert; + +/** + * Default {@link HealthAggregator} implementation that aggregates {@link Health} + * instances and determines the final system state based on a simple ordered list. + *

+ * If a different order is required or a new {@link Status} type will be used, the order + * can be set by calling {@link #setStatusOrder(List)}. + * + * @author Christian Dupuis + * @since 1.1.0 + * @deprecated since 2.2.0 in favor of {@link SimpleStatusAggregator} + */ +@Deprecated +public class OrderedHealthAggregator extends AbstractHealthAggregator { + + private List statusOrder; + + /** + * Create a new {@link OrderedHealthAggregator} instance. + */ + public OrderedHealthAggregator() { + setStatusOrder(Status.DOWN, Status.OUT_OF_SERVICE, Status.UP, Status.UNKNOWN); + } + + /** + * Set the ordering of the status. + * @param statusOrder an ordered list of the status + */ + public void setStatusOrder(Status... statusOrder) { + String[] order = new String[statusOrder.length]; + for (int i = 0; i < statusOrder.length; i++) { + order[i] = statusOrder[i].getCode(); + } + setStatusOrder(Arrays.asList(order)); + } + + /** + * Set the ordering of the status. + * @param statusOrder an ordered list of the status codes + */ + public void setStatusOrder(List statusOrder) { + Assert.notNull(statusOrder, "StatusOrder must not be null"); + this.statusOrder = statusOrder; + } + + @Override + protected Status aggregateStatus(List candidates) { + // Only sort those status instances that we know about + List filteredCandidates = new ArrayList<>(); + for (Status candidate : candidates) { + if (this.statusOrder.contains(candidate.getCode())) { + filteredCandidates.add(candidate); + } + } + // If no status is given return UNKNOWN + if (filteredCandidates.isEmpty()) { + return Status.UNKNOWN; + } + // Sort given Status instances by configured order + filteredCandidates.sort(new StatusComparator(this.statusOrder)); + return filteredCandidates.get(0); + } + + /** + * {@link Comparator} used to order {@link Status}. + */ + private static class StatusComparator implements Comparator { + + private final List statusOrder; + + StatusComparator(List statusOrder) { + this.statusOrder = statusOrder; + } + + @Override + public int compare(Status s1, Status s2) { + int i1 = this.statusOrder.indexOf(s1.getCode()); + int i2 = this.statusOrder.indexOf(s2.getCode()); + return (i1 < i2) ? -1 : (i1 != i2) ? 1 : s1.getCode().compareTo(s2.getCode()); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ReactiveHealthContributor.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ReactiveHealthContributor.java index 89b4c3a148e..3bd7eb245a7 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ReactiveHealthContributor.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ReactiveHealthContributor.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,6 +31,7 @@ import org.springframework.util.Assert; */ public interface ReactiveHealthContributor { + @SuppressWarnings("deprecation") static ReactiveHealthContributor adapt(HealthContributor healthContributor) { Assert.notNull(healthContributor, "HealthContributor must not be null"); if (healthContributor instanceof HealthIndicator) { diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ReactiveHealthEndpointWebExtension.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ReactiveHealthEndpointWebExtension.java index f5547ad4ae1..a3d16e62c17 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ReactiveHealthEndpointWebExtension.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ReactiveHealthEndpointWebExtension.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,6 +45,18 @@ public class ReactiveHealthEndpointWebExtension private static final String[] NO_PATH = {}; + /** + * Create a new {@link ReactiveHealthEndpointWebExtension} instance. + * @param delegate the delegate health indicator + * @param responseMapper the response mapper + * @deprecated since 2.2.0 in favor of + * {@link #ReactiveHealthEndpointWebExtension(ReactiveHealthContributorRegistry, HealthEndpointGroups)} + */ + @Deprecated + public ReactiveHealthEndpointWebExtension(ReactiveHealthIndicator delegate, + HealthWebEndpointResponseMapper responseMapper) { + } + /** * Create a new {@link ReactiveHealthEndpointWebExtension} instance. * @param registry the health contributor registry diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ReactiveHealthIndicatorRegistry.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ReactiveHealthIndicatorRegistry.java new file mode 100644 index 00000000000..7d3f68e36c0 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ReactiveHealthIndicatorRegistry.java @@ -0,0 +1,70 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import java.util.Map; + +/** + * A registry of {@link ReactiveHealthIndicator ReactiveHealthIndicators}. + *

+ * Implementations must be thread-safe. + * + * @author Andy Wilkinson + * @author Vedran Pavic + * @author Stephane Nicoll + * @since 2.1.0 + * @see HealthIndicatorRegistry + * @deprecated since 2.2.0 in favor of a {@link ReactiveHealthContributorRegistry} + */ +@Deprecated +public interface ReactiveHealthIndicatorRegistry { + + /** + * Registers the given {@link ReactiveHealthIndicator}, associating it with the given + * {@code name}. + * @param name the name of the indicator + * @param healthIndicator the indicator + * @throws IllegalStateException if an indicator with the given {@code name} is + * already registered. + */ + void register(String name, ReactiveHealthIndicator healthIndicator); + + /** + * Unregisters the {@link ReactiveHealthIndicator} previously registered with the + * given {@code name}. + * @param name the name of the indicator + * @return the unregistered indicator, or {@code null} if no indicator was found in + * the registry for the given {@code name}. + */ + ReactiveHealthIndicator unregister(String name); + + /** + * Returns the {@link ReactiveHealthIndicator} registered with the given {@code name}. + * @param name the name of the indicator + * @return the health indicator, or {@code null} if no indicator was registered with + * the given {@code name}. + */ + ReactiveHealthIndicator get(String name); + + /** + * Returns a snapshot of the registered health indicators and their names. The + * contents of the map do not reflect subsequent changes to the registry. + * @return the snapshot of registered health indicators + */ + Map getAll(); + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ReactiveHealthIndicatorRegistryFactory.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ReactiveHealthIndicatorRegistryFactory.java new file mode 100644 index 00000000000..9896104a137 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ReactiveHealthIndicatorRegistryFactory.java @@ -0,0 +1,88 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.Function; + +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; + +/** + * Factory to create a {@link HealthIndicatorRegistry}. + * + * @author Stephane Nicoll + * @since 2.1.0 + * @deprecated since 2.2.0 in favor of {@link DefaultReactiveHealthIndicatorRegistry} + */ +@Deprecated +public class ReactiveHealthIndicatorRegistryFactory { + + private final Function healthIndicatorNameFactory; + + public ReactiveHealthIndicatorRegistryFactory(Function healthIndicatorNameFactory) { + this.healthIndicatorNameFactory = healthIndicatorNameFactory; + } + + public ReactiveHealthIndicatorRegistryFactory() { + this(new HealthIndicatorNameFactory()); + } + + /** + * Create a {@link ReactiveHealthIndicatorRegistry} based on the specified health + * indicators. Each {@link HealthIndicator} are wrapped to a + * {@link HealthIndicatorReactiveAdapter}. If two instances share the same name, the + * reactive variant takes precedence. + * @param reactiveHealthIndicators the {@link ReactiveHealthIndicator} instances + * mapped by name + * @param healthIndicators the {@link HealthIndicator} instances mapped by name if + * any. + * @return a {@link ReactiveHealthIndicator} that delegates to the specified + * {@code reactiveHealthIndicators}. + */ + public ReactiveHealthIndicatorRegistry createReactiveHealthIndicatorRegistry( + Map reactiveHealthIndicators, + Map healthIndicators) { + Assert.notNull(reactiveHealthIndicators, "ReactiveHealthIndicators must not be null"); + return initialize(new DefaultReactiveHealthIndicatorRegistry(), reactiveHealthIndicators, healthIndicators); + } + + protected T initialize(T registry, + Map reactiveHealthIndicators, + Map healthIndicators) { + merge(reactiveHealthIndicators, healthIndicators).forEach((beanName, indicator) -> { + String name = this.healthIndicatorNameFactory.apply(beanName); + registry.register(name, indicator); + }); + return registry; + } + + private Map merge(Map reactiveHealthIndicators, + Map healthIndicators) { + if (ObjectUtils.isEmpty(healthIndicators)) { + return reactiveHealthIndicators; + } + Map allIndicators = new LinkedHashMap<>(reactiveHealthIndicators); + healthIndicators.forEach((beanName, indicator) -> { + String name = this.healthIndicatorNameFactory.apply(beanName); + allIndicators.computeIfAbsent(name, (n) -> new HealthIndicatorReactiveAdapter(indicator)); + }); + return allIndicators; + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ShowDetails.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ShowDetails.java new file mode 100644 index 00000000000..30432741f5c --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/health/ShowDetails.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +/** + * Options for showing details in responses from the {@link HealthEndpoint} web + * extensions. + * + * @author Andy Wilkinson + * @since 2.0.0 + * @deprecated since 2.2.0 in favor of {@code HealthEndpointProperties.ShowDetails} + */ +@Deprecated +public enum ShowDetails { + + /** + * Never show details in the response. + */ + NEVER, + + /** + * Show details in the response when accessed by an authorized user. + */ + WHEN_AUTHORIZED, + + /** + * Always show details in the response. + */ + ALWAYS + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/elasticsearch/ElasticsearchHealthIndicatorTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/elasticsearch/ElasticsearchHealthIndicatorTests.java new file mode 100644 index 00000000000..7f49fa29bf2 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/elasticsearch/ElasticsearchHealthIndicatorTests.java @@ -0,0 +1,225 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.elasticsearch; + +import java.util.Map; + +import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.ElasticsearchTimeoutException; +import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest; +import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse; +import org.elasticsearch.action.support.PlainActionFuture; +import org.elasticsearch.client.AdminClient; +import org.elasticsearch.client.Client; +import org.elasticsearch.client.ClusterAdminClient; +import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.block.ClusterBlocks; +import org.elasticsearch.cluster.health.ClusterHealthStatus; +import org.elasticsearch.cluster.node.DiscoveryNodes; +import org.elasticsearch.cluster.routing.RoutingTable; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.Status; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; + +/** + * Test for {@link ElasticsearchHealthIndicator}. + * + * @author Andy Wilkinson + */ +@Deprecated +class ElasticsearchHealthIndicatorTests { + + @Mock + private Client client; + + @Mock + private AdminClient admin; + + @Mock + private ClusterAdminClient cluster; + + private ElasticsearchHealthIndicator indicator; + + @BeforeEach + void setUp() { + MockitoAnnotations.initMocks(this); + given(this.client.admin()).willReturn(this.admin); + given(this.admin.cluster()).willReturn(this.cluster); + this.indicator = new ElasticsearchHealthIndicator(this.client, 100L); + } + + @Test + void defaultConfigurationQueriesAllIndicesWith100msTimeout() { + TestActionFuture responseFuture = new TestActionFuture(); + responseFuture.onResponse(new StubClusterHealthResponse()); + ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(ClusterHealthRequest.class); + given(this.cluster.health(requestCaptor.capture())).willReturn(responseFuture); + Health health = this.indicator.health(); + assertThat(responseFuture.getTimeout).isEqualTo(100L); + assertThat(requestCaptor.getValue().indices()).contains("_all"); + assertThat(health.getStatus()).isEqualTo(Status.UP); + } + + @Test + void certainIndices() { + this.indicator = new ElasticsearchHealthIndicator(this.client, 100L, "test-index-1", "test-index-2"); + PlainActionFuture responseFuture = new PlainActionFuture<>(); + responseFuture.onResponse(new StubClusterHealthResponse()); + ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(ClusterHealthRequest.class); + given(this.cluster.health(requestCaptor.capture())).willReturn(responseFuture); + Health health = this.indicator.health(); + assertThat(requestCaptor.getValue().indices()).contains("test-index-1", "test-index-2"); + assertThat(health.getStatus()).isEqualTo(Status.UP); + } + + @Test + void customTimeout() { + this.indicator = new ElasticsearchHealthIndicator(this.client, 1000L); + TestActionFuture responseFuture = new TestActionFuture(); + responseFuture.onResponse(new StubClusterHealthResponse()); + ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(ClusterHealthRequest.class); + given(this.cluster.health(requestCaptor.capture())).willReturn(responseFuture); + this.indicator.health(); + assertThat(responseFuture.getTimeout).isEqualTo(1000L); + } + + @Test + void healthDetails() { + PlainActionFuture responseFuture = new PlainActionFuture<>(); + responseFuture.onResponse(new StubClusterHealthResponse()); + given(this.cluster.health(any(ClusterHealthRequest.class))).willReturn(responseFuture); + Health health = this.indicator.health(); + assertThat(health.getStatus()).isEqualTo(Status.UP); + Map details = health.getDetails(); + assertDetail(details, "clusterName", "test-cluster"); + assertDetail(details, "activeShards", 1); + assertDetail(details, "relocatingShards", 2); + assertDetail(details, "activePrimaryShards", 3); + assertDetail(details, "initializingShards", 4); + assertDetail(details, "unassignedShards", 5); + assertDetail(details, "numberOfNodes", 6); + assertDetail(details, "numberOfDataNodes", 7); + } + + @Test + void redResponseMapsToDown() { + PlainActionFuture responseFuture = new PlainActionFuture<>(); + responseFuture.onResponse(new StubClusterHealthResponse(ClusterHealthStatus.RED)); + given(this.cluster.health(any(ClusterHealthRequest.class))).willReturn(responseFuture); + assertThat(this.indicator.health().getStatus()).isEqualTo(Status.DOWN); + } + + @Test + void yellowResponseMapsToUp() { + PlainActionFuture responseFuture = new PlainActionFuture<>(); + responseFuture.onResponse(new StubClusterHealthResponse(ClusterHealthStatus.YELLOW)); + given(this.cluster.health(any(ClusterHealthRequest.class))).willReturn(responseFuture); + assertThat(this.indicator.health().getStatus()).isEqualTo(Status.UP); + } + + @Test + void responseTimeout() { + PlainActionFuture responseFuture = new PlainActionFuture<>(); + given(this.cluster.health(any(ClusterHealthRequest.class))).willReturn(responseFuture); + Health health = this.indicator.health(); + assertThat(health.getStatus()).isEqualTo(Status.DOWN); + assertThat((String) health.getDetails().get("error")).contains(ElasticsearchTimeoutException.class.getName()); + } + + @SuppressWarnings("unchecked") + private void assertDetail(Map details, String detail, T value) { + assertThat((T) details.get(detail)).isEqualTo(value); + } + + private static final class StubClusterHealthResponse extends ClusterHealthResponse { + + private final ClusterHealthStatus status; + + private StubClusterHealthResponse() { + this(ClusterHealthStatus.GREEN); + } + + private StubClusterHealthResponse(ClusterHealthStatus status) { + super("test-cluster", new String[0], new ClusterState(null, 0, null, null, RoutingTable.builder().build(), + DiscoveryNodes.builder().build(), ClusterBlocks.builder().build(), null, 1, false)); + this.status = status; + } + + @Override + public int getActiveShards() { + return 1; + } + + @Override + public int getRelocatingShards() { + return 2; + } + + @Override + public int getActivePrimaryShards() { + return 3; + } + + @Override + public int getInitializingShards() { + return 4; + } + + @Override + public int getUnassignedShards() { + return 5; + } + + @Override + public int getNumberOfNodes() { + return 6; + } + + @Override + public int getNumberOfDataNodes() { + return 7; + } + + @Override + public ClusterHealthStatus getStatus() { + return this.status; + } + + } + + static class TestActionFuture extends PlainActionFuture { + + private long getTimeout = -1L; + + @Override + public ClusterHealthResponse actionGet(long timeoutMillis) throws ElasticsearchException { + this.getTimeout = timeoutMillis; + return super.actionGet(timeoutMillis); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ApplicationHealthIndicatorTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ApplicationHealthIndicatorTests.java new file mode 100644 index 00000000000..eb257665e65 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ApplicationHealthIndicatorTests.java @@ -0,0 +1,37 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ApplicationHealthIndicator}. + * + * @author Phillip Webb + */ +@Deprecated +class ApplicationHealthIndicatorTests { + + @Test + void indicatesUp() { + ApplicationHealthIndicator healthIndicator = new ApplicationHealthIndicator(); + assertThat(healthIndicator.health().getStatus()).isEqualTo(Status.UP); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeHealthIndicatorTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeHealthIndicatorTests.java new file mode 100644 index 00000000000..9d9ea8572ea --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeHealthIndicatorTests.java @@ -0,0 +1,90 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; + +/** + * Tests for {@link CompositeHealthIndicator} + * + * @author Tyler J. Frederick + * @author Phillip Webb + * @author Christian Dupuis + */ +@Deprecated +class CompositeHealthIndicatorTests { + + private HealthAggregator healthAggregator; + + @Mock + private HealthIndicator one; + + @Mock + private HealthIndicator two; + + @BeforeEach + void setup() { + MockitoAnnotations.initMocks(this); + 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.healthAggregator = new OrderedHealthAggregator(); + } + + @Test + void createWithIndicators() { + Map indicators = new HashMap<>(); + indicators.put("one", this.one); + indicators.put("two", this.two); + CompositeHealthIndicator composite = new CompositeHealthIndicator(this.healthAggregator, indicators); + Health result = composite.health(); + assertThat(result.getDetails()).hasSize(2); + assertThat(result.getDetails()).containsEntry("one", + new Health.Builder().unknown().withDetail("1", "1").build()); + assertThat(result.getDetails()).containsEntry("two", + new Health.Builder().unknown().withDetail("2", "2").build()); + } + + @Test + void testSerialization() throws Exception { + Map indicators = new LinkedHashMap<>(); + indicators.put("db1", this.one); + indicators.put("db2", this.two); + CompositeHealthIndicator innerComposite = new CompositeHealthIndicator(this.healthAggregator, indicators); + CompositeHealthIndicator composite = new CompositeHealthIndicator(this.healthAggregator, + Collections.singletonMap("db", innerComposite)); + Health result = composite.health(); + ObjectMapper mapper = new ObjectMapper(); + assertThat(mapper.writeValueAsString(result)) + .isEqualTo("{\"status\":\"UNKNOWN\",\"details\":{\"db\":{\"status\":\"UNKNOWN\"" + + ",\"details\":{\"db1\":{\"status\":\"UNKNOWN\",\"details\"" + + ":{\"1\":\"1\"}},\"db2\":{\"status\":\"UNKNOWN\",\"details\":{\"2\":\"2\"}}}}}}"); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeReactiveHealthIndicatorTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeReactiveHealthIndicatorTests.java new file mode 100644 index 00000000000..f8818b560f2 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeReactiveHealthIndicatorTests.java @@ -0,0 +1,120 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import java.time.Duration; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link CompositeReactiveHealthIndicator}. + * + * @author Stephane Nicoll + */ +@Deprecated +class CompositeReactiveHealthIndicatorTests { + + private static final Health UNKNOWN_HEALTH = Health.unknown().withDetail("detail", "value").build(); + + private static final Health HEALTHY = Health.up().build(); + + private OrderedHealthAggregator healthAggregator = new OrderedHealthAggregator(); + + @Test + void singleIndicator() { + CompositeReactiveHealthIndicator indicator = new CompositeReactiveHealthIndicator(this.healthAggregator, + new DefaultReactiveHealthIndicatorRegistry(Collections.singletonMap("test", () -> Mono.just(HEALTHY)))); + StepVerifier.create(indicator.health()).consumeNextWith((h) -> { + assertThat(h.getStatus()).isEqualTo(Status.UP); + assertThat(h.getDetails()).containsOnlyKeys("test"); + assertThat(h.getDetails().get("test")).isEqualTo(HEALTHY); + }).verifyComplete(); + } + + @Test + void longHealth() { + Map indicators = new HashMap<>(); + for (int i = 0; i < 50; i++) { + indicators.put("test" + i, new TimeoutHealth(10000, Status.UP)); + } + CompositeReactiveHealthIndicator indicator = new CompositeReactiveHealthIndicator(this.healthAggregator, + new DefaultReactiveHealthIndicatorRegistry(indicators)); + StepVerifier.withVirtualTime(indicator::health).expectSubscription().thenAwait(Duration.ofMillis(10000)) + .consumeNextWith((h) -> { + assertThat(h.getStatus()).isEqualTo(Status.UP); + assertThat(h.getDetails()).hasSize(50); + }).verifyComplete(); + + } + + @Test + void timeoutReachedUsesFallback() { + Map indicators = new HashMap<>(); + indicators.put("slow", new TimeoutHealth(10000, Status.UP)); + indicators.put("fast", new TimeoutHealth(10, Status.UP)); + CompositeReactiveHealthIndicator indicator = new CompositeReactiveHealthIndicator(this.healthAggregator, + new DefaultReactiveHealthIndicatorRegistry(indicators)).timeoutStrategy(100, UNKNOWN_HEALTH); + StepVerifier.create(indicator.health()).consumeNextWith((h) -> { + assertThat(h.getStatus()).isEqualTo(Status.UP); + assertThat(h.getDetails()).containsOnlyKeys("slow", "fast"); + assertThat(h.getDetails().get("slow")).isEqualTo(UNKNOWN_HEALTH); + assertThat(h.getDetails().get("fast")).isEqualTo(HEALTHY); + }).verifyComplete(); + } + + @Test + void timeoutNotReached() { + Map indicators = new HashMap<>(); + indicators.put("slow", new TimeoutHealth(10000, Status.UP)); + indicators.put("fast", new TimeoutHealth(10, Status.UP)); + CompositeReactiveHealthIndicator indicator = new CompositeReactiveHealthIndicator(this.healthAggregator, + new DefaultReactiveHealthIndicatorRegistry(indicators)).timeoutStrategy(20000, null); + StepVerifier.withVirtualTime(indicator::health).expectSubscription().thenAwait(Duration.ofMillis(10000)) + .consumeNextWith((h) -> { + assertThat(h.getStatus()).isEqualTo(Status.UP); + assertThat(h.getDetails()).containsOnlyKeys("slow", "fast"); + assertThat(h.getDetails().get("slow")).isEqualTo(HEALTHY); + assertThat(h.getDetails().get("fast")).isEqualTo(HEALTHY); + }).verifyComplete(); + } + + static class TimeoutHealth implements ReactiveHealthIndicator { + + private final long timeout; + + private final Status status; + + TimeoutHealth(long timeout, Status status) { + this.timeout = timeout; + this.status = status; + } + + @Override + public Mono health() { + return Mono.delay(Duration.ofMillis(this.timeout)).map((l) -> Health.status(this.status).build()); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/DefaultHealthIndicatorRegistryTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/DefaultHealthIndicatorRegistryTests.java new file mode 100644 index 00000000000..53a68f89126 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/DefaultHealthIndicatorRegistryTests.java @@ -0,0 +1,103 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import java.util.Map; + +import 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.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link DefaultHealthIndicatorRegistry}. + * + * @author Vedran Pavic + * @author Stephane Nicoll + */ +@Deprecated +class DefaultHealthIndicatorRegistryTests { + + private HealthIndicator one = mock(HealthIndicator.class); + + private HealthIndicator two = mock(HealthIndicator.class); + + private DefaultHealthIndicatorRegistry 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 DefaultHealthIndicatorRegistry(); + } + + @Test + void register() { + this.registry.register("one", this.one); + this.registry.register("two", this.two); + assertThat(this.registry.getAll()).hasSize(2); + assertThat(this.registry.get("one")).isSameAs(this.one); + assertThat(this.registry.get("two")).isSameAs(this.two); + } + + @Test + void registerAlreadyUsedName() { + this.registry.register("one", this.one); + assertThatIllegalStateException().isThrownBy(() -> this.registry.register("one", this.two)) + .withMessageContaining("HealthIndicator with name 'one' already registered"); + } + + @Test + void unregister() { + this.registry.register("one", this.one); + this.registry.register("two", this.two); + assertThat(this.registry.getAll()).hasSize(2); + HealthIndicator two = this.registry.unregister("two"); + assertThat(two).isSameAs(this.two); + assertThat(this.registry.getAll()).hasSize(1); + } + + @Test + void unregisterUnknown() { + this.registry.register("one", this.one); + assertThat(this.registry.getAll()).hasSize(1); + HealthIndicator two = this.registry.unregister("two"); + assertThat(two).isNull(); + assertThat(this.registry.getAll()).hasSize(1); + } + + @Test + void getAllIsASnapshot() { + this.registry.register("one", this.one); + Map snapshot = this.registry.getAll(); + assertThat(snapshot).containsOnlyKeys("one"); + this.registry.register("two", this.two); + assertThat(snapshot).containsOnlyKeys("one"); + } + + @Test + void getAllIsImmutable() { + this.registry.register("one", this.one); + Map snapshot = this.registry.getAll(); + assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(snapshot::clear); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/DefaultReactiveHealthIndicatorRegistryTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/DefaultReactiveHealthIndicatorRegistryTests.java new file mode 100644 index 00000000000..117d8ffc6f8 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/DefaultReactiveHealthIndicatorRegistryTests.java @@ -0,0 +1,104 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import java.util.Map; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link DefaultReactiveHealthIndicatorRegistry}. + * + * @author Vedran Pavic + * @author Stephane Nicoll + */ +@Deprecated +class DefaultReactiveHealthIndicatorRegistryTests { + + private ReactiveHealthIndicator one = mock(ReactiveHealthIndicator.class); + + private ReactiveHealthIndicator two = mock(ReactiveHealthIndicator.class); + + private DefaultReactiveHealthIndicatorRegistry registry; + + @BeforeEach + void setUp() { + given(this.one.health()).willReturn(Mono.just(new Health.Builder().unknown().withDetail("1", "1").build())); + given(this.two.health()).willReturn(Mono.just(new Health.Builder().unknown().withDetail("2", "2").build())); + this.registry = new DefaultReactiveHealthIndicatorRegistry(); + } + + @Test + void register() { + this.registry.register("one", this.one); + this.registry.register("two", this.two); + assertThat(this.registry.getAll()).hasSize(2); + assertThat(this.registry.get("one")).isSameAs(this.one); + assertThat(this.registry.get("two")).isSameAs(this.two); + } + + @Test + void registerAlreadyUsedName() { + this.registry.register("one", this.one); + assertThatIllegalStateException().isThrownBy(() -> this.registry.register("one", this.two)) + .withMessageContaining("HealthIndicator with name 'one' already registered"); + } + + @Test + void unregister() { + this.registry.register("one", this.one); + this.registry.register("two", this.two); + assertThat(this.registry.getAll()).hasSize(2); + ReactiveHealthIndicator two = this.registry.unregister("two"); + assertThat(two).isSameAs(this.two); + assertThat(this.registry.getAll()).hasSize(1); + } + + @Test + void unregisterUnknown() { + this.registry.register("one", this.one); + assertThat(this.registry.getAll()).hasSize(1); + ReactiveHealthIndicator two = this.registry.unregister("two"); + assertThat(two).isNull(); + assertThat(this.registry.getAll()).hasSize(1); + } + + @Test + void getAllIsASnapshot() { + this.registry.register("one", this.one); + Map snapshot = this.registry.getAll(); + assertThat(snapshot).containsOnlyKeys("one"); + this.registry.register("two", this.two); + assertThat(snapshot).containsOnlyKeys("one"); + } + + @Test + void getAllIsImmutable() { + this.registry.register("one", this.one); + Map snapshot = this.registry.getAll(); + assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(snapshot::clear); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthEndpointTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthEndpointTests.java index c50722cd077..40f8578b628 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthEndpointTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthEndpointTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,7 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.actuate.health.HealthEndpointSupport.HealthResult; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.mockito.Mockito.mock; /** @@ -34,6 +35,15 @@ import static org.mockito.Mockito.mock; class HealthEndpointTests extends HealthEndpointSupportTests { + @Test + @SuppressWarnings("deprecation") + void createWhenUsingDeprecatedConstructorThrowsException() { + HealthIndicator healthIndicator = mock(HealthIndicator.class); + assertThatIllegalStateException().isThrownBy(() -> new HealthEndpoint(healthIndicator)) + .withMessage("Unable to create class org.springframework.boot.actuate.health.HealthEndpoint " + + "using deprecated constructor"); + } + @Test void healthReturnsSystemHealth() { this.registry.registerContributor("test", createContributor(this.up)); diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthEndpointWebExtensionTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthEndpointWebExtensionTests.java index 692e7fd8d28..b80241cbe66 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthEndpointWebExtensionTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthEndpointWebExtensionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +27,7 @@ import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; import org.springframework.boot.actuate.health.HealthEndpointSupport.HealthResult; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.mockito.Mockito.mock; /** @@ -37,6 +38,16 @@ import static org.mockito.Mockito.mock; class HealthEndpointWebExtensionTests extends HealthEndpointSupportTests { + @Test + @SuppressWarnings("deprecation") + void createWhenUsingDeprecatedConstructorThrowsException() { + HealthEndpoint delegate = mock(HealthEndpoint.class); + HealthWebEndpointResponseMapper responseMapper = mock(HealthWebEndpointResponseMapper.class); + assertThatIllegalStateException().isThrownBy(() -> new HealthEndpointWebExtension(delegate, responseMapper)) + .withMessage("Unable to create class org.springframework.boot.actuate." + + "health.HealthEndpointWebExtension using deprecated constructor"); + } + @Test void healthReturnsSystemHealth() { this.registry.registerContributor("test", createContributor(this.up)); diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthIndicatorReactiveAdapterTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthIndicatorReactiveAdapterTests.java index 1506c751f9b..494a9d6b700 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthIndicatorReactiveAdapterTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthIndicatorReactiveAdapterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,6 +29,7 @@ import static org.mockito.Mockito.mock; * * @author Stephane Nicoll */ +@SuppressWarnings("deprecation") class HealthIndicatorReactiveAdapterTests { @Test diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthWebEndpointResponseMapperTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthWebEndpointResponseMapperTests.java new file mode 100644 index 00000000000..aba93fa613c --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/HealthWebEndpointResponseMapperTests.java @@ -0,0 +1,155 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import java.security.Principal; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.function.Supplier; + +import org.junit.jupiter.api.Test; +import org.mockito.stubbing.Answer; + +import org.springframework.boot.actuate.endpoint.SecurityContext; +import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; +import org.springframework.http.HttpStatus; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.SimpleGrantedAuthority; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; + +/** + * Tests for {@link HealthWebEndpointResponseMapper}. + * + * @author Stephane Nicoll + */ +@Deprecated +class HealthWebEndpointResponseMapperTests { + + private final HealthStatusHttpMapper statusHttpMapper = new HealthStatusHttpMapper(); + + private Set authorizedRoles = Collections.singleton("ACTUATOR"); + + @Test + void mapDetailsWithDisableDetailsDoesNotInvokeSupplier() { + HealthWebEndpointResponseMapper mapper = createMapper(ShowDetails.NEVER); + Supplier supplier = mockSupplier(); + SecurityContext securityContext = mock(SecurityContext.class); + WebEndpointResponse response = mapper.mapDetails(supplier, securityContext); + assertThat(response.getStatus()).isEqualTo(HttpStatus.NOT_FOUND.value()); + verifyNoInteractions(supplier); + verifyNoInteractions(securityContext); + } + + @Test + void mapDetailsWithUnauthorizedUserDoesNotInvokeSupplier() { + HealthWebEndpointResponseMapper mapper = createMapper(ShowDetails.WHEN_AUTHORIZED); + Supplier supplier = mockSupplier(); + SecurityContext securityContext = mockSecurityContext("USER"); + WebEndpointResponse response = mapper.mapDetails(supplier, securityContext); + assertThat(response.getStatus()).isEqualTo(HttpStatus.NOT_FOUND.value()); + assertThat(response.getBody()).isNull(); + verifyNoInteractions(supplier); + verify(securityContext).isUserInRole("ACTUATOR"); + } + + @Test + void mapDetailsWithAuthorizedUserInvokesSupplier() { + HealthWebEndpointResponseMapper mapper = createMapper(ShowDetails.WHEN_AUTHORIZED); + Supplier supplier = mockSupplier(); + given(supplier.get()).willReturn(Health.down().build()); + SecurityContext securityContext = mockSecurityContext("ACTUATOR"); + WebEndpointResponse response = mapper.mapDetails(supplier, securityContext); + assertThat(response.getStatus()).isEqualTo(HttpStatus.SERVICE_UNAVAILABLE.value()); + assertThat(response.getBody().getStatus()).isEqualTo(Status.DOWN); + verify(supplier).get(); + verify(securityContext).isUserInRole("ACTUATOR"); + } + + @Test + void mapDetailsWithRightAuthoritiesInvokesSupplier() { + HealthWebEndpointResponseMapper mapper = createMapper(ShowDetails.WHEN_AUTHORIZED); + Supplier supplier = mockSupplier(); + given(supplier.get()).willReturn(Health.down().build()); + SecurityContext securityContext = getSecurityContext("ACTUATOR"); + WebEndpointResponse response = mapper.mapDetails(supplier, securityContext); + assertThat(response.getStatus()).isEqualTo(HttpStatus.SERVICE_UNAVAILABLE.value()); + assertThat(response.getBody().getStatus()).isEqualTo(Status.DOWN); + verify(supplier).get(); + } + + @Test + void mapDetailsWithOtherAuthoritiesShouldNotInvokeSupplier() { + HealthWebEndpointResponseMapper mapper = createMapper(ShowDetails.WHEN_AUTHORIZED); + Supplier supplier = mockSupplier(); + given(supplier.get()).willReturn(Health.down().build()); + SecurityContext securityContext = getSecurityContext("OTHER"); + WebEndpointResponse response = mapper.mapDetails(supplier, securityContext); + assertThat(response.getStatus()).isEqualTo(HttpStatus.NOT_FOUND.value()); + assertThat(response.getBody()).isNull(); + verifyNoInteractions(supplier); + } + + private SecurityContext getSecurityContext(String other) { + SecurityContext securityContext = mock(SecurityContext.class); + Authentication principal = mock(Authentication.class); + given(securityContext.getPrincipal()).willReturn(principal); + given(principal.getAuthorities()) + .willAnswer((invocation) -> Collections.singleton(new SimpleGrantedAuthority(other))); + return securityContext; + } + + @Test + void mapDetailsWithUnavailableHealth() { + HealthWebEndpointResponseMapper mapper = createMapper(ShowDetails.ALWAYS); + Supplier supplier = mockSupplier(); + SecurityContext securityContext = mock(SecurityContext.class); + WebEndpointResponse response = mapper.mapDetails(supplier, securityContext); + assertThat(response.getStatus()).isEqualTo(HttpStatus.NOT_FOUND.value()); + assertThat(response.getBody()).isNull(); + verify(supplier).get(); + verifyNoInteractions(securityContext); + } + + @SuppressWarnings("unchecked") + private Supplier mockSupplier() { + return mock(Supplier.class); + } + + private SecurityContext mockSecurityContext(String... roles) { + List associatedRoles = Arrays.asList(roles); + SecurityContext securityContext = mock(SecurityContext.class); + given(securityContext.getPrincipal()).willReturn(mock(Principal.class)); + given(securityContext.isUserInRole(anyString())).will((Answer) (invocation) -> { + String expectedRole = invocation.getArgument(0); + return associatedRoles.contains(expectedRole); + }); + return securityContext; + } + + private HealthWebEndpointResponseMapper createMapper(ShowDetails showDetails) { + return new HealthWebEndpointResponseMapper(this.statusHttpMapper, showDetails, this.authorizedRoles); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/OrderedHealthAggregatorTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/OrderedHealthAggregatorTests.java new file mode 100644 index 00000000000..d98eb675140 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/OrderedHealthAggregatorTests.java @@ -0,0 +1,87 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link OrderedHealthAggregator}. + * + * @author Christian Dupuis + */ +@Deprecated +class OrderedHealthAggregatorTests { + + private OrderedHealthAggregator healthAggregator; + + @BeforeEach + void setup() { + this.healthAggregator = new OrderedHealthAggregator(); + } + + @Test + void defaultOrder() { + Map healths = new HashMap<>(); + healths.put("h1", new Health.Builder().status(Status.DOWN).build()); + healths.put("h2", new Health.Builder().status(Status.UP).build()); + healths.put("h3", new Health.Builder().status(Status.UNKNOWN).build()); + healths.put("h4", new Health.Builder().status(Status.OUT_OF_SERVICE).build()); + assertThat(this.healthAggregator.aggregate(healths).getStatus()).isEqualTo(Status.DOWN); + } + + @Test + void customOrder() { + this.healthAggregator.setStatusOrder(Status.UNKNOWN, Status.UP, Status.OUT_OF_SERVICE, Status.DOWN); + Map healths = new HashMap<>(); + healths.put("h1", new Health.Builder().status(Status.DOWN).build()); + healths.put("h2", new Health.Builder().status(Status.UP).build()); + healths.put("h3", new Health.Builder().status(Status.UNKNOWN).build()); + healths.put("h4", new Health.Builder().status(Status.OUT_OF_SERVICE).build()); + assertThat(this.healthAggregator.aggregate(healths).getStatus()).isEqualTo(Status.UNKNOWN); + } + + @Test + void defaultOrderWithCustomStatus() { + Map healths = new HashMap<>(); + healths.put("h1", new Health.Builder().status(Status.DOWN).build()); + healths.put("h2", new Health.Builder().status(Status.UP).build()); + healths.put("h3", new Health.Builder().status(Status.UNKNOWN).build()); + healths.put("h4", new Health.Builder().status(Status.OUT_OF_SERVICE).build()); + healths.put("h5", new Health.Builder().status(new Status("CUSTOM")).build()); + assertThat(this.healthAggregator.aggregate(healths).getStatus()).isEqualTo(Status.DOWN); + } + + @Test + void customOrderWithCustomStatus() { + this.healthAggregator.setStatusOrder(Arrays.asList("DOWN", "OUT_OF_SERVICE", "UP", "UNKNOWN", "CUSTOM")); + Map healths = new HashMap<>(); + healths.put("h1", new Health.Builder().status(Status.DOWN).build()); + healths.put("h2", new Health.Builder().status(Status.UP).build()); + healths.put("h3", new Health.Builder().status(Status.UNKNOWN).build()); + healths.put("h4", new Health.Builder().status(Status.OUT_OF_SERVICE).build()); + healths.put("h5", new Health.Builder().status(new Status("CUSTOM")).build()); + assertThat(this.healthAggregator.aggregate(healths).getStatus()).isEqualTo(Status.DOWN); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ReactiveHealthContributorTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ReactiveHealthContributorTests.java index 201d79e0acb..8f89f860d70 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ReactiveHealthContributorTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ReactiveHealthContributorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,6 +39,7 @@ class ReactiveHealthContributorTests { } @Test + @SuppressWarnings("deprecation") void adaptWhenHealthIndicatorReturnsHealthIndicatorReactiveAdapter() { HealthIndicator indicator = () -> Health.outOfService().build(); ReactiveHealthContributor adapted = ReactiveHealthContributor.adapt(indicator); diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ReactiveHealthEndpointWebExtensionTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ReactiveHealthEndpointWebExtensionTests.java index 7d097eb1485..ce79a32e1fb 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ReactiveHealthEndpointWebExtensionTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ReactiveHealthEndpointWebExtensionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,6 +28,7 @@ import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; import org.springframework.boot.actuate.health.HealthEndpointSupport.HealthResult; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.mockito.Mockito.mock; /** @@ -38,6 +39,17 @@ import static org.mockito.Mockito.mock; class ReactiveHealthEndpointWebExtensionTests extends HealthEndpointSupportTests> { + @Test + @SuppressWarnings("deprecation") + void createWhenUsingDeprecatedConstructorThrowsException() { + ReactiveHealthIndicator delegate = mock(ReactiveHealthIndicator.class); + HealthWebEndpointResponseMapper responseMapper = mock(HealthWebEndpointResponseMapper.class); + assertThatIllegalStateException() + .isThrownBy(() -> new ReactiveHealthEndpointWebExtension(delegate, responseMapper)).withMessage( + "Unable to create class org.springframework.boot.actuate.health.ReactiveHealthEndpointWebExtension " + + "using deprecated constructor"); + } + @Test void healthReturnsSystemHealth() { this.registry.registerContributor("test", createContributor(this.up)); diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ReactiveHealthIndicatorRegistryFactoryTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ReactiveHealthIndicatorRegistryFactoryTests.java new file mode 100644 index 00000000000..5939865f9e0 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/ReactiveHealthIndicatorRegistryFactoryTests.java @@ -0,0 +1,59 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.health; + +import java.util.Collections; + +import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ReactiveHealthIndicatorRegistryFactory}. + * + * @author Stephane Nicoll + */ +@Deprecated +class ReactiveHealthIndicatorRegistryFactoryTests { + + private static final Health UP = new Health.Builder().status(Status.UP).build(); + + private static final Health DOWN = new Health.Builder().status(Status.DOWN).build(); + + private final ReactiveHealthIndicatorRegistryFactory factory = new ReactiveHealthIndicatorRegistryFactory(); + + @Test + void defaultHealthIndicatorNameFactory() { + ReactiveHealthIndicatorRegistry registry = this.factory.createReactiveHealthIndicatorRegistry( + Collections.singletonMap("myHealthIndicator", () -> Mono.just(UP)), null); + assertThat(registry.getAll()).containsOnlyKeys("my"); + } + + @Test + void healthIndicatorIsAdapted() { + ReactiveHealthIndicatorRegistry registry = this.factory.createReactiveHealthIndicatorRegistry( + Collections.singletonMap("test", () -> Mono.just(UP)), Collections.singletonMap("regular", () -> DOWN)); + assertThat(registry.getAll()).containsOnlyKeys("test", "regular"); + StepVerifier.create(registry.get("regular").health()).consumeNextWith((h) -> { + assertThat(h.getStatus()).isEqualTo(Status.DOWN); + assertThat(h.getDetails()).isEmpty(); + }).verifyComplete(); + } + +}