Skip to content

Commit

Permalink
Add HealthContributor and refactor HealthEndpoint
Browse files Browse the repository at this point in the history
Overhaul `HealthEndpoint` support to make it easier to support health
groups. Prior to this commit the `HealthIndicator` interface was used
for both regular indicators and composite indicators. In addition the
`Health` result was used to both represent individual, system and
composite health. This design unfortunately means that all health
contributors need to be aware of the `HealthAggregator` and could not
easily support heath groups if per-group aggregation is required.

This commit reworks many aspects of the health support in order to
provide a cleaner separation between a `HealthIndicator`and a
composite. The following changes have been made:

- A `HealthContributor` interface has been introduced to represent
  the general concept of something that contributes health information.
  A contributor can either be a `HealthIndicator` or a
  `CompositeHealthContributor`.

- A `HealthComponent` class has been introduced to mirror the
  contributor arrangement. The component can be either
  `CompositeHealth` or `Health`.

- The `HealthAggregator` interface has been replaced with a more
  focused `StatusAggregator` interface which only deals with `Status`
  results.

- `CompositeHealthIndicator` has been replaced with
  `CompositeHealthContributor` which only provides access to other
  contributors. A composite can no longer directly return `Health`.

- `HealthIndicatorRegistry` has been replaced with
  `HealthContributorRegistry` and the default implementation now
  uses a copy-on-write strategy.

- `HealthEndpoint`, `HealthEndpointWebExtension` and
  `ReactiveHealthEndpointWebExtension` now extend a common
  `HealthEndpointSupport` class. They are now driven by a
  health contributor registry and `HealthEndpointSettings`.

- The `HealthStatusHttpMapper` class has been replaced by a
  `HttpCodeStatusMapper` interface.

- The `HealthWebEndpointResponseMapper` class has been replaced
  by a `HealthEndpointSettings` strategy. This allows us to move
  role related logic and `ShowDetails` to the auto-configure module.

- `SimpleHttpCodeStatusMapper` and `SimpleStatusAggregator`
  implementations have been added which are configured via constructor
  arguments rather than setters.

- Endpoint auto-configuration has been reworked and the
  `CompositeHealthIndicatorConfiguration` class has been replaced
  by `CompositeHealthContributorConfiguration`.

- The endpoint JSON has been changed make `details` distinct from
  `components`.

See spring-projectsgh-17926
  • Loading branch information
philwebb committed Aug 21, 2019
1 parent 5f88c59 commit 2f0e104
Show file tree
Hide file tree
Showing 143 changed files with 5,267 additions and 1,301 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@
import reactor.core.publisher.Mono;

import org.springframework.boot.actuate.autoconfigure.cloudfoundry.EndpointCloudFoundryExtension;
import org.springframework.boot.actuate.endpoint.SecurityContext;
import org.springframework.boot.actuate.endpoint.annotation.EndpointExtension;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.Selector;
import org.springframework.boot.actuate.endpoint.annotation.Selector.Match;
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthComponent;
import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.health.ReactiveHealthEndpointWebExtension;
import org.springframework.boot.actuate.health.ShowDetails;

/**
* Reactive {@link EndpointExtension @EndpointExtension} for the {@link HealthEndpoint}
Expand All @@ -44,8 +46,14 @@ public CloudFoundryReactiveHealthEndpointWebExtension(ReactiveHealthEndpointWebE
}

@ReadOperation
public Mono<WebEndpointResponse<Health>> health() {
return this.delegate.health(null, ShowDetails.ALWAYS);
public Mono<WebEndpointResponse<? extends HealthComponent>> health() {
return this.delegate.health(SecurityContext.NONE, true);
}

@ReadOperation
public Mono<WebEndpointResponse<? extends HealthComponent>> health(
@Selector(match = Match.ALL_REMAINING) String... path) {
return this.delegate.health(SecurityContext.NONE, true, path);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@
package org.springframework.boot.actuate.autoconfigure.cloudfoundry.servlet;

import org.springframework.boot.actuate.autoconfigure.cloudfoundry.EndpointCloudFoundryExtension;
import org.springframework.boot.actuate.endpoint.SecurityContext;
import org.springframework.boot.actuate.endpoint.annotation.EndpointExtension;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.Selector;
import org.springframework.boot.actuate.endpoint.annotation.Selector.Match;
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthComponent;
import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.health.HealthEndpointWebExtension;
import org.springframework.boot.actuate.health.ShowDetails;

/**
* {@link EndpointExtension @EndpointExtension} for the {@link HealthEndpoint} that always
Expand All @@ -42,8 +44,13 @@ public CloudFoundryHealthEndpointWebExtension(HealthEndpointWebExtension delegat
}

@ReadOperation
public WebEndpointResponse<Health> getHealth() {
return this.delegate.getHealth(null, ShowDetails.ALWAYS);
public WebEndpointResponse<HealthComponent> health() {
return this.delegate.health(SecurityContext.NONE, true);
}

@ReadOperation
public WebEndpointResponse<HealthComponent> health(@Selector(match = Match.ALL_REMAINING) String... path) {
return this.delegate.health(SecurityContext.NONE, true, path);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* 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.lang.reflect.Constructor;
import java.util.Map;

import org.springframework.beans.BeanUtils;
import org.springframework.core.ResolvableType;
import org.springframework.util.Assert;

/**
* Base class for health contributor configurations that can combine source beans into a
* composite.
*
* @param <C> the contributor type
* @param <I> the health indicator type
* @param <B> the bean type
* @author Stephane Nicoll
* @author Phillip Webb
* @since 2.2.0
*/
public abstract class AbstractCompositeHealthContributorConfiguration<C, I extends C, B> {

private final Class<?> indicatorType;

private final Class<?> beanType;

AbstractCompositeHealthContributorConfiguration() {
ResolvableType type = ResolvableType.forClass(AbstractCompositeHealthContributorConfiguration.class,
getClass());
this.indicatorType = type.resolveGeneric(1);
this.beanType = type.resolveGeneric(2);

}

protected final C createContributor(Map<String, B> beans) {
Assert.notEmpty(beans, "Beans must not be empty");
if (beans.size() == 1) {
return createIndicator(beans.values().iterator().next());
}
return createComposite(beans);
}

protected abstract C createComposite(Map<String, B> beans);

@SuppressWarnings("unchecked")
protected I createIndicator(B bean) {
try {
Constructor<I> constructor = (Constructor<I>) this.indicatorType.getDeclaredConstructor(this.beanType);
return BeanUtils.instantiateClass(constructor, bean);
}
catch (Exception ex) {
throw new IllegalStateException(
"Unable to create health indicator " + this.indicatorType + " for bean type " + this.beanType, ex);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.boot.actuate.autoconfigure.health;

import java.util.Collection;

import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointProperties.ShowDetails;
import org.springframework.boot.actuate.endpoint.SecurityContext;
import org.springframework.boot.actuate.health.HealthEndpointSettings;
import org.springframework.boot.actuate.health.HttpCodeStatusMapper;
import org.springframework.boot.actuate.health.StatusAggregator;
import org.springframework.util.CollectionUtils;

/**
* Auto-configured {@link HealthEndpointSettings} backed by
* {@link HealthEndpointProperties}.
*
* @author Phillip Webb
* @author Andy Wilkinson
*/
class AutoConfiguredHealthEndpointSettings implements HealthEndpointSettings {

private final StatusAggregator statusAggregator;

private final HttpCodeStatusMapper httpCodeStatusMapper;

private final ShowDetails showDetails;

private final Collection<String> roles;

/**
* Create a new {@link AutoConfiguredHealthEndpointSettings} instance.
* @param statusAggregator the status aggregator to use
* @param httpCodeStatusMapper the HTTP code status mapper to use
* @param showDetails the show details setting
* @param roles the roles to match
*/
AutoConfiguredHealthEndpointSettings(StatusAggregator statusAggregator, HttpCodeStatusMapper httpCodeStatusMapper,
ShowDetails showDetails, Collection<String> roles) {
this.statusAggregator = statusAggregator;
this.httpCodeStatusMapper = httpCodeStatusMapper;
this.showDetails = showDetails;
this.roles = roles;
}

@Override
public boolean includeDetails(SecurityContext securityContext) {
ShowDetails showDetails = this.showDetails;
switch (showDetails) {
case NEVER:
return false;
case ALWAYS:
return true;
case WHEN_AUTHORIZED:
return isAuthorized(securityContext);
}
throw new IllegalStateException("Unsupported ShowDetails value " + showDetails);
}

private boolean isAuthorized(SecurityContext securityContext) {
if (securityContext.getPrincipal() == null) {
return false;
}
return CollectionUtils.isEmpty(this.roles) || this.roles.stream().anyMatch(securityContext::isUserInRole);
}

@Override
public StatusAggregator getStatusAggregator() {
return this.statusAggregator;
}

@Override
public HttpCodeStatusMapper getHttpCodeStatusMapper() {
return this.httpCodeStatusMapper;
}

}
Original file line number Diff line number Diff line change
@@ -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 java.util.Map;

import org.springframework.boot.actuate.health.CompositeHealthContributor;
import org.springframework.boot.actuate.health.HealthContributor;
import org.springframework.boot.actuate.health.HealthIndicator;

/**
* Base class for health contributor configurations that can combine source beans into a
* composite.
*
* @param <I> the health indicator type
* @param <B> the bean type
* @author Stephane Nicoll
* @author Phillip Webb
* @since 2.2.0
*/
public abstract class CompositeHealthContributorConfiguration<I extends HealthIndicator, B>
extends AbstractCompositeHealthContributorConfiguration<HealthContributor, I, B> {

@Override
protected final HealthContributor createComposite(Map<String, B> beans) {
return CompositeHealthContributor.fromMap(beans, this::createIndicator);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@
* @param <S> the bean source type
* @author Stephane Nicoll
* @since 2.0.0
* @deprecated since 2.0.0 in favor of {@link CompositeHealthContributorConfiguration}
*/
@Deprecated
public abstract class CompositeHealthIndicatorConfiguration<H extends HealthIndicator, S> {

@Autowired
Expand Down
Original file line number Diff line number Diff line change
@@ -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 java.util.Map;

import org.springframework.boot.actuate.health.CompositeReactiveHealthContributor;
import org.springframework.boot.actuate.health.ReactiveHealthContributor;
import org.springframework.boot.actuate.health.ReactiveHealthIndicator;

/**
* Base class for health contributor configurations that can combine source beans into a
* composite.
*
* @param <I> the health indicator type
* @param <B> the bean type
* @author Stephane Nicoll
* @author Phillip Webb
* @since 2.2.0
*/
public abstract class CompositeReactiveHealthContributorConfiguration<I extends ReactiveHealthIndicator, B>
extends AbstractCompositeHealthContributorConfiguration<ReactiveHealthContributor, I, B> {

@Override
protected final ReactiveHealthContributor createComposite(Map<String, B> beans) {
return CompositeReactiveHealthContributor.fromMap(beans, this::createIndicator);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@
* @param <S> 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<H extends ReactiveHealthIndicator, S> {

@Autowired
Expand Down
Loading

0 comments on commit 2f0e104

Please sign in to comment.