Skip to content

Commit

Permalink
Allow health groups to be configured at an additional path
Browse files Browse the repository at this point in the history
Closes gh-25471

Co-authored-by: Phillip Webb <pwebb@vmware.com>
  • Loading branch information
mbhave and philwebb committed Aug 12, 2021
1 parent fdde40e commit 49c86e6
Show file tree
Hide file tree
Showing 48 changed files with 1,659 additions and 160 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2021 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.
Expand Down Expand Up @@ -61,8 +61,9 @@ public ReadinessStateHealthIndicator readinessStateHealthIndicator(
}

@Bean
public AvailabilityProbesHealthEndpointGroupsPostProcessor availabilityProbesHealthEndpointGroupsPostProcessor() {
return new AvailabilityProbesHealthEndpointGroupsPostProcessor();
public AvailabilityProbesHealthEndpointGroupsPostProcessor availabilityProbesHealthEndpointGroupsPostProcessor(
Environment environment) {
return new AvailabilityProbesHealthEndpointGroupsPostProcessor(environment);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2021 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.
Expand All @@ -21,6 +21,7 @@
import java.util.Set;

import org.springframework.boot.actuate.endpoint.SecurityContext;
import org.springframework.boot.actuate.health.AdditionalHealthEndpointPath;
import org.springframework.boot.actuate.health.HealthEndpointGroup;
import org.springframework.boot.actuate.health.HttpCodeStatusMapper;
import org.springframework.boot.actuate.health.StatusAggregator;
Expand All @@ -35,8 +36,11 @@ class AvailabilityProbesHealthEndpointGroup implements HealthEndpointGroup {

private final Set<String> members;

AvailabilityProbesHealthEndpointGroup(String... members) {
private final AdditionalHealthEndpointPath additionalPath;

AvailabilityProbesHealthEndpointGroup(AdditionalHealthEndpointPath additionalPath, String... members) {
this.members = new HashSet<>(Arrays.asList(members));
this.additionalPath = additionalPath;
}

@Override
Expand Down Expand Up @@ -64,4 +68,9 @@ public HttpCodeStatusMapper getHttpCodeStatusMapper() {
return HttpCodeStatusMapper.DEFAULT;
}

@Override
public AdditionalHealthEndpointPath getAdditionalPath() {
return this.additionalPath;
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2021 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.
Expand All @@ -22,6 +22,8 @@
import java.util.Map;
import java.util.Set;

import org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
import org.springframework.boot.actuate.health.AdditionalHealthEndpointPath;
import org.springframework.boot.actuate.health.HealthEndpointGroup;
import org.springframework.boot.actuate.health.HealthEndpointGroups;
import org.springframework.util.Assert;
Expand All @@ -31,29 +33,39 @@
*
* @author Phillip Webb
* @author Brian Clozel
* @author Madhura Bhave
*/
class AvailabilityProbesHealthEndpointGroups implements HealthEndpointGroups {

private static final Map<String, AvailabilityProbesHealthEndpointGroup> GROUPS;
static {
Map<String, AvailabilityProbesHealthEndpointGroup> groups = new LinkedHashMap<>();
groups.put("liveness", new AvailabilityProbesHealthEndpointGroup("livenessState"));
groups.put("readiness", new AvailabilityProbesHealthEndpointGroup("readinessState"));
GROUPS = Collections.unmodifiableMap(groups);
}

private final HealthEndpointGroups groups;

private final Map<String, HealthEndpointGroup> probeGroups;

private final Set<String> names;

AvailabilityProbesHealthEndpointGroups(HealthEndpointGroups groups) {
AvailabilityProbesHealthEndpointGroups(HealthEndpointGroups groups, boolean addAdditionalPaths) {
Assert.notNull(groups, "Groups must not be null");
this.groups = groups;
this.probeGroups = createProbeGroups(addAdditionalPaths);
Set<String> names = new LinkedHashSet<>(groups.getNames());
names.addAll(GROUPS.keySet());
names.addAll(this.probeGroups.keySet());
this.names = Collections.unmodifiableSet(names);
}

private Map<String, HealthEndpointGroup> createProbeGroups(boolean addAdditionalPaths) {
Map<String, HealthEndpointGroup> probeGroups = new LinkedHashMap<>();
probeGroups.put("liveness", createProbeGroup(addAdditionalPaths, "/livez", "livenessState"));
probeGroups.put("readiness", createProbeGroup(addAdditionalPaths, "/readyz", "readinessState"));
return Collections.unmodifiableMap(probeGroups);
}

private AvailabilityProbesHealthEndpointGroup createProbeGroup(boolean addAdditionalPath, String path,
String members) {
AdditionalHealthEndpointPath additionalPath = (!addAdditionalPath) ? null
: AdditionalHealthEndpointPath.of(WebServerNamespace.SERVER, path);
return new AvailabilityProbesHealthEndpointGroup(additionalPath, members);
}

@Override
public HealthEndpointGroup getPrimary() {
return this.groups.getPrimary();
Expand All @@ -68,13 +80,14 @@ public Set<String> getNames() {
public HealthEndpointGroup get(String name) {
HealthEndpointGroup group = this.groups.get(name);
if (group == null) {
group = GROUPS.get(name);
group = this.probeGroups.get(name);
}
return group;
}

static boolean containsAllProbeGroups(HealthEndpointGroups groups) {
return groups.getNames().containsAll(GROUPS.keySet());
Set<String> names = groups.getNames();
return names.contains("liveness") && names.contains("readiness");
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2021 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.
Expand All @@ -20,22 +20,31 @@
import org.springframework.boot.actuate.health.HealthEndpointGroupsPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.Environment;

/**
* {@link HealthEndpointGroupsPostProcessor} to add
* {@link AvailabilityProbesHealthEndpointGroups}.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
@Order(Ordered.LOWEST_PRECEDENCE)
class AvailabilityProbesHealthEndpointGroupsPostProcessor implements HealthEndpointGroupsPostProcessor {

private final boolean addAdditionalPaths;

AvailabilityProbesHealthEndpointGroupsPostProcessor(Environment environment) {
this.addAdditionalPaths = "true"
.equalsIgnoreCase(environment.getProperty("management.endpoint.health.probes.add-additional-paths"));
}

@Override
public HealthEndpointGroups postProcessHealthEndpointGroups(HealthEndpointGroups groups) {
if (AvailabilityProbesHealthEndpointGroups.containsAllProbeGroups(groups)) {
return groups;
}
return new AvailabilityProbesHealthEndpointGroups(groups);
return new AvailabilityProbesHealthEndpointGroups(groups, this.addAdditionalPaths);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,13 @@ public CloudFoundryReactiveHealthEndpointWebExtension(ReactiveHealthEndpointWebE

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

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

}
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,13 @@ public CloudFoundryHealthEndpointWebExtension(HealthEndpointWebExtension delegat

@ReadOperation
public WebEndpointResponse<HealthComponent> health(ApiVersion apiVersion) {
return this.delegate.health(apiVersion, SecurityContext.NONE, true);
return this.delegate.health(apiVersion, null, SecurityContext.NONE, true);
}

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

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,21 @@

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.model.Resource;

import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
import org.springframework.boot.actuate.autoconfigure.web.jersey.ManagementContextResourceConfigCustomizer;
import org.springframework.boot.actuate.autoconfigure.web.server.ConditionalOnManagementPort;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementPortType;
import org.springframework.boot.actuate.endpoint.EndpointId;
import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver;
Expand All @@ -36,8 +41,12 @@
import org.springframework.boot.actuate.endpoint.web.ExposableServletEndpoint;
import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
import org.springframework.boot.actuate.endpoint.web.WebEndpointsSupplier;
import org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier;
import org.springframework.boot.actuate.endpoint.web.jersey.JerseyEndpointResourceFactory;
import org.springframework.boot.actuate.endpoint.web.jersey.JerseyHealthEndpointAdditionalPathResourceFactory;
import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.health.HealthEndpointGroups;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
Expand All @@ -64,6 +73,8 @@
@ConditionalOnMissingBean(type = "org.springframework.web.servlet.DispatcherServlet")
class JerseyWebEndpointManagementContextConfiguration {

private static final EndpointId HEALTH_ENDPOINT_ID = EndpointId.of("health");

@Bean
JerseyWebEndpointsResourcesRegistrar jerseyWebEndpointsResourcesRegistrar(Environment environment,
WebEndpointsSupplier webEndpointsSupplier, ServletEndpointsSupplier servletEndpointsSupplier,
Expand All @@ -74,6 +85,17 @@ JerseyWebEndpointsResourcesRegistrar jerseyWebEndpointsResourcesRegistrar(Enviro
endpointMediaTypes, basePath, shouldRegisterLinks);
}

@Bean
@ConditionalOnBean(HealthEndpoint.class)
@ConditionalOnManagementPort(ManagementPortType.DIFFERENT)
JerseyAdditionalHealthEndpointPathsManagementResourcesRegistrar jerseyDifferentPortAdditionalHealthEndpointPathsResourcesRegistrar(
WebEndpointsSupplier webEndpointsSupplier, HealthEndpointGroups healthEndpointGroups) {
Collection<ExposableWebEndpoint> webEndpoints = webEndpointsSupplier.getEndpoints();
ExposableWebEndpoint health = webEndpoints.stream()
.filter((endpoint) -> endpoint.getEndpointId().equals(HEALTH_ENDPOINT_ID)).findFirst().get();
return new JerseyAdditionalHealthEndpointPathsManagementResourcesRegistrar(health, healthEndpointGroups);
}

private boolean shouldRegisterLinksMapping(WebEndpointProperties properties, Environment environment,
String basePath) {
return properties.getDiscovery().isEnabled() && (StringUtils.hasText(basePath)
Expand Down Expand Up @@ -134,4 +156,38 @@ private void register(Collection<Resource> resources, ResourceConfig config) {

}

class JerseyAdditionalHealthEndpointPathsManagementResourcesRegistrar
implements ManagementContextResourceConfigCustomizer {

private final ExposableWebEndpoint endpoint;

private final HealthEndpointGroups groups;

JerseyAdditionalHealthEndpointPathsManagementResourcesRegistrar(ExposableWebEndpoint endpoint,
HealthEndpointGroups groups) {
this.endpoint = endpoint;
this.groups = groups;
}

@Override
public void customize(ResourceConfig config) {
register(config);
}

private void register(ResourceConfig config) {
EndpointMapping mapping = new EndpointMapping("");
JerseyHealthEndpointAdditionalPathResourceFactory resourceFactory = new JerseyHealthEndpointAdditionalPathResourceFactory(
WebServerNamespace.MANAGEMENT, this.groups);
Collection<Resource> endpointResources = resourceFactory
.createEndpointResources(mapping, Collections.singletonList(this.endpoint), null, null, false)
.stream().filter(Objects::nonNull).collect(Collectors.toList());
register(endpointResources, config);
}

private void register(Collection<Resource> resources, ResourceConfig config) {
config.registerResources(new HashSet<>(resources));
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.springframework.boot.actuate.autoconfigure.endpoint.web.CorsEndpointProperties;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
import org.springframework.boot.actuate.autoconfigure.web.server.ConditionalOnManagementPort;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementPortType;
import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
Expand All @@ -31,9 +32,13 @@
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
import org.springframework.boot.actuate.endpoint.web.WebEndpointsSupplier;
import org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpointsSupplier;
import org.springframework.boot.actuate.endpoint.web.reactive.AdditionalHealthEndpointPathsWebFluxHandlerMapping;
import org.springframework.boot.actuate.endpoint.web.reactive.ControllerEndpointHandlerMapping;
import org.springframework.boot.actuate.endpoint.web.reactive.WebFluxEndpointHandlerMapping;
import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.health.HealthEndpointGroups;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
Expand Down Expand Up @@ -84,6 +89,18 @@ private boolean shouldRegisterLinksMapping(WebEndpointProperties properties, Env
|| ManagementPortType.get(environment).equals(ManagementPortType.DIFFERENT));
}

@Bean
@ConditionalOnManagementPort(ManagementPortType.DIFFERENT)
@ConditionalOnBean(HealthEndpoint.class)
public AdditionalHealthEndpointPathsWebFluxHandlerMapping managementHealthEndpointWebFluxHandlerMapping(
WebEndpointsSupplier webEndpointsSupplier, HealthEndpointGroups groups) {
Collection<ExposableWebEndpoint> webEndpoints = webEndpointsSupplier.getEndpoints();
ExposableWebEndpoint health = webEndpoints.stream()
.filter((endpoint) -> endpoint.getEndpointId().equals(HealthEndpoint.ID)).findFirst().get();
return new AdditionalHealthEndpointPathsWebFluxHandlerMapping(new EndpointMapping(""), health,
groups.getAllWithAdditionalPath(WebServerNamespace.MANAGEMENT));
}

@Bean
@ConditionalOnMissingBean
public ControllerEndpointHandlerMapping controllerEndpointHandlerMapping(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.springframework.boot.actuate.autoconfigure.endpoint.web.CorsEndpointProperties;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
import org.springframework.boot.actuate.autoconfigure.web.server.ConditionalOnManagementPort;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementPortType;
import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
Expand All @@ -31,10 +32,14 @@
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
import org.springframework.boot.actuate.endpoint.web.WebEndpointsSupplier;
import org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpointsSupplier;
import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier;
import org.springframework.boot.actuate.endpoint.web.servlet.AdditionalHealthEndpointPathsWebMvcHandlerMapping;
import org.springframework.boot.actuate.endpoint.web.servlet.ControllerEndpointHandlerMapping;
import org.springframework.boot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping;
import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.health.HealthEndpointGroups;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
Expand Down Expand Up @@ -86,6 +91,18 @@ private boolean shouldRegisterLinksMapping(WebEndpointProperties webEndpointProp
|| ManagementPortType.get(environment).equals(ManagementPortType.DIFFERENT));
}

@Bean
@ConditionalOnManagementPort(ManagementPortType.DIFFERENT)
@ConditionalOnBean(HealthEndpoint.class)
public AdditionalHealthEndpointPathsWebMvcHandlerMapping managementHealthEndpointWebMvcHandlerMapping(
WebEndpointsSupplier webEndpointsSupplier, HealthEndpointGroups groups) {
Collection<ExposableWebEndpoint> webEndpoints = webEndpointsSupplier.getEndpoints();
ExposableWebEndpoint health = webEndpoints.stream()
.filter((endpoint) -> endpoint.getEndpointId().equals(HealthEndpoint.ID)).findFirst().get();
return new AdditionalHealthEndpointPathsWebMvcHandlerMapping(health,
groups.getAllWithAdditionalPath(WebServerNamespace.MANAGEMENT));
}

@Bean
@ConditionalOnMissingBean
public ControllerEndpointHandlerMapping controllerEndpointHandlerMapping(
Expand Down
Loading

0 comments on commit 49c86e6

Please sign in to comment.