Allow health groups to be configured at an additional path
Closes gh-25471 Co-authored-by: Phillip Webb <pwebb@vmware.com>
This commit is contained in:
parent
fdde40e4fb
commit
49c86e6e1b
|
@ -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.
|
||||
|
@ -61,8 +61,9 @@ public class AvailabilityProbesAutoConfiguration {
|
|||
}
|
||||
|
||||
@Bean
|
||||
public AvailabilityProbesHealthEndpointGroupsPostProcessor availabilityProbesHealthEndpointGroupsPostProcessor() {
|
||||
return new AvailabilityProbesHealthEndpointGroupsPostProcessor();
|
||||
public AvailabilityProbesHealthEndpointGroupsPostProcessor availabilityProbesHealthEndpointGroupsPostProcessor(
|
||||
Environment environment) {
|
||||
return new AvailabilityProbesHealthEndpointGroupsPostProcessor(environment);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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.
|
||||
|
@ -21,6 +21,7 @@ import java.util.HashSet;
|
|||
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;
|
||||
|
@ -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
|
||||
|
@ -64,4 +68,9 @@ class AvailabilityProbesHealthEndpointGroup implements HealthEndpointGroup {
|
|||
return HttpCodeStatusMapper.DEFAULT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdditionalHealthEndpointPath getAdditionalPath() {
|
||||
return this.additionalPath;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
@ -22,6 +22,8 @@ import java.util.LinkedHashSet;
|
|||
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;
|
||||
|
@ -31,29 +33,39 @@ import org.springframework.util.Assert;
|
|||
*
|
||||
* @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();
|
||||
|
@ -68,13 +80,14 @@ class AvailabilityProbesHealthEndpointGroups implements HealthEndpointGroups {
|
|||
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");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
@ -20,22 +20,31 @@ import org.springframework.boot.actuate.health.HealthEndpointGroups;
|
|||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -48,13 +48,13 @@ public class CloudFoundryReactiveHealthEndpointWebExtension {
|
|||
|
||||
@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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -46,13 +46,13 @@ public class CloudFoundryHealthEndpointWebExtension {
|
|||
|
||||
@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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -18,8 +18,11 @@ package org.springframework.boot.actuate.autoconfigure.endpoint.web.jersey;
|
|||
|
||||
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;
|
||||
|
@ -27,7 +30,9 @@ 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;
|
||||
|
@ -36,8 +41,12 @@ import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
|||
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;
|
||||
|
@ -64,6 +73,8 @@ import org.springframework.util.StringUtils;
|
|||
@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,
|
||||
|
@ -74,6 +85,17 @@ class JerseyWebEndpointManagementContextConfiguration {
|
|||
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)
|
||||
|
@ -134,4 +156,38 @@ class JerseyWebEndpointManagementContextConfiguration {
|
|||
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import java.util.List;
|
|||
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;
|
||||
|
@ -31,9 +32,13 @@ import org.springframework.boot.actuate.endpoint.web.EndpointMapping;
|
|||
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;
|
||||
|
@ -84,6 +89,18 @@ public class WebFluxEndpointManagementContextConfiguration {
|
|||
|| 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(
|
||||
|
|
|
@ -23,6 +23,7 @@ import java.util.List;
|
|||
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;
|
||||
|
@ -31,10 +32,14 @@ import org.springframework.boot.actuate.endpoint.web.EndpointMapping;
|
|||
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;
|
||||
|
@ -86,6 +91,18 @@ public class WebMvcEndpointManagementContextConfiguration {
|
|||
|| 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(
|
||||
|
|
|
@ -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.
|
||||
|
@ -22,6 +22,7 @@ import java.util.function.Predicate;
|
|||
|
||||
import org.springframework.boot.actuate.autoconfigure.health.HealthProperties.Show;
|
||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||
import org.springframework.boot.actuate.health.AdditionalHealthEndpointPath;
|
||||
import org.springframework.boot.actuate.health.HealthEndpointGroup;
|
||||
import org.springframework.boot.actuate.health.HttpCodeStatusMapper;
|
||||
import org.springframework.boot.actuate.health.StatusAggregator;
|
||||
|
@ -35,6 +36,7 @@ import org.springframework.util.CollectionUtils;
|
|||
*
|
||||
* @author Phillip Webb
|
||||
* @author Andy Wilkinson
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
class AutoConfiguredHealthEndpointGroup implements HealthEndpointGroup {
|
||||
|
||||
|
@ -50,6 +52,8 @@ class AutoConfiguredHealthEndpointGroup implements HealthEndpointGroup {
|
|||
|
||||
private final Collection<String> roles;
|
||||
|
||||
private final AdditionalHealthEndpointPath additionalPath;
|
||||
|
||||
/**
|
||||
* Create a new {@link AutoConfiguredHealthEndpointGroup} instance.
|
||||
* @param members a predicate used to test for group membership
|
||||
|
@ -58,16 +62,18 @@ class AutoConfiguredHealthEndpointGroup implements HealthEndpointGroup {
|
|||
* @param showComponents the show components setting
|
||||
* @param showDetails the show details setting
|
||||
* @param roles the roles to match
|
||||
* @param additionalPath the additional path to use for this group
|
||||
*/
|
||||
AutoConfiguredHealthEndpointGroup(Predicate<String> members, StatusAggregator statusAggregator,
|
||||
HttpCodeStatusMapper httpCodeStatusMapper, Show showComponents, Show showDetails,
|
||||
Collection<String> roles) {
|
||||
HttpCodeStatusMapper httpCodeStatusMapper, Show showComponents, Show showDetails, Collection<String> roles,
|
||||
AdditionalHealthEndpointPath additionalPath) {
|
||||
this.members = members;
|
||||
this.statusAggregator = statusAggregator;
|
||||
this.httpCodeStatusMapper = httpCodeStatusMapper;
|
||||
this.showComponents = showComponents;
|
||||
this.showDetails = showDetails;
|
||||
this.roles = roles;
|
||||
this.additionalPath = additionalPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -141,4 +147,9 @@ class AutoConfiguredHealthEndpointGroup implements HealthEndpointGroup {
|
|||
return this.httpCodeStatusMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdditionalHealthEndpointPath getAdditionalPath() {
|
||||
return this.additionalPath;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
@ -33,6 +33,7 @@ import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils;
|
|||
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointProperties.Group;
|
||||
import org.springframework.boot.actuate.autoconfigure.health.HealthProperties.Show;
|
||||
import org.springframework.boot.actuate.autoconfigure.health.HealthProperties.Status;
|
||||
import org.springframework.boot.actuate.health.AdditionalHealthEndpointPath;
|
||||
import org.springframework.boot.actuate.health.HealthEndpointGroup;
|
||||
import org.springframework.boot.actuate.health.HealthEndpointGroups;
|
||||
import org.springframework.boot.actuate.health.HttpCodeStatusMapper;
|
||||
|
@ -48,6 +49,7 @@ import org.springframework.util.ObjectUtils;
|
|||
* Auto-configured {@link HealthEndpointGroups}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
class AutoConfiguredHealthEndpointGroups implements HealthEndpointGroups {
|
||||
|
||||
|
@ -77,7 +79,7 @@ class AutoConfiguredHealthEndpointGroups implements HealthEndpointGroups {
|
|||
httpCodeStatusMapper = new SimpleHttpCodeStatusMapper(properties.getStatus().getHttpMapping());
|
||||
}
|
||||
this.primaryGroup = new AutoConfiguredHealthEndpointGroup(ALL, statusAggregator, httpCodeStatusMapper,
|
||||
showComponents, showDetails, roles);
|
||||
showComponents, showDetails, roles, null);
|
||||
this.groups = createGroups(properties.getGroup(), beanFactory, statusAggregator, httpCodeStatusMapper,
|
||||
showComponents, showDetails, roles);
|
||||
}
|
||||
|
@ -106,8 +108,10 @@ class AutoConfiguredHealthEndpointGroups implements HealthEndpointGroups {
|
|||
return defaultHttpCodeStatusMapper;
|
||||
});
|
||||
Predicate<String> members = new IncludeExcludeGroupMemberPredicate(group.getInclude(), group.getExclude());
|
||||
AdditionalHealthEndpointPath additionalPath = (group.getAdditionalPath() != null)
|
||||
? AdditionalHealthEndpointPath.from(group.getAdditionalPath()) : null;
|
||||
groups.put(groupName, new AutoConfiguredHealthEndpointGroup(members, statusAggregator, httpCodeStatusMapper,
|
||||
showComponents, showDetails, roles));
|
||||
showComponents, showDetails, roles, additionalPath));
|
||||
});
|
||||
return Collections.unmodifiableMap(groups);
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
@ -61,6 +61,10 @@ public class HealthEndpointProperties extends HealthProperties {
|
|||
*/
|
||||
public static class Group extends HealthProperties {
|
||||
|
||||
public static final String SERVER_PREFIX = "server:";
|
||||
|
||||
public static final String MANAGEMENT_PREFIX = "management:";
|
||||
|
||||
/**
|
||||
* Health indicator IDs that should be included or '*' for all.
|
||||
*/
|
||||
|
@ -77,6 +81,14 @@ public class HealthEndpointProperties extends HealthProperties {
|
|||
*/
|
||||
private Show showDetails;
|
||||
|
||||
/**
|
||||
* Additional path that this group can be made available on. The additional path
|
||||
* must start with a valid prefix, either `server` or `management` to indicate if
|
||||
* it will be available on the main port or the management port. For instance,
|
||||
* `server:/healthz` will configure the group on the main port at `/healthz`.
|
||||
*/
|
||||
private String additionalPath;
|
||||
|
||||
public Set<String> getInclude() {
|
||||
return this.include;
|
||||
}
|
||||
|
@ -102,6 +114,14 @@ public class HealthEndpointProperties extends HealthProperties {
|
|||
this.showDetails = showDetails;
|
||||
}
|
||||
|
||||
public String getAdditionalPath() {
|
||||
return this.additionalPath;
|
||||
}
|
||||
|
||||
public void setAdditionalPath(String additionalPath) {
|
||||
this.additionalPath = additionalPath;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2019 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.
|
||||
|
@ -16,6 +16,13 @@
|
|||
|
||||
package org.springframework.boot.actuate.autoconfigure.health;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMapping;
|
||||
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.reactive.AdditionalHealthEndpointPathsWebFluxHandlerMapping;
|
||||
import org.springframework.boot.actuate.health.HealthEndpoint;
|
||||
import org.springframework.boot.actuate.health.HealthEndpointGroups;
|
||||
import org.springframework.boot.actuate.health.ReactiveHealthContributorRegistry;
|
||||
|
@ -31,6 +38,7 @@ import org.springframework.context.annotation.Configuration;
|
|||
* Configuration for {@link HealthEndpoint} reactive web extensions.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Madhura Bhave
|
||||
* @see HealthEndpointAutoConfiguration
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
|
@ -46,4 +54,19 @@ class HealthEndpointReactiveWebExtensionConfiguration {
|
|||
return new ReactiveHealthEndpointWebExtension(reactiveHealthContributorRegistry, groups);
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class WebFluxAdditionalHealthEndpointPathsConfiguration {
|
||||
|
||||
@Bean
|
||||
AdditionalHealthEndpointPathsWebFluxHandlerMapping healthEndpointWebFluxHandlerMapping(
|
||||
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.SERVER));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2019 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.
|
||||
|
@ -16,21 +16,48 @@
|
|||
|
||||
package org.springframework.boot.actuate.autoconfigure.health;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.glassfish.jersey.server.ResourceConfig;
|
||||
import org.glassfish.jersey.server.model.Resource;
|
||||
import org.glassfish.jersey.servlet.ServletContainer;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMapping;
|
||||
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.jersey.JerseyHealthEndpointAdditionalPathResourceFactory;
|
||||
import org.springframework.boot.actuate.endpoint.web.servlet.AdditionalHealthEndpointPathsWebMvcHandlerMapping;
|
||||
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.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
|
||||
import org.springframework.boot.autoconfigure.jersey.JerseyProperties;
|
||||
import org.springframework.boot.autoconfigure.jersey.ResourceConfigCustomizer;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.DefaultJerseyApplicationPath;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.JerseyApplicationPath;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.web.servlet.ServletRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.DispatcherServlet;
|
||||
|
||||
/**
|
||||
* Configuration for {@link HealthEndpoint} web extensions.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Madhura Bhave
|
||||
* @see HealthEndpointAutoConfiguration
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
|
@ -46,4 +73,98 @@ class HealthEndpointWebExtensionConfiguration {
|
|||
return new HealthEndpointWebExtension(healthContributorRegistry, groups);
|
||||
}
|
||||
|
||||
private static ExposableWebEndpoint getHealthEndpoint(WebEndpointsSupplier webEndpointsSupplier) {
|
||||
Collection<ExposableWebEndpoint> webEndpoints = webEndpointsSupplier.getEndpoints();
|
||||
return webEndpoints.stream().filter((endpoint) -> endpoint.getEndpointId().equals(HealthEndpoint.ID))
|
||||
.findFirst().get();
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnBean(DispatcherServlet.class)
|
||||
static class MvcAdditionalHealthEndpointPathsConfiguration {
|
||||
|
||||
@Bean
|
||||
AdditionalHealthEndpointPathsWebMvcHandlerMapping healthEndpointWebMvcHandlerMapping(
|
||||
WebEndpointsSupplier webEndpointsSupplier, HealthEndpointGroups groups) {
|
||||
ExposableWebEndpoint health = getHealthEndpoint(webEndpointsSupplier);
|
||||
return new AdditionalHealthEndpointPathsWebMvcHandlerMapping(health,
|
||||
groups.getAllWithAdditionalPath(WebServerNamespace.SERVER));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(ResourceConfig.class)
|
||||
@ConditionalOnMissingClass("org.springframework.web.servlet.DispatcherServlet")
|
||||
static class JerseyAdditionalHealthEndpointPathsConfiguration {
|
||||
|
||||
@Bean
|
||||
JerseyAdditionalHealthEndpointPathsResourcesRegistrar jerseyAdditionalHealthEndpointPathsResourcesRegistrar(
|
||||
WebEndpointsSupplier webEndpointsSupplier, HealthEndpointGroups healthEndpointGroups) {
|
||||
ExposableWebEndpoint health = getHealthEndpoint(webEndpointsSupplier);
|
||||
return new JerseyAdditionalHealthEndpointPathsResourcesRegistrar(health, healthEndpointGroups);
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnMissingBean(ResourceConfig.class)
|
||||
@EnableConfigurationProperties(JerseyProperties.class)
|
||||
static class JerseyInfrastructureConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(JerseyApplicationPath.class)
|
||||
JerseyApplicationPath jerseyApplicationPath(JerseyProperties properties, ResourceConfig config) {
|
||||
return new DefaultJerseyApplicationPath(properties.getApplicationPath(), config);
|
||||
}
|
||||
|
||||
@Bean
|
||||
ResourceConfig resourceConfig(ObjectProvider<ResourceConfigCustomizer> resourceConfigCustomizers) {
|
||||
ResourceConfig resourceConfig = new ResourceConfig();
|
||||
resourceConfigCustomizers.orderedStream().forEach((customizer) -> customizer.customize(resourceConfig));
|
||||
return resourceConfig;
|
||||
}
|
||||
|
||||
@Bean
|
||||
ServletRegistrationBean<ServletContainer> jerseyServletRegistration(
|
||||
JerseyApplicationPath jerseyApplicationPath, ResourceConfig resourceConfig) {
|
||||
return new ServletRegistrationBean<>(new ServletContainer(resourceConfig),
|
||||
jerseyApplicationPath.getUrlMapping());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class JerseyAdditionalHealthEndpointPathsResourcesRegistrar implements ResourceConfigCustomizer {
|
||||
|
||||
private final ExposableWebEndpoint endpoint;
|
||||
|
||||
private final HealthEndpointGroups groups;
|
||||
|
||||
JerseyAdditionalHealthEndpointPathsResourcesRegistrar(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.SERVER, 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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
@ -32,7 +32,7 @@ import static org.mockito.Mockito.mock;
|
|||
*/
|
||||
class AvailabilityProbesHealthEndpointGroupTests {
|
||||
|
||||
private AvailabilityProbesHealthEndpointGroup group = new AvailabilityProbesHealthEndpointGroup("a", "b");
|
||||
private AvailabilityProbesHealthEndpointGroup group = new AvailabilityProbesHealthEndpointGroup(null, "a", "b");
|
||||
|
||||
@Test
|
||||
void isMemberWhenMemberReturnsTrue() {
|
||||
|
|
|
@ -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.
|
||||
|
@ -21,7 +21,9 @@ import java.util.Set;
|
|||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.actuate.health.HealthEndpointGroup;
|
||||
import org.springframework.boot.actuate.health.HealthEndpointGroups;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
|
@ -31,10 +33,12 @@ import static org.mockito.Mockito.mock;
|
|||
* Tests for {@link AvailabilityProbesHealthEndpointGroupsPostProcessor}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
class AvailabilityProbesHealthEndpointGroupsPostProcessorTests {
|
||||
|
||||
private AvailabilityProbesHealthEndpointGroupsPostProcessor postProcessor = new AvailabilityProbesHealthEndpointGroupsPostProcessor();
|
||||
private AvailabilityProbesHealthEndpointGroupsPostProcessor postProcessor = new AvailabilityProbesHealthEndpointGroupsPostProcessor(
|
||||
new MockEnvironment());
|
||||
|
||||
@Test
|
||||
void postProcessHealthEndpointGroupsWhenGroupsAlreadyContainedReturnsOriginal() {
|
||||
|
@ -68,7 +72,43 @@ class AvailabilityProbesHealthEndpointGroupsPostProcessorTests {
|
|||
given(groups.getNames()).willReturn(names);
|
||||
assertThat(this.postProcessor.postProcessHealthEndpointGroups(groups))
|
||||
.isInstanceOf(AvailabilityProbesHealthEndpointGroups.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void postProcessHealthEndpointGroupsWhenAdditionalPathPropertyIsTrue() {
|
||||
HealthEndpointGroups postProcessed = getPostProcessed("true");
|
||||
HealthEndpointGroup liveness = postProcessed.get("liveness");
|
||||
HealthEndpointGroup readiness = postProcessed.get("readiness");
|
||||
assertThat(liveness.getAdditionalPath().toString()).isEqualTo("server:/livez");
|
||||
assertThat(readiness.getAdditionalPath().toString()).isEqualTo("server:/readyz");
|
||||
}
|
||||
|
||||
private HealthEndpointGroups getPostProcessed(String value) {
|
||||
MockEnvironment environment = new MockEnvironment();
|
||||
environment.setProperty("management.endpoint.health.probes.add-additional-paths", value);
|
||||
AvailabilityProbesHealthEndpointGroupsPostProcessor postProcessor = new AvailabilityProbesHealthEndpointGroupsPostProcessor(
|
||||
environment);
|
||||
HealthEndpointGroups groups = mock(HealthEndpointGroups.class);
|
||||
return postProcessor.postProcessHealthEndpointGroups(groups);
|
||||
}
|
||||
|
||||
@Test
|
||||
void postProcessHealthEndpointGroupsWhenAdditionalPathPropertyIsFalse() {
|
||||
HealthEndpointGroups postProcessed = getPostProcessed("false");
|
||||
HealthEndpointGroup liveness = postProcessed.get("liveness");
|
||||
HealthEndpointGroup readiness = postProcessed.get("readiness");
|
||||
assertThat(liveness.getAdditionalPath()).isNull();
|
||||
assertThat(readiness.getAdditionalPath()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void postProcessHealthEndpointGroupsWhenAdditionalPathPropertyIsNull() {
|
||||
HealthEndpointGroups groups = mock(HealthEndpointGroups.class);
|
||||
HealthEndpointGroups postProcessed = this.postProcessor.postProcessHealthEndpointGroups(groups);
|
||||
HealthEndpointGroup liveness = postProcessed.get("liveness");
|
||||
HealthEndpointGroup readiness = postProcessed.get("readiness");
|
||||
assertThat(liveness.getAdditionalPath()).isNull();
|
||||
assertThat(readiness.getAdditionalPath()).isNull();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
@ -50,46 +50,46 @@ class AvailabilityProbesHealthEndpointGroupsTests {
|
|||
|
||||
@Test
|
||||
void createWhenGroupsIsNullThrowsException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new AvailabilityProbesHealthEndpointGroups(null))
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new AvailabilityProbesHealthEndpointGroups(null, false))
|
||||
.withMessage("Groups must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getPrimaryDelegatesToGroups() {
|
||||
given(this.delegate.getPrimary()).willReturn(this.group);
|
||||
HealthEndpointGroups availabilityProbes = new AvailabilityProbesHealthEndpointGroups(this.delegate);
|
||||
HealthEndpointGroups availabilityProbes = new AvailabilityProbesHealthEndpointGroups(this.delegate, false);
|
||||
assertThat(availabilityProbes.getPrimary()).isEqualTo(this.group);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getNamesIncludesAvailabilityProbeGroups() {
|
||||
given(this.delegate.getNames()).willReturn(Collections.singleton("test"));
|
||||
HealthEndpointGroups availabilityProbes = new AvailabilityProbesHealthEndpointGroups(this.delegate);
|
||||
HealthEndpointGroups availabilityProbes = new AvailabilityProbesHealthEndpointGroups(this.delegate, false);
|
||||
assertThat(availabilityProbes.getNames()).containsExactly("test", "liveness", "readiness");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWhenProbeInDelegateReturnsGroupFromDelegate() {
|
||||
given(this.delegate.get("liveness")).willReturn(this.group);
|
||||
HealthEndpointGroups availabilityProbes = new AvailabilityProbesHealthEndpointGroups(this.delegate);
|
||||
HealthEndpointGroups availabilityProbes = new AvailabilityProbesHealthEndpointGroups(this.delegate, false);
|
||||
assertThat(availabilityProbes.get("liveness")).isEqualTo(this.group);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWhenProbeNotInDelegateReturnsProbeGroup() {
|
||||
HealthEndpointGroups availabilityProbes = new AvailabilityProbesHealthEndpointGroups(this.delegate);
|
||||
HealthEndpointGroups availabilityProbes = new AvailabilityProbesHealthEndpointGroups(this.delegate, false);
|
||||
assertThat(availabilityProbes.get("liveness")).isInstanceOf(AvailabilityProbesHealthEndpointGroup.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWhenNotProbeAndNotInDelegateReturnsNull() {
|
||||
HealthEndpointGroups availabilityProbes = new AvailabilityProbesHealthEndpointGroups(this.delegate);
|
||||
HealthEndpointGroups availabilityProbes = new AvailabilityProbesHealthEndpointGroups(this.delegate, false);
|
||||
assertThat(availabilityProbes.get("mygroup")).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getLivenessProbeHasOnlyLivenessStateAsMember() {
|
||||
HealthEndpointGroups availabilityProbes = new AvailabilityProbesHealthEndpointGroups(this.delegate);
|
||||
HealthEndpointGroups availabilityProbes = new AvailabilityProbesHealthEndpointGroups(this.delegate, false);
|
||||
HealthEndpointGroup probeGroup = availabilityProbes.get("liveness");
|
||||
assertThat(probeGroup.isMember("livenessState")).isTrue();
|
||||
assertThat(probeGroup.isMember("readinessState")).isFalse();
|
||||
|
@ -97,7 +97,7 @@ class AvailabilityProbesHealthEndpointGroupsTests {
|
|||
|
||||
@Test
|
||||
void getReadinessProbeHasOnlyReadinessStateAsMember() {
|
||||
HealthEndpointGroups availabilityProbes = new AvailabilityProbesHealthEndpointGroups(this.delegate);
|
||||
HealthEndpointGroups availabilityProbes = new AvailabilityProbesHealthEndpointGroups(this.delegate, false);
|
||||
HealthEndpointGroup probeGroup = availabilityProbes.get("readiness");
|
||||
assertThat(probeGroup.isMember("livenessState")).isFalse();
|
||||
assertThat(probeGroup.isMember("readinessState")).isTrue();
|
||||
|
|
|
@ -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.
|
||||
|
@ -28,6 +28,7 @@ import javax.sql.DataSource;
|
|||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||
import org.springframework.boot.actuate.health.AdditionalHealthEndpointPath;
|
||||
import org.springframework.boot.actuate.health.CompositeHealthContributor;
|
||||
import org.springframework.boot.actuate.health.DefaultHealthContributorRegistry;
|
||||
import org.springframework.boot.actuate.health.Health;
|
||||
|
@ -164,6 +165,11 @@ class HealthEndpointDocumentationTests extends MockMvcEndpointDocumentationTests
|
|||
return this.httpCodeStatusMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdditionalHealthEndpointPath getAdditionalPath() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
@ -59,7 +59,7 @@ class AutoConfiguredHealthEndpointGroupTests {
|
|||
@Test
|
||||
void isMemberWhenMemberPredicateMatchesAcceptsTrue() {
|
||||
AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> name.startsWith("a"),
|
||||
this.statusAggregator, this.httpCodeStatusMapper, null, Show.ALWAYS, Collections.emptySet());
|
||||
this.statusAggregator, this.httpCodeStatusMapper, null, Show.ALWAYS, Collections.emptySet(), null);
|
||||
assertThat(group.isMember("albert")).isTrue();
|
||||
assertThat(group.isMember("arnold")).isTrue();
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ class AutoConfiguredHealthEndpointGroupTests {
|
|||
@Test
|
||||
void isMemberWhenMemberPredicateRejectsReturnsTrue() {
|
||||
AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> name.startsWith("a"),
|
||||
this.statusAggregator, this.httpCodeStatusMapper, null, Show.ALWAYS, Collections.emptySet());
|
||||
this.statusAggregator, this.httpCodeStatusMapper, null, Show.ALWAYS, Collections.emptySet(), null);
|
||||
assertThat(group.isMember("bert")).isFalse();
|
||||
assertThat(group.isMember("ernie")).isFalse();
|
||||
}
|
||||
|
@ -75,21 +75,22 @@ class AutoConfiguredHealthEndpointGroupTests {
|
|||
@Test
|
||||
void showDetailsWhenShowDetailsIsNeverReturnsFalse() {
|
||||
AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> true,
|
||||
this.statusAggregator, this.httpCodeStatusMapper, null, Show.NEVER, Collections.emptySet());
|
||||
this.statusAggregator, this.httpCodeStatusMapper, null, Show.NEVER, Collections.emptySet(), null);
|
||||
assertThat(group.showDetails(SecurityContext.NONE)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void showDetailsWhenShowDetailsIsAlwaysReturnsTrue() {
|
||||
AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> true,
|
||||
this.statusAggregator, this.httpCodeStatusMapper, null, Show.ALWAYS, Collections.emptySet());
|
||||
this.statusAggregator, this.httpCodeStatusMapper, null, Show.ALWAYS, Collections.emptySet(), null);
|
||||
assertThat(group.showDetails(SecurityContext.NONE)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void showDetailsWhenShowDetailsIsWhenAuthorizedAndPrincipalIsNullReturnsFalse() {
|
||||
AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> true,
|
||||
this.statusAggregator, this.httpCodeStatusMapper, null, Show.WHEN_AUTHORIZED, Collections.emptySet());
|
||||
this.statusAggregator, this.httpCodeStatusMapper, null, Show.WHEN_AUTHORIZED, Collections.emptySet(),
|
||||
null);
|
||||
given(this.securityContext.getPrincipal()).willReturn(null);
|
||||
assertThat(group.showDetails(this.securityContext)).isFalse();
|
||||
}
|
||||
|
@ -97,7 +98,8 @@ class AutoConfiguredHealthEndpointGroupTests {
|
|||
@Test
|
||||
void showDetailsWhenShowDetailsIsWhenAuthorizedAndRolesAreEmptyReturnsTrue() {
|
||||
AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> true,
|
||||
this.statusAggregator, this.httpCodeStatusMapper, null, Show.WHEN_AUTHORIZED, Collections.emptySet());
|
||||
this.statusAggregator, this.httpCodeStatusMapper, null, Show.WHEN_AUTHORIZED, Collections.emptySet(),
|
||||
null);
|
||||
given(this.securityContext.getPrincipal()).willReturn(this.principal);
|
||||
assertThat(group.showDetails(this.securityContext)).isTrue();
|
||||
}
|
||||
|
@ -106,7 +108,7 @@ class AutoConfiguredHealthEndpointGroupTests {
|
|||
void showDetailsWhenShowDetailsIsWhenAuthorizedAndUseIsInRoleReturnsTrue() {
|
||||
AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> true,
|
||||
this.statusAggregator, this.httpCodeStatusMapper, null, Show.WHEN_AUTHORIZED,
|
||||
Arrays.asList("admin", "root", "bossmode"));
|
||||
Arrays.asList("admin", "root", "bossmode"), null);
|
||||
given(this.securityContext.getPrincipal()).willReturn(this.principal);
|
||||
given(this.securityContext.isUserInRole("admin")).willReturn(false);
|
||||
given(this.securityContext.isUserInRole("root")).willReturn(true);
|
||||
|
@ -117,7 +119,7 @@ class AutoConfiguredHealthEndpointGroupTests {
|
|||
void showDetailsWhenShowDetailsIsWhenAuthorizedAndUserIsNotInRoleReturnsFalse() {
|
||||
AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> true,
|
||||
this.statusAggregator, this.httpCodeStatusMapper, null, Show.WHEN_AUTHORIZED,
|
||||
Arrays.asList("admin", "root", "bossmode"));
|
||||
Arrays.asList("admin", "root", "bossmode"), null);
|
||||
given(this.securityContext.getPrincipal()).willReturn(this.principal);
|
||||
assertThat(group.showDetails(this.securityContext)).isFalse();
|
||||
}
|
||||
|
@ -126,7 +128,7 @@ class AutoConfiguredHealthEndpointGroupTests {
|
|||
void showDetailsWhenShowDetailsIsWhenAuthorizedAndUserHasRightAuthorityReturnsTrue() {
|
||||
AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> true,
|
||||
this.statusAggregator, this.httpCodeStatusMapper, null, Show.WHEN_AUTHORIZED,
|
||||
Arrays.asList("admin", "root", "bossmode"));
|
||||
Arrays.asList("admin", "root", "bossmode"), null);
|
||||
Authentication principal = mock(Authentication.class);
|
||||
given(principal.getAuthorities())
|
||||
.willAnswer((invocation) -> Collections.singleton(new SimpleGrantedAuthority("admin")));
|
||||
|
@ -138,7 +140,7 @@ class AutoConfiguredHealthEndpointGroupTests {
|
|||
void showDetailsWhenShowDetailsIsWhenAuthorizedAndUserDoesNotHaveRightAuthoritiesReturnsFalse() {
|
||||
AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> true,
|
||||
this.statusAggregator, this.httpCodeStatusMapper, null, Show.WHEN_AUTHORIZED,
|
||||
Arrays.asList("admin", "rot", "bossmode"));
|
||||
Arrays.asList("admin", "rot", "bossmode"), null);
|
||||
Authentication principal = mock(Authentication.class);
|
||||
given(principal.getAuthorities())
|
||||
.willAnswer((invocation) -> Collections.singleton(new SimpleGrantedAuthority("other")));
|
||||
|
@ -149,24 +151,26 @@ class AutoConfiguredHealthEndpointGroupTests {
|
|||
@Test
|
||||
void showComponentsWhenShowComponentsIsNullDelegatesToShowDetails() {
|
||||
AutoConfiguredHealthEndpointGroup alwaysGroup = new AutoConfiguredHealthEndpointGroup((name) -> true,
|
||||
this.statusAggregator, this.httpCodeStatusMapper, null, Show.ALWAYS, Collections.emptySet());
|
||||
this.statusAggregator, this.httpCodeStatusMapper, null, Show.ALWAYS, Collections.emptySet(), null);
|
||||
assertThat(alwaysGroup.showComponents(SecurityContext.NONE)).isTrue();
|
||||
AutoConfiguredHealthEndpointGroup neverGroup = new AutoConfiguredHealthEndpointGroup((name) -> true,
|
||||
this.statusAggregator, this.httpCodeStatusMapper, null, Show.NEVER, Collections.emptySet());
|
||||
this.statusAggregator, this.httpCodeStatusMapper, null, Show.NEVER, Collections.emptySet(), null);
|
||||
assertThat(neverGroup.showComponents(SecurityContext.NONE)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void showComponentsWhenShowComponentsIsNeverReturnsFalse() {
|
||||
AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> true,
|
||||
this.statusAggregator, this.httpCodeStatusMapper, Show.NEVER, Show.ALWAYS, Collections.emptySet());
|
||||
this.statusAggregator, this.httpCodeStatusMapper, Show.NEVER, Show.ALWAYS, Collections.emptySet(),
|
||||
null);
|
||||
assertThat(group.showComponents(SecurityContext.NONE)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void showComponentsWhenShowComponentsIsAlwaysReturnsTrue() {
|
||||
AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> true,
|
||||
this.statusAggregator, this.httpCodeStatusMapper, Show.ALWAYS, Show.NEVER, Collections.emptySet());
|
||||
this.statusAggregator, this.httpCodeStatusMapper, Show.ALWAYS, Show.NEVER, Collections.emptySet(),
|
||||
null);
|
||||
assertThat(group.showComponents(SecurityContext.NONE)).isTrue();
|
||||
}
|
||||
|
||||
|
@ -174,7 +178,7 @@ class AutoConfiguredHealthEndpointGroupTests {
|
|||
void showComponentsWhenShowComponentsIsWhenAuthorizedAndPrincipalIsNullReturnsFalse() {
|
||||
AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> true,
|
||||
this.statusAggregator, this.httpCodeStatusMapper, Show.WHEN_AUTHORIZED, Show.NEVER,
|
||||
Collections.emptySet());
|
||||
Collections.emptySet(), null);
|
||||
given(this.securityContext.getPrincipal()).willReturn(null);
|
||||
assertThat(group.showComponents(this.securityContext)).isFalse();
|
||||
}
|
||||
|
@ -183,7 +187,7 @@ class AutoConfiguredHealthEndpointGroupTests {
|
|||
void showComponentsWhenShowComponentsIsWhenAuthorizedAndRolesAreEmptyReturnsTrue() {
|
||||
AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> true,
|
||||
this.statusAggregator, this.httpCodeStatusMapper, Show.WHEN_AUTHORIZED, Show.NEVER,
|
||||
Collections.emptySet());
|
||||
Collections.emptySet(), null);
|
||||
given(this.securityContext.getPrincipal()).willReturn(this.principal);
|
||||
assertThat(group.showComponents(this.securityContext)).isTrue();
|
||||
}
|
||||
|
@ -192,7 +196,7 @@ class AutoConfiguredHealthEndpointGroupTests {
|
|||
void showComponentsWhenShowComponentsIsWhenAuthorizedAndUseIsInRoleReturnsTrue() {
|
||||
AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> true,
|
||||
this.statusAggregator, this.httpCodeStatusMapper, Show.WHEN_AUTHORIZED, Show.NEVER,
|
||||
Arrays.asList("admin", "root", "bossmode"));
|
||||
Arrays.asList("admin", "root", "bossmode"), null);
|
||||
given(this.securityContext.getPrincipal()).willReturn(this.principal);
|
||||
given(this.securityContext.isUserInRole("admin")).willReturn(false);
|
||||
given(this.securityContext.isUserInRole("root")).willReturn(true);
|
||||
|
@ -203,7 +207,7 @@ class AutoConfiguredHealthEndpointGroupTests {
|
|||
void showComponentsWhenShowComponentsIsWhenAuthorizedAndUserIsNotInRoleReturnsFalse() {
|
||||
AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> true,
|
||||
this.statusAggregator, this.httpCodeStatusMapper, Show.WHEN_AUTHORIZED, Show.NEVER,
|
||||
Arrays.asList("admin", "rot", "bossmode"));
|
||||
Arrays.asList("admin", "rot", "bossmode"), null);
|
||||
given(this.securityContext.getPrincipal()).willReturn(this.principal);
|
||||
assertThat(group.showComponents(this.securityContext)).isFalse();
|
||||
}
|
||||
|
@ -212,7 +216,7 @@ class AutoConfiguredHealthEndpointGroupTests {
|
|||
void showComponentsWhenShowComponentsIsWhenAuthorizedAndUserHasRightAuthoritiesReturnsTrue() {
|
||||
AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> true,
|
||||
this.statusAggregator, this.httpCodeStatusMapper, Show.WHEN_AUTHORIZED, Show.NEVER,
|
||||
Arrays.asList("admin", "root", "bossmode"));
|
||||
Arrays.asList("admin", "root", "bossmode"), null);
|
||||
Authentication principal = mock(Authentication.class);
|
||||
given(principal.getAuthorities())
|
||||
.willAnswer((invocation) -> Collections.singleton(new SimpleGrantedAuthority("admin")));
|
||||
|
@ -224,7 +228,7 @@ class AutoConfiguredHealthEndpointGroupTests {
|
|||
void showComponentsWhenShowComponentsIsWhenAuthorizedAndUserDoesNotHaveRightAuthoritiesReturnsFalse() {
|
||||
AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> true,
|
||||
this.statusAggregator, this.httpCodeStatusMapper, Show.WHEN_AUTHORIZED, Show.NEVER,
|
||||
Arrays.asList("admin", "rot", "bossmode"));
|
||||
Arrays.asList("admin", "rot", "bossmode"), null);
|
||||
Authentication principal = mock(Authentication.class);
|
||||
given(principal.getAuthorities())
|
||||
.willAnswer((invocation) -> Collections.singleton(new SimpleGrantedAuthority("other")));
|
||||
|
@ -235,14 +239,14 @@ class AutoConfiguredHealthEndpointGroupTests {
|
|||
@Test
|
||||
void getStatusAggregatorReturnsStatusAggregator() {
|
||||
AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> true,
|
||||
this.statusAggregator, this.httpCodeStatusMapper, null, Show.ALWAYS, Collections.emptySet());
|
||||
this.statusAggregator, this.httpCodeStatusMapper, null, Show.ALWAYS, Collections.emptySet(), null);
|
||||
assertThat(group.getStatusAggregator()).isSameAs(this.statusAggregator);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getHttpCodeStatusMapperReturnsHttpCodeStatusMapper() {
|
||||
AutoConfiguredHealthEndpointGroup group = new AutoConfiguredHealthEndpointGroup((name) -> true,
|
||||
this.statusAggregator, this.httpCodeStatusMapper, null, Show.ALWAYS, Collections.emptySet());
|
||||
this.statusAggregator, this.httpCodeStatusMapper, null, Show.ALWAYS, Collections.emptySet(), null);
|
||||
assertThat(group.getHttpCodeStatusMapper()).isSameAs(this.httpCodeStatusMapper);
|
||||
}
|
||||
|
||||
|
|
|
@ -22,9 +22,12 @@ import org.junit.jupiter.api.Test;
|
|||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
|
||||
import org.springframework.boot.actuate.endpoint.ApiVersion;
|
||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
|
||||
import org.springframework.boot.actuate.health.DefaultHealthContributorRegistry;
|
||||
import org.springframework.boot.actuate.health.DefaultReactiveHealthContributorRegistry;
|
||||
import org.springframework.boot.actuate.health.Health;
|
||||
|
@ -69,8 +72,10 @@ class HealthEndpointAutoConfigurationTests {
|
|||
.of(HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class));
|
||||
|
||||
private final ReactiveWebApplicationContextRunner reactiveContextRunner = new ReactiveWebApplicationContextRunner()
|
||||
.withUserConfiguration(HealthIndicatorsConfiguration.class).withConfiguration(AutoConfigurations
|
||||
.of(HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class));
|
||||
.withUserConfiguration(HealthIndicatorsConfiguration.class)
|
||||
.withConfiguration(AutoConfigurations.of(HealthContributorAutoConfiguration.class,
|
||||
HealthEndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class,
|
||||
EndpointAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
void runWhenHealthEndpointIsDisabledDoesNotCreateBeans() {
|
||||
|
@ -208,8 +213,8 @@ class HealthEndpointAutoConfigurationTests {
|
|||
void runCreatesHealthEndpointWebExtension() {
|
||||
this.contextRunner.run((context) -> {
|
||||
HealthEndpointWebExtension webExtension = context.getBean(HealthEndpointWebExtension.class);
|
||||
WebEndpointResponse<HealthComponent> response = webExtension.health(ApiVersion.V3, SecurityContext.NONE,
|
||||
true, "simple");
|
||||
WebEndpointResponse<HealthComponent> response = webExtension.health(ApiVersion.V3,
|
||||
WebServerNamespace.SERVER, SecurityContext.NONE, true, "simple");
|
||||
Health health = (Health) response.getBody();
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(health.getDetails()).containsEntry("counter", 42);
|
||||
|
@ -220,8 +225,8 @@ class HealthEndpointAutoConfigurationTests {
|
|||
void runWhenHasHealthEndpointWebExtensionBeanDoesNotCreateExtraHealthEndpointWebExtension() {
|
||||
this.contextRunner.withUserConfiguration(HealthEndpointWebExtensionConfiguration.class).run((context) -> {
|
||||
HealthEndpointWebExtension webExtension = context.getBean(HealthEndpointWebExtension.class);
|
||||
WebEndpointResponse<HealthComponent> response = webExtension.health(ApiVersion.V3, SecurityContext.NONE,
|
||||
true, "simple");
|
||||
WebEndpointResponse<HealthComponent> response = webExtension.health(ApiVersion.V3,
|
||||
WebServerNamespace.SERVER, SecurityContext.NONE, true, "simple");
|
||||
assertThat(response).isNull();
|
||||
});
|
||||
}
|
||||
|
@ -231,7 +236,7 @@ class HealthEndpointAutoConfigurationTests {
|
|||
this.reactiveContextRunner.run((context) -> {
|
||||
ReactiveHealthEndpointWebExtension webExtension = context.getBean(ReactiveHealthEndpointWebExtension.class);
|
||||
Mono<WebEndpointResponse<? extends HealthComponent>> response = webExtension.health(ApiVersion.V3,
|
||||
SecurityContext.NONE, true, "simple");
|
||||
WebServerNamespace.SERVER, SecurityContext.NONE, true, "simple");
|
||||
Health health = (Health) (response.block().getBody());
|
||||
assertThat(health.getDetails()).containsEntry("counter", 42);
|
||||
});
|
||||
|
@ -244,7 +249,7 @@ class HealthEndpointAutoConfigurationTests {
|
|||
ReactiveHealthEndpointWebExtension webExtension = context
|
||||
.getBean(ReactiveHealthEndpointWebExtension.class);
|
||||
Mono<WebEndpointResponse<? extends HealthComponent>> response = webExtension.health(ApiVersion.V3,
|
||||
SecurityContext.NONE, true, "simple");
|
||||
WebServerNamespace.SERVER, SecurityContext.NONE, true, "simple");
|
||||
assertThat(response).isNull();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* 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.
|
||||
* 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.integrationtest;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener;
|
||||
import org.springframework.boot.test.context.assertj.ApplicationContextAssertProvider;
|
||||
import org.springframework.boot.test.context.runner.AbstractApplicationContextRunner;
|
||||
import org.springframework.boot.test.context.runner.ContextConsumer;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
||||
/**
|
||||
* Abstract base class for health groups with an additional path.
|
||||
*
|
||||
* @param <T> the runner
|
||||
* @param <C> the application context type
|
||||
* @param <A> the assertions
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
abstract class AbstractHealthEndpointAdditionalPathIntegrationTests<T extends AbstractApplicationContextRunner<T, C, A>, C extends ConfigurableApplicationContext, A extends ApplicationContextAssertProvider<C>> {
|
||||
|
||||
private final T runner;
|
||||
|
||||
AbstractHealthEndpointAdditionalPathIntegrationTests(T runner) {
|
||||
this.runner = runner;
|
||||
}
|
||||
|
||||
@Test
|
||||
void groupIsAvailableAtAdditionalPath() {
|
||||
this.runner
|
||||
.withPropertyValues("management.endpoint.health.group.live.include=diskSpace",
|
||||
"management.endpoint.health.group.live.additional-path=server:/healthz",
|
||||
"management.endpoint.health.group.live.show-components=always")
|
||||
.run(withWebTestClient(this::testResponse, "local.server.port"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void groupIsAvailableAtAdditionalPathWithoutSlash() {
|
||||
this.runner
|
||||
.withPropertyValues("management.endpoint.health.group.live.include=diskSpace",
|
||||
"management.endpoint.health.group.live.additional-path=server:healthz",
|
||||
"management.endpoint.health.group.live.show-components=always")
|
||||
.run(withWebTestClient(this::testResponse, "local.server.port"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void groupIsAvailableAtAdditionalPathOnManagementPort() {
|
||||
this.runner.withPropertyValues("management.endpoint.health.group.live.include=diskSpace",
|
||||
"management.server.port=0", "management.endpoint.health.group.live.additional-path=management:healthz",
|
||||
"management.endpoint.health.group.live.show-components=always")
|
||||
.run(withWebTestClient(this::testResponse, "local.management.port"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void groupIsAvailableAtAdditionalPathOnServerPortWithDifferentManagementPort() {
|
||||
this.runner.withPropertyValues("management.endpoint.health.group.live.include=diskSpace",
|
||||
"management.server.port=0", "management.endpoint.health.group.live.additional-path=server:healthz",
|
||||
"management.endpoint.health.group.live.show-components=always")
|
||||
.withInitializer(new ConditionEvaluationReportLoggingListener())
|
||||
.run(withWebTestClient(this::testResponse, "local.server.port"));
|
||||
}
|
||||
|
||||
private void testResponse(WebTestClient client) {
|
||||
client.get().uri("/healthz").accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBody()
|
||||
.jsonPath("status").isEqualTo("UP").jsonPath("components.diskSpace").exists();
|
||||
}
|
||||
|
||||
private ContextConsumer<A> withWebTestClient(Consumer<WebTestClient> consumer, String property) {
|
||||
return (context) -> {
|
||||
String port = context.getEnvironment().getProperty(property);
|
||||
WebTestClient client = WebTestClient.bindToServer().baseUrl("http://localhost:" + port).build();
|
||||
consumer.accept(client);
|
||||
};
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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.
|
||||
* 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.integrationtest;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.system.DiskSpaceHealthContributorAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration;
|
||||
import org.springframework.boot.test.context.FilteredClassLoader;
|
||||
import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext;
|
||||
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
|
||||
import org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer;
|
||||
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
|
||||
import org.springframework.web.context.ConfigurableWebApplicationContext;
|
||||
import org.springframework.web.servlet.DispatcherServlet;
|
||||
|
||||
/**
|
||||
* Integration tests for health groups on an additional path on Jersey.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
class JerseyHealthEndpointAdditionalPathIntegrationTests extends
|
||||
AbstractHealthEndpointAdditionalPathIntegrationTests<WebApplicationContextRunner, ConfigurableWebApplicationContext, AssertableWebApplicationContext> {
|
||||
|
||||
JerseyHealthEndpointAdditionalPathIntegrationTests() {
|
||||
super(new WebApplicationContextRunner(AnnotationConfigServletWebServerApplicationContext::new)
|
||||
.withConfiguration(AutoConfigurations.of(JacksonAutoConfiguration.class, JerseyAutoConfiguration.class,
|
||||
EndpointAutoConfiguration.class, ServletWebServerFactoryAutoConfiguration.class,
|
||||
WebEndpointAutoConfiguration.class, JerseyAutoConfiguration.class,
|
||||
ManagementContextAutoConfiguration.class, ServletManagementContextAutoConfiguration.class,
|
||||
HealthEndpointAutoConfiguration.class, DiskSpaceHealthContributorAutoConfiguration.class))
|
||||
.withInitializer(new ServerPortInfoApplicationContextInitializer())
|
||||
.withClassLoader(new FilteredClassLoader(DispatcherServlet.class)).withPropertyValues("server.port=0"));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* 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.
|
||||
* 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.integrationtest;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.system.DiskSpaceHealthContributorAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
|
||||
import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext;
|
||||
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
|
||||
import org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer;
|
||||
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
|
||||
import org.springframework.web.context.ConfigurableWebApplicationContext;
|
||||
|
||||
/**
|
||||
* Integration tests for MVC health groups on an additional path.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
class WebMvcHealthEndpointAdditionalPathIntegrationTests extends
|
||||
AbstractHealthEndpointAdditionalPathIntegrationTests<WebApplicationContextRunner, ConfigurableWebApplicationContext, AssertableWebApplicationContext> {
|
||||
|
||||
WebMvcHealthEndpointAdditionalPathIntegrationTests() {
|
||||
super(new WebApplicationContextRunner(AnnotationConfigServletWebServerApplicationContext::new)
|
||||
.withConfiguration(AutoConfigurations.of(JacksonAutoConfiguration.class,
|
||||
HttpMessageConvertersAutoConfiguration.class, ManagementContextAutoConfiguration.class,
|
||||
ServletWebServerFactoryAutoConfiguration.class, WebMvcAutoConfiguration.class,
|
||||
ServletManagementContextAutoConfiguration.class, WebEndpointAutoConfiguration.class,
|
||||
EndpointAutoConfiguration.class, DispatcherServletAutoConfiguration.class,
|
||||
HealthEndpointAutoConfiguration.class, DiskSpaceHealthContributorAutoConfiguration.class))
|
||||
.withInitializer(new ServerPortInfoApplicationContextInitializer())
|
||||
.withPropertyValues("server.port=0"));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* 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.
|
||||
* 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.integrationtest;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.beans.BeansEndpointAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.system.DiskSpaceHealthContributorAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.web.reactive.ReactiveManagementContextAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration;
|
||||
import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplicationContext;
|
||||
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
|
||||
import org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer;
|
||||
import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext;
|
||||
import org.springframework.boot.web.reactive.context.ConfigurableReactiveWebApplicationContext;
|
||||
|
||||
/**
|
||||
* Integration tests for Webflux health groups on an additional path.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
class WebfluxHealthEndpointAdditionalPathIntegrationTests extends
|
||||
AbstractHealthEndpointAdditionalPathIntegrationTests<ReactiveWebApplicationContextRunner, ConfigurableReactiveWebApplicationContext, AssertableReactiveWebApplicationContext> {
|
||||
|
||||
WebfluxHealthEndpointAdditionalPathIntegrationTests() {
|
||||
super(new ReactiveWebApplicationContextRunner(AnnotationConfigReactiveWebServerApplicationContext::new)
|
||||
.withConfiguration(AutoConfigurations.of(JacksonAutoConfiguration.class, CodecsAutoConfiguration.class,
|
||||
WebFluxAutoConfiguration.class, HttpHandlerAutoConfiguration.class,
|
||||
EndpointAutoConfiguration.class, HealthEndpointAutoConfiguration.class,
|
||||
DiskSpaceHealthContributorAutoConfiguration.class, WebEndpointAutoConfiguration.class,
|
||||
ManagementContextAutoConfiguration.class, ReactiveWebServerFactoryAutoConfiguration.class,
|
||||
ReactiveManagementContextAutoConfiguration.class, BeansEndpointAutoConfiguration.class))
|
||||
.withInitializer(new ServerPortInfoApplicationContextInitializer())
|
||||
.withPropertyValues("server.port=0"));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* 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.
|
||||
* 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.endpoint.web;
|
||||
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Enumeration of server namespaces.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Madhura Bhave
|
||||
* @since 2.6.0
|
||||
*/
|
||||
public final class WebServerNamespace {
|
||||
|
||||
/**
|
||||
* {@link WebServerNamespace} that represents the main server.
|
||||
*/
|
||||
public static final WebServerNamespace SERVER = new WebServerNamespace("server");
|
||||
|
||||
/**
|
||||
* {@link WebServerNamespace} that represents the management server.
|
||||
*/
|
||||
public static final WebServerNamespace MANAGEMENT = new WebServerNamespace("management");
|
||||
|
||||
private final String value;
|
||||
|
||||
private WebServerNamespace(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public static WebServerNamespace from(String value) {
|
||||
if (StringUtils.hasText(value)) {
|
||||
return new WebServerNamespace(value);
|
||||
}
|
||||
return SERVER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
WebServerNamespace other = (WebServerNamespace) obj;
|
||||
return this.value.equals(other.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.value.hashCode();
|
||||
}
|
||||
|
||||
}
|
|
@ -42,6 +42,7 @@ import reactor.core.publisher.Mono;
|
|||
|
||||
import org.springframework.boot.actuate.endpoint.InvalidEndpointRequestException;
|
||||
import org.springframework.boot.actuate.endpoint.InvocationContext;
|
||||
import org.springframework.boot.actuate.endpoint.OperationArgumentResolver;
|
||||
import org.springframework.boot.actuate.endpoint.ProducibleOperationArgumentResolver;
|
||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver;
|
||||
|
@ -52,6 +53,7 @@ import org.springframework.boot.actuate.endpoint.web.Link;
|
|||
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebOperation;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebOperationRequestPredicate;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
@ -91,7 +93,7 @@ public class JerseyEndpointResourceFactory {
|
|||
return resources;
|
||||
}
|
||||
|
||||
private Resource createResource(EndpointMapping endpointMapping, WebOperation operation) {
|
||||
protected Resource createResource(EndpointMapping endpointMapping, WebOperation operation) {
|
||||
WebOperationRequestPredicate requestPredicate = operation.getRequestPredicate();
|
||||
String path = requestPredicate.getPath();
|
||||
String matchAllRemainingPathSegmentsVariable = requestPredicate.getMatchAllRemainingPathSegmentsVariable();
|
||||
|
@ -99,11 +101,19 @@ public class JerseyEndpointResourceFactory {
|
|||
path = path.replace("{*" + matchAllRemainingPathSegmentsVariable + "}",
|
||||
"{" + matchAllRemainingPathSegmentsVariable + ": .*}");
|
||||
}
|
||||
Builder resourceBuilder = Resource.builder().path(endpointMapping.createSubPath(path));
|
||||
return getResource(endpointMapping, operation, requestPredicate, path, null, null);
|
||||
}
|
||||
|
||||
protected Resource getResource(EndpointMapping endpointMapping, WebOperation operation,
|
||||
WebOperationRequestPredicate requestPredicate, String path, WebServerNamespace serverNamespace,
|
||||
JerseyRemainingPathSegmentProvider remainingPathSegmentProvider) {
|
||||
Builder resourceBuilder = Resource.builder().path(endpointMapping.getPath())
|
||||
.path(endpointMapping.createSubPath(path));
|
||||
resourceBuilder.addMethod(requestPredicate.getHttpMethod().name())
|
||||
.consumes(StringUtils.toStringArray(requestPredicate.getConsumes()))
|
||||
.produces(StringUtils.toStringArray(requestPredicate.getProduces()))
|
||||
.handledBy(new OperationInflector(operation, !requestPredicate.getConsumes().isEmpty()));
|
||||
.handledBy(new OperationInflector(operation, !requestPredicate.getConsumes().isEmpty(), serverNamespace,
|
||||
remainingPathSegmentProvider));
|
||||
return resourceBuilder.build();
|
||||
}
|
||||
|
||||
|
@ -137,9 +147,16 @@ public class JerseyEndpointResourceFactory {
|
|||
|
||||
private final boolean readBody;
|
||||
|
||||
private OperationInflector(WebOperation operation, boolean readBody) {
|
||||
private final WebServerNamespace serverNamespace;
|
||||
|
||||
private final JerseyRemainingPathSegmentProvider remainingPathSegmentProvider;
|
||||
|
||||
private OperationInflector(WebOperation operation, boolean readBody, WebServerNamespace serverNamespace,
|
||||
JerseyRemainingPathSegmentProvider remainingPathSegments) {
|
||||
this.operation = operation;
|
||||
this.readBody = readBody;
|
||||
this.serverNamespace = serverNamespace;
|
||||
this.remainingPathSegmentProvider = remainingPathSegments;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -152,7 +169,10 @@ public class JerseyEndpointResourceFactory {
|
|||
arguments.putAll(extractQueryParameters(data));
|
||||
try {
|
||||
JerseySecurityContext securityContext = new JerseySecurityContext(data.getSecurityContext());
|
||||
OperationArgumentResolver serverNamespaceArgumentResolver = OperationArgumentResolver
|
||||
.of(WebServerNamespace.class, () -> this.serverNamespace);
|
||||
InvocationContext invocationContext = new InvocationContext(securityContext, arguments,
|
||||
serverNamespaceArgumentResolver,
|
||||
new ProducibleOperationArgumentResolver(() -> data.getHeaders().get("Accept")));
|
||||
Object response = this.operation.invoke(invocationContext);
|
||||
return convertToJaxRsResponse(response, data.getRequest().getMethod());
|
||||
|
@ -173,12 +193,21 @@ public class JerseyEndpointResourceFactory {
|
|||
String matchAllRemainingPathSegmentsVariable = this.operation.getRequestPredicate()
|
||||
.getMatchAllRemainingPathSegmentsVariable();
|
||||
if (matchAllRemainingPathSegmentsVariable != null) {
|
||||
String remainingPathSegments = (String) pathParameters.get(matchAllRemainingPathSegmentsVariable);
|
||||
String remainingPathSegments = getRemainingPathSegments(requestContext, pathParameters,
|
||||
matchAllRemainingPathSegmentsVariable);
|
||||
pathParameters.put(matchAllRemainingPathSegmentsVariable, tokenizePathSegments(remainingPathSegments));
|
||||
}
|
||||
return pathParameters;
|
||||
}
|
||||
|
||||
private String getRemainingPathSegments(ContainerRequestContext requestContext,
|
||||
Map<String, Object> pathParameters, String matchAllRemainingPathSegmentsVariable) {
|
||||
if (this.remainingPathSegmentProvider != null) {
|
||||
return this.remainingPathSegmentProvider.get(requestContext, matchAllRemainingPathSegmentsVariable);
|
||||
}
|
||||
return (String) pathParameters.get(matchAllRemainingPathSegmentsVariable);
|
||||
}
|
||||
|
||||
private String[] tokenizePathSegments(String path) {
|
||||
String[] segments = StringUtils.tokenizeToStringArray(path, PATH_SEPARATOR, false, true);
|
||||
for (int i = 0; i < segments.length; i++) {
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* 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.
|
||||
* 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.endpoint.web.jersey;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.glassfish.jersey.server.model.Resource;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMapping;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebOperation;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebOperationRequestPredicate;
|
||||
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;
|
||||
|
||||
/**
|
||||
* A factory for creating Jersey {@link Resource Resources} for health groups with
|
||||
* additional path.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @since 2.6.0
|
||||
*/
|
||||
public class JerseyHealthEndpointAdditionalPathResourceFactory extends JerseyEndpointResourceFactory {
|
||||
|
||||
private final Set<HealthEndpointGroup> groups;
|
||||
|
||||
private final WebServerNamespace serverNamespace;
|
||||
|
||||
public JerseyHealthEndpointAdditionalPathResourceFactory(WebServerNamespace serverNamespace,
|
||||
HealthEndpointGroups groups) {
|
||||
this.serverNamespace = serverNamespace;
|
||||
this.groups = groups.getAllWithAdditionalPath(serverNamespace);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Resource createResource(EndpointMapping endpointMapping, WebOperation operation) {
|
||||
WebOperationRequestPredicate requestPredicate = operation.getRequestPredicate();
|
||||
String matchAllRemainingPathSegmentsVariable = requestPredicate.getMatchAllRemainingPathSegmentsVariable();
|
||||
if (matchAllRemainingPathSegmentsVariable != null) {
|
||||
for (HealthEndpointGroup group : this.groups) {
|
||||
AdditionalHealthEndpointPath additionalPath = group.getAdditionalPath();
|
||||
if (additionalPath != null) {
|
||||
return getResource(endpointMapping, operation, requestPredicate, additionalPath.getValue(),
|
||||
this.serverNamespace, (data, pathSegmentsVariable) -> data.getUriInfo().getPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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.
|
||||
* 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.endpoint.web.jersey;
|
||||
|
||||
import javax.ws.rs.container.ContainerRequestContext;
|
||||
|
||||
/**
|
||||
* Strategy interface used to provide the remaining path segments for a Jersey actuator
|
||||
* endpoint.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
interface JerseyRemainingPathSegmentProvider {
|
||||
|
||||
String get(ContainerRequestContext requestContext, String matchAllRemainingPathSegmentsVariable);
|
||||
|
||||
}
|
|
@ -31,6 +31,7 @@ import reactor.core.scheduler.Schedulers;
|
|||
|
||||
import org.springframework.boot.actuate.endpoint.InvalidEndpointRequestException;
|
||||
import org.springframework.boot.actuate.endpoint.InvocationContext;
|
||||
import org.springframework.boot.actuate.endpoint.OperationArgumentResolver;
|
||||
import org.springframework.boot.actuate.endpoint.OperationType;
|
||||
import org.springframework.boot.actuate.endpoint.ProducibleOperationArgumentResolver;
|
||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||
|
@ -41,6 +42,8 @@ import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
|
|||
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebOperation;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebOperationRequestPredicate;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
|
||||
import org.springframework.boot.web.context.WebServerApplicationContext;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
|
@ -64,6 +67,7 @@ import org.springframework.web.reactive.result.method.RequestMappingInfo;
|
|||
import org.springframework.web.reactive.result.method.RequestMappingInfoHandlerMapping;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.util.pattern.PathPattern;
|
||||
|
||||
/**
|
||||
* A custom {@link HandlerMapping} that makes web endpoints available over HTTP using
|
||||
|
@ -132,18 +136,25 @@ public abstract class AbstractWebFluxEndpointHandlerMapping extends RequestMappi
|
|||
}
|
||||
|
||||
private void registerMappingForOperation(ExposableWebEndpoint endpoint, WebOperation operation) {
|
||||
ReactiveWebOperation reactiveWebOperation = wrapReactiveWebOperation(endpoint, operation,
|
||||
new ReactiveWebOperationAdapter(operation));
|
||||
RequestMappingInfo requestMappingInfo = createRequestMappingInfo(operation);
|
||||
if (operation.getType() == OperationType.WRITE) {
|
||||
registerMapping(createRequestMappingInfo(operation), new WriteOperationHandler((reactiveWebOperation)),
|
||||
ReactiveWebOperation reactiveWebOperation = wrapReactiveWebOperation(endpoint, operation,
|
||||
new ReactiveWebOperationAdapter(operation));
|
||||
registerMapping(requestMappingInfo, new WriteOperationHandler((reactiveWebOperation)),
|
||||
this.handleWriteMethod);
|
||||
}
|
||||
else {
|
||||
registerMapping(createRequestMappingInfo(operation), new ReadOperationHandler((reactiveWebOperation)),
|
||||
this.handleReadMethod);
|
||||
registerReadMapping(requestMappingInfo, endpoint, operation);
|
||||
}
|
||||
}
|
||||
|
||||
protected void registerReadMapping(RequestMappingInfo requestMappingInfo, ExposableWebEndpoint endpoint,
|
||||
WebOperation operation) {
|
||||
ReactiveWebOperation reactiveWebOperation = wrapReactiveWebOperation(endpoint, operation,
|
||||
new ReactiveWebOperationAdapter(operation));
|
||||
registerMapping(requestMappingInfo, new ReadOperationHandler((reactiveWebOperation)), this.handleReadMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook point that allows subclasses to wrap the {@link ReactiveWebOperation} before
|
||||
* it's called. Allows additional features, such as security, to be added.
|
||||
|
@ -299,32 +310,25 @@ public abstract class AbstractWebFluxEndpointHandlerMapping extends RequestMappi
|
|||
@Override
|
||||
public Mono<ResponseEntity<Object>> handle(ServerWebExchange exchange, Map<String, String> body) {
|
||||
Map<String, Object> arguments = getArguments(exchange, body);
|
||||
String matchAllRemainingPathSegmentsVariable = this.operation.getRequestPredicate()
|
||||
.getMatchAllRemainingPathSegmentsVariable();
|
||||
if (matchAllRemainingPathSegmentsVariable != null) {
|
||||
arguments.put(matchAllRemainingPathSegmentsVariable,
|
||||
tokenizePathSegments((String) arguments.get(matchAllRemainingPathSegmentsVariable)));
|
||||
}
|
||||
OperationArgumentResolver serverNamespaceArgumentResolver = OperationArgumentResolver
|
||||
.of(WebServerNamespace.class, () -> WebServerNamespace
|
||||
.from(WebServerApplicationContext.getServerNamepace(exchange.getApplicationContext())));
|
||||
return this.securityContextSupplier.get()
|
||||
.map((securityContext) -> new InvocationContext(securityContext, arguments,
|
||||
serverNamespaceArgumentResolver,
|
||||
new ProducibleOperationArgumentResolver(
|
||||
() -> exchange.getRequest().getHeaders().get("Accept"))))
|
||||
.flatMap((invocationContext) -> handleResult((Publisher<?>) this.invoker.invoke(invocationContext),
|
||||
exchange.getRequest().getMethod()));
|
||||
}
|
||||
|
||||
private String[] tokenizePathSegments(String path) {
|
||||
String[] segments = StringUtils.tokenizeToStringArray(path, PATH_SEPARATOR, false, true);
|
||||
for (int i = 0; i < segments.length; i++) {
|
||||
if (segments[i].contains("%")) {
|
||||
segments[i] = StringUtils.uriDecode(segments[i], StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
return segments;
|
||||
}
|
||||
|
||||
private Map<String, Object> getArguments(ServerWebExchange exchange, Map<String, String> body) {
|
||||
Map<String, Object> arguments = new LinkedHashMap<>(getTemplateVariables(exchange));
|
||||
String matchAllRemainingPathSegmentsVariable = this.operation.getRequestPredicate()
|
||||
.getMatchAllRemainingPathSegmentsVariable();
|
||||
if (matchAllRemainingPathSegmentsVariable != null) {
|
||||
arguments.put(matchAllRemainingPathSegmentsVariable, getRemainingPathSegments(exchange));
|
||||
}
|
||||
if (body != null) {
|
||||
arguments.putAll(body);
|
||||
}
|
||||
|
@ -333,6 +337,26 @@ public abstract class AbstractWebFluxEndpointHandlerMapping extends RequestMappi
|
|||
return arguments;
|
||||
}
|
||||
|
||||
private Object getRemainingPathSegments(ServerWebExchange exchange) {
|
||||
PathPattern pathPattern = exchange.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
|
||||
if (pathPattern.hasPatternSyntax()) {
|
||||
String remainingSegments = pathPattern
|
||||
.extractPathWithinPattern(exchange.getRequest().getPath().pathWithinApplication()).value();
|
||||
return tokenizePathSegments(remainingSegments);
|
||||
}
|
||||
return tokenizePathSegments(pathPattern.toString());
|
||||
}
|
||||
|
||||
private String[] tokenizePathSegments(String value) {
|
||||
String[] segments = StringUtils.tokenizeToStringArray(value, PATH_SEPARATOR, false, true);
|
||||
for (int i = 0; i < segments.length; i++) {
|
||||
if (segments[i].contains("%")) {
|
||||
segments[i] = StringUtils.uriDecode(segments[i], StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
return segments;
|
||||
}
|
||||
|
||||
private Map<String, String> getTemplateVariables(ServerWebExchange exchange) {
|
||||
return exchange.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* 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.
|
||||
* 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.endpoint.web.reactive;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMapping;
|
||||
import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebOperation;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebOperationRequestPredicate;
|
||||
import org.springframework.boot.actuate.health.AdditionalHealthEndpointPath;
|
||||
import org.springframework.boot.actuate.health.HealthEndpointGroup;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.reactive.HandlerMapping;
|
||||
import org.springframework.web.reactive.result.method.RequestMappingInfo;
|
||||
|
||||
/**
|
||||
* A custom {@link HandlerMapping} that allows health groups to be mapped to an additional
|
||||
* path.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @since 2.6.0
|
||||
*/
|
||||
public class AdditionalHealthEndpointPathsWebFluxHandlerMapping extends AbstractWebFluxEndpointHandlerMapping {
|
||||
|
||||
private final EndpointMapping endpointMapping;
|
||||
|
||||
private final ExposableWebEndpoint endpoint;
|
||||
|
||||
private final Set<HealthEndpointGroup> groups;
|
||||
|
||||
public AdditionalHealthEndpointPathsWebFluxHandlerMapping(EndpointMapping endpointMapping,
|
||||
ExposableWebEndpoint endpoint, Set<HealthEndpointGroup> groups) {
|
||||
super(endpointMapping, Collections.singletonList(endpoint), null, null, false);
|
||||
this.endpointMapping = endpointMapping;
|
||||
this.groups = groups;
|
||||
this.endpoint = endpoint;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initHandlerMethods() {
|
||||
for (WebOperation operation : this.endpoint.getOperations()) {
|
||||
WebOperationRequestPredicate predicate = operation.getRequestPredicate();
|
||||
String matchAllRemainingPathSegmentsVariable = predicate.getMatchAllRemainingPathSegmentsVariable();
|
||||
if (matchAllRemainingPathSegmentsVariable != null) {
|
||||
for (HealthEndpointGroup group : this.groups) {
|
||||
AdditionalHealthEndpointPath additionalPath = group.getAdditionalPath();
|
||||
if (additionalPath != null) {
|
||||
RequestMappingInfo requestMappingInfo = getRequestMappingInfo(operation,
|
||||
additionalPath.getValue());
|
||||
registerReadMapping(requestMappingInfo, this.endpoint, operation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private RequestMappingInfo getRequestMappingInfo(WebOperation operation, String additionalPath) {
|
||||
WebOperationRequestPredicate predicate = operation.getRequestPredicate();
|
||||
String path = this.endpointMapping.createSubPath(additionalPath);
|
||||
RequestMethod method = RequestMethod.valueOf(predicate.getHttpMethod().name());
|
||||
String[] consumes = StringUtils.toStringArray(predicate.getConsumes());
|
||||
String[] produces = StringUtils.toStringArray(predicate.getProduces());
|
||||
return RequestMappingInfo.paths(path).methods(method).consumes(consumes).produces(produces).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LinksHandler getLinksHandler() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -32,6 +32,7 @@ import javax.servlet.http.HttpServletResponse;
|
|||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.boot.actuate.endpoint.InvalidEndpointRequestException;
|
||||
import org.springframework.boot.actuate.endpoint.InvocationContext;
|
||||
import org.springframework.boot.actuate.endpoint.OperationArgumentResolver;
|
||||
import org.springframework.boot.actuate.endpoint.ProducibleOperationArgumentResolver;
|
||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
|
||||
|
@ -41,6 +42,8 @@ import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
|
|||
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebOperation;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebOperationRequestPredicate;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
|
||||
import org.springframework.boot.web.context.WebServerApplicationContext;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
@ -54,6 +57,8 @@ import org.springframework.util.StringUtils;
|
|||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
import org.springframework.web.context.support.WebApplicationContextUtils;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
@ -172,6 +177,11 @@ public abstract class AbstractWebMvcEndpointHandlerMapping extends RequestMappin
|
|||
if (matchAllRemainingPathSegmentsVariable != null) {
|
||||
path = path.replace("{*" + matchAllRemainingPathSegmentsVariable + "}", "**");
|
||||
}
|
||||
registerMapping(endpoint, predicate, operation, path);
|
||||
}
|
||||
|
||||
protected void registerMapping(ExposableWebEndpoint endpoint, WebOperationRequestPredicate predicate,
|
||||
WebOperation operation, String path) {
|
||||
ServletWebOperation servletWebOperation = wrapServletWebOperation(endpoint, operation,
|
||||
new ServletWebOperationAdapter(operation));
|
||||
registerMapping(createRequestMappingInfo(predicate, path), new OperationHandler(servletWebOperation),
|
||||
|
@ -286,8 +296,17 @@ public abstract class AbstractWebMvcEndpointHandlerMapping extends RequestMappin
|
|||
Map<String, Object> arguments = getArguments(request, body);
|
||||
try {
|
||||
ServletSecurityContext securityContext = new ServletSecurityContext(request);
|
||||
ProducibleOperationArgumentResolver producibleOperationArgumentResolver = new ProducibleOperationArgumentResolver(
|
||||
() -> headers.get("Accept"));
|
||||
OperationArgumentResolver serverNamespaceArgumentResolver = OperationArgumentResolver
|
||||
.of(WebServerNamespace.class, () -> {
|
||||
WebApplicationContext applicationContext = WebApplicationContextUtils
|
||||
.getRequiredWebApplicationContext(request.getServletContext());
|
||||
return WebServerNamespace
|
||||
.from(WebServerApplicationContext.getServerNamepace(applicationContext));
|
||||
});
|
||||
InvocationContext invocationContext = new InvocationContext(securityContext, arguments,
|
||||
new ProducibleOperationArgumentResolver(() -> headers.get("Accept")));
|
||||
serverNamespaceArgumentResolver, producibleOperationArgumentResolver);
|
||||
return handleResult(this.operation.invoke(invocationContext), HttpMethod.resolve(request.getMethod()));
|
||||
}
|
||||
catch (InvalidEndpointRequestException ex) {
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* 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.
|
||||
* 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.endpoint.web.servlet;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMapping;
|
||||
import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebOperation;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebOperationRequestPredicate;
|
||||
import org.springframework.boot.actuate.health.AdditionalHealthEndpointPath;
|
||||
import org.springframework.boot.actuate.health.HealthEndpointGroup;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
|
||||
/**
|
||||
* A custom {@link HandlerMapping} that allows health groups to be mapped to an additional
|
||||
* path.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @since 2.6.0
|
||||
*/
|
||||
public class AdditionalHealthEndpointPathsWebMvcHandlerMapping extends AbstractWebMvcEndpointHandlerMapping {
|
||||
|
||||
private final ExposableWebEndpoint endpoint;
|
||||
|
||||
private final Set<HealthEndpointGroup> groups;
|
||||
|
||||
public AdditionalHealthEndpointPathsWebMvcHandlerMapping(ExposableWebEndpoint endpoint,
|
||||
Set<HealthEndpointGroup> groups) {
|
||||
super(new EndpointMapping(""), Collections.singletonList(endpoint), null, false);
|
||||
this.endpoint = endpoint;
|
||||
this.groups = groups;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initHandlerMethods() {
|
||||
for (WebOperation operation : this.endpoint.getOperations()) {
|
||||
WebOperationRequestPredicate predicate = operation.getRequestPredicate();
|
||||
String matchAllRemainingPathSegmentsVariable = predicate.getMatchAllRemainingPathSegmentsVariable();
|
||||
if (matchAllRemainingPathSegmentsVariable != null) {
|
||||
for (HealthEndpointGroup group : this.groups) {
|
||||
AdditionalHealthEndpointPath additionalPath = group.getAdditionalPath();
|
||||
if (additionalPath != null) {
|
||||
registerMapping(this.endpoint, predicate, operation, additionalPath.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LinksHandler getLinksHandler() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.health;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Value object that represents an additional path for a {@link HealthEndpointGroup}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Madhura Bhave
|
||||
* @since 2.6.0
|
||||
*/
|
||||
public final class AdditionalHealthEndpointPath {
|
||||
|
||||
private final WebServerNamespace namespace;
|
||||
|
||||
private final String value;
|
||||
|
||||
private final String canonicalValue;
|
||||
|
||||
private AdditionalHealthEndpointPath(WebServerNamespace namespace, String value) {
|
||||
this.namespace = namespace;
|
||||
this.value = value;
|
||||
this.canonicalValue = (!value.startsWith("/")) ? "/" + value : value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link WebServerNamespace} associated with this path.
|
||||
* @return the server namespace
|
||||
*/
|
||||
public WebServerNamespace getNamespace() {
|
||||
return this.namespace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value corresponding to this path.
|
||||
* @return the path
|
||||
*/
|
||||
public String getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if this path has the given {@link WebServerNamespace}.
|
||||
* @param webServerNamespace the server namespace
|
||||
* @return the new instance
|
||||
*/
|
||||
public boolean hasNamespace(WebServerNamespace webServerNamespace) {
|
||||
return this.namespace.equals(webServerNamespace);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
AdditionalHealthEndpointPath other = (AdditionalHealthEndpointPath) obj;
|
||||
boolean result = true;
|
||||
result = result && this.namespace.equals(other.namespace);
|
||||
result = result && this.canonicalValue.equals(other.canonicalValue);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + this.namespace.hashCode();
|
||||
result = prime * result + this.canonicalValue.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.namespace.getValue() + ":" + this.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@link AdditionalHealthEndpointPath} from the given input. The input
|
||||
* must contain a prefix and value separated by a `:`. The value must be limited to
|
||||
* one path segment. For example, `server:/healthz`.
|
||||
* @param value the value to parse
|
||||
* @return the new instance
|
||||
*/
|
||||
public static AdditionalHealthEndpointPath from(String value) {
|
||||
Assert.hasText(value, "Value must not be null");
|
||||
String[] values = value.split(":");
|
||||
Assert.isTrue(values.length == 2, "Value must contain a valid namespace and value separated by ':'.");
|
||||
Assert.isTrue(StringUtils.hasText(values[0]), "Value must contain a valid namespace.");
|
||||
WebServerNamespace namespace = WebServerNamespace.from(values[0]);
|
||||
validateValue(values[1]);
|
||||
return new AdditionalHealthEndpointPath(namespace, values[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@link AdditionalHealthEndpointPath} from the given
|
||||
* {@link WebServerNamespace} and value.
|
||||
* @param webServerNamespace the server namespace
|
||||
* @param value the value
|
||||
* @return the new instance
|
||||
*/
|
||||
public static AdditionalHealthEndpointPath of(WebServerNamespace webServerNamespace, String value) {
|
||||
Assert.notNull(webServerNamespace, "The server namespace must not be null.");
|
||||
Assert.notNull(value, "The value must not be null.");
|
||||
validateValue(value);
|
||||
return new AdditionalHealthEndpointPath(webServerNamespace, value);
|
||||
}
|
||||
|
||||
private static void validateValue(String value) {
|
||||
Assert.isTrue(StringUtils.countOccurrencesOf(value, "/") <= 1 && value.indexOf("/") <= 0,
|
||||
"Value must contain only one segment.");
|
||||
}
|
||||
|
||||
}
|
|
@ -20,6 +20,7 @@ import java.util.Map;
|
|||
import java.util.Set;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.ApiVersion;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointId;
|
||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||
|
@ -39,6 +40,11 @@ import org.springframework.boot.actuate.endpoint.annotation.Selector.Match;
|
|||
@Endpoint(id = "health")
|
||||
public class HealthEndpoint extends HealthEndpointSupport<HealthContributor, HealthComponent> {
|
||||
|
||||
/**
|
||||
* Health endpoint id.
|
||||
*/
|
||||
public static final EndpointId ID = EndpointId.of("health");
|
||||
|
||||
private static final String[] EMPTY_PATH = {};
|
||||
|
||||
/**
|
||||
|
@ -62,7 +68,7 @@ public class HealthEndpoint extends HealthEndpointSupport<HealthContributor, Hea
|
|||
}
|
||||
|
||||
private HealthComponent health(ApiVersion apiVersion, String... path) {
|
||||
HealthResult<HealthComponent> result = getHealth(apiVersion, SecurityContext.NONE, true, path);
|
||||
HealthResult<HealthComponent> result = getHealth(apiVersion, null, SecurityContext.NONE, true, path);
|
||||
return (result != null) ? result.getHealth() : null;
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
@ -23,6 +23,7 @@ import org.springframework.boot.actuate.endpoint.SecurityContext;
|
|||
* by the {@link HealthEndpoint}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Madhura Bhave
|
||||
* @since 2.2.0
|
||||
*/
|
||||
public interface HealthEndpointGroup {
|
||||
|
@ -62,4 +63,12 @@ public interface HealthEndpointGroup {
|
|||
*/
|
||||
HttpCodeStatusMapper getHttpCodeStatusMapper();
|
||||
|
||||
/**
|
||||
* Return an additional path that can be used to map the health group to an
|
||||
* alternative location.
|
||||
* @return the additional health path or {@code null}
|
||||
* @since 2.6.0
|
||||
*/
|
||||
AdditionalHealthEndpointPath getAdditionalPath();
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2019 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.
|
||||
|
@ -16,9 +16,11 @@
|
|||
|
||||
package org.springframework.boot.actuate.health;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
|
@ -48,6 +50,40 @@ public interface HealthEndpointGroups {
|
|||
*/
|
||||
HealthEndpointGroup get(String name);
|
||||
|
||||
/**
|
||||
* Return the group with the specified additional path or {@code null} if no group
|
||||
* with that path is found.
|
||||
* @param path the additional path
|
||||
* @return the matching {@link HealthEndpointGroup} or {@code null}
|
||||
* @since 2.6.0
|
||||
*/
|
||||
default HealthEndpointGroup get(AdditionalHealthEndpointPath path) {
|
||||
Assert.notNull(path, "Path must not be null");
|
||||
for (String name : getNames()) {
|
||||
HealthEndpointGroup group = get(name);
|
||||
if (path.equals(group.getAdditionalPath())) {
|
||||
return group;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all the groups with an additional path on the specified
|
||||
* {@link WebServerNamespace}.
|
||||
* @param namespace the {@link WebServerNamespace}
|
||||
* @return the matching groups
|
||||
* @since 2.6.0
|
||||
*/
|
||||
default Set<HealthEndpointGroup> getAllWithAdditionalPath(WebServerNamespace namespace) {
|
||||
Assert.notNull(namespace, "Namespace must not be null");
|
||||
Set<HealthEndpointGroup> filteredGroups = new LinkedHashSet<>();
|
||||
getNames().stream().map(this::get).filter(
|
||||
(group) -> group.getAdditionalPath() != null && group.getAdditionalPath().hasNamespace(namespace))
|
||||
.forEach(filteredGroups::add);
|
||||
return filteredGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create a {@link HealthEndpointGroups} instance.
|
||||
* @param primary the primary group
|
||||
|
|
|
@ -23,6 +23,7 @@ import java.util.stream.Collectors;
|
|||
|
||||
import org.springframework.boot.actuate.endpoint.ApiVersion;
|
||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
|
@ -53,14 +54,28 @@ abstract class HealthEndpointSupport<C, T> {
|
|||
this.groups = groups;
|
||||
}
|
||||
|
||||
HealthResult<T> getHealth(ApiVersion apiVersion, SecurityContext securityContext, boolean showAll, String... path) {
|
||||
HealthEndpointGroup group = (path.length > 0) ? this.groups.get(path[0]) : null;
|
||||
if (group != null) {
|
||||
return getHealth(apiVersion, group, securityContext, showAll, path, 1);
|
||||
HealthResult<T> getHealth(ApiVersion apiVersion, WebServerNamespace serverNamespace,
|
||||
SecurityContext securityContext, boolean showAll, String... path) {
|
||||
if (path.length > 0) {
|
||||
HealthEndpointGroup group = getHealthGroup(serverNamespace, path);
|
||||
if (group != null) {
|
||||
return getHealth(apiVersion, group, securityContext, showAll, path, 1);
|
||||
}
|
||||
}
|
||||
return getHealth(apiVersion, this.groups.getPrimary(), securityContext, showAll, path, 0);
|
||||
}
|
||||
|
||||
private HealthEndpointGroup getHealthGroup(WebServerNamespace serverNamespace, String... path) {
|
||||
if (this.groups.get(path[0]) != null) {
|
||||
return this.groups.get(path[0]);
|
||||
}
|
||||
if (serverNamespace != null) {
|
||||
AdditionalHealthEndpointPath additionalPath = AdditionalHealthEndpointPath.of(serverNamespace, path[0]);
|
||||
return this.groups.get(additionalPath);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private HealthResult<T> getHealth(ApiVersion apiVersion, HealthEndpointGroup group, SecurityContext securityContext,
|
||||
boolean showAll, String[] path, int pathOffset) {
|
||||
boolean showComponents = showAll || group.showComponents(securityContext);
|
||||
|
@ -71,8 +86,8 @@ abstract class HealthEndpointSupport<C, T> {
|
|||
return null;
|
||||
}
|
||||
Object contributor = getContributor(path, pathOffset);
|
||||
T health = getContribution(apiVersion, group, contributor, showComponents, showDetails,
|
||||
isSystemHealth ? this.groups.getNames() : null, false);
|
||||
Set<String> groupNames = isSystemHealth ? this.groups.getNames() : null;
|
||||
T health = getContribution(apiVersion, group, contributor, showComponents, showDetails, groupNames, false);
|
||||
return (health != null) ? new HealthResult<>(health, group) : null;
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ 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.endpoint.web.WebServerNamespace;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension;
|
||||
|
||||
/**
|
||||
|
@ -56,19 +57,20 @@ public class HealthEndpointWebExtension extends HealthEndpointSupport<HealthCont
|
|||
}
|
||||
|
||||
@ReadOperation
|
||||
public WebEndpointResponse<HealthComponent> health(ApiVersion apiVersion, SecurityContext securityContext) {
|
||||
return health(apiVersion, securityContext, false, NO_PATH);
|
||||
public WebEndpointResponse<HealthComponent> health(ApiVersion apiVersion, WebServerNamespace serverNamespace,
|
||||
SecurityContext securityContext) {
|
||||
return health(apiVersion, serverNamespace, securityContext, false, NO_PATH);
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
public WebEndpointResponse<HealthComponent> health(ApiVersion apiVersion, SecurityContext securityContext,
|
||||
@Selector(match = Match.ALL_REMAINING) String... path) {
|
||||
return health(apiVersion, securityContext, false, path);
|
||||
public WebEndpointResponse<HealthComponent> health(ApiVersion apiVersion, WebServerNamespace serverNamespace,
|
||||
SecurityContext securityContext, @Selector(match = Match.ALL_REMAINING) String... path) {
|
||||
return health(apiVersion, serverNamespace, securityContext, false, path);
|
||||
}
|
||||
|
||||
public WebEndpointResponse<HealthComponent> health(ApiVersion apiVersion, SecurityContext securityContext,
|
||||
boolean showAll, String... path) {
|
||||
HealthResult<HealthComponent> result = getHealth(apiVersion, securityContext, showAll, path);
|
||||
public WebEndpointResponse<HealthComponent> health(ApiVersion apiVersion, WebServerNamespace serverNamespace,
|
||||
SecurityContext securityContext, boolean showAll, String... path) {
|
||||
HealthResult<HealthComponent> result = getHealth(apiVersion, serverNamespace, securityContext, showAll, path);
|
||||
if (result == null) {
|
||||
return (Arrays.equals(path, NO_PATH))
|
||||
? new WebEndpointResponse<>(DEFAULT_HEALTH, WebEndpointResponse.STATUS_OK)
|
||||
|
|
|
@ -29,6 +29,7 @@ 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.endpoint.web.WebServerNamespace;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension;
|
||||
|
||||
/**
|
||||
|
@ -57,19 +58,21 @@ public class ReactiveHealthEndpointWebExtension
|
|||
|
||||
@ReadOperation
|
||||
public Mono<WebEndpointResponse<? extends HealthComponent>> health(ApiVersion apiVersion,
|
||||
SecurityContext securityContext) {
|
||||
return health(apiVersion, securityContext, false, NO_PATH);
|
||||
WebServerNamespace serverNamespace, SecurityContext securityContext) {
|
||||
return health(apiVersion, serverNamespace, securityContext, false, NO_PATH);
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
public Mono<WebEndpointResponse<? extends HealthComponent>> health(ApiVersion apiVersion,
|
||||
SecurityContext securityContext, @Selector(match = Match.ALL_REMAINING) String... path) {
|
||||
return health(apiVersion, securityContext, false, path);
|
||||
WebServerNamespace serverNamespace, SecurityContext securityContext,
|
||||
@Selector(match = Match.ALL_REMAINING) String... path) {
|
||||
return health(apiVersion, serverNamespace, securityContext, false, path);
|
||||
}
|
||||
|
||||
public Mono<WebEndpointResponse<? extends HealthComponent>> health(ApiVersion apiVersion,
|
||||
SecurityContext securityContext, boolean showAll, String... path) {
|
||||
HealthResult<Mono<? extends HealthComponent>> result = getHealth(apiVersion, securityContext, showAll, path);
|
||||
WebServerNamespace serverNamespace, SecurityContext securityContext, boolean showAll, String... path) {
|
||||
HealthResult<Mono<? extends HealthComponent>> result = getHealth(apiVersion, serverNamespace, securityContext,
|
||||
showAll, path);
|
||||
if (result == null) {
|
||||
return (Arrays.equals(path, NO_PATH))
|
||||
? Mono.just(new WebEndpointResponse<>(DEFAULT_HEALTH, WebEndpointResponse.STATUS_OK))
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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.
|
||||
* 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.endpoint.web;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link WebServerNamespace}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
class WebServerNamespaceTests {
|
||||
|
||||
@Test
|
||||
void fromWhenValueHasText() {
|
||||
assertThat(WebServerNamespace.from("management")).isEqualTo(WebServerNamespace.MANAGEMENT);
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromWhenValueIsNull() {
|
||||
assertThat(WebServerNamespace.from(null)).isEqualTo(WebServerNamespace.SERVER);
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromWhenValueIsEmpty() {
|
||||
assertThat(WebServerNamespace.from("")).isEqualTo(WebServerNamespace.SERVER);
|
||||
}
|
||||
|
||||
@Test
|
||||
void namespaceWithSameValueAreEqual() {
|
||||
assertThat(WebServerNamespace.from("value")).isEqualTo(WebServerNamespace.from("value"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void namespaceWithDifferentValuesAreNotEqual() {
|
||||
assertThat(WebServerNamespace.from("value")).isNotEqualTo(WebServerNamespace.from("other"));
|
||||
}
|
||||
|
||||
}
|
|
@ -137,7 +137,7 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
|
|||
@Test
|
||||
void matchAllRemainingPathsSelectorShouldDecodePath() {
|
||||
load(MatchAllRemainingEndpointConfiguration.class,
|
||||
(client) -> client.get().uri("/matchallremaining/one/two%20three/").exchange().expectStatus().isOk()
|
||||
(client) -> client.get().uri("/matchallremaining/one/two three/").exchange().expectStatus().isOk()
|
||||
.expectBody().jsonPath("selection").isEqualTo("one|two three"));
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* 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.
|
||||
* 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 org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Tests for {@link AdditionalHealthEndpointPath}.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
class AdditionalHealthEndpointPathTests {
|
||||
|
||||
@Test
|
||||
void fromValidPathShouldCreatePath() {
|
||||
AdditionalHealthEndpointPath path = AdditionalHealthEndpointPath.from("server:/my-path");
|
||||
assertThat(path.getValue()).isEqualTo("/my-path");
|
||||
assertThat(path.getNamespace()).isEqualTo(WebServerNamespace.SERVER);
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromValidPathWithoutSlashShouldCreatePath() {
|
||||
AdditionalHealthEndpointPath path = AdditionalHealthEndpointPath.from("server:my-path");
|
||||
assertThat(path.getValue()).isEqualTo("my-path");
|
||||
assertThat(path.getNamespace()).isEqualTo(WebServerNamespace.SERVER);
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromNullPathShouldThrowException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> AdditionalHealthEndpointPath.from(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromEmptyPathShouldThrowException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> AdditionalHealthEndpointPath.from(""));
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromPathWithNoNamespaceShouldThrowException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> AdditionalHealthEndpointPath.from("my-path"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromPathWithEmptyNamespaceShouldThrowException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> AdditionalHealthEndpointPath.from(":my-path"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromPathWithMultipleSegmentsShouldThrowException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> AdditionalHealthEndpointPath.from("server:/my-path/my-sub-path"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromPathWithMultipleSegmentsNotStartingWithSlashShouldThrowException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> AdditionalHealthEndpointPath.from("server:my-path/my-sub-path"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void pathsWithTheSameNamespaceAndValueAreEqual() {
|
||||
assertThat(AdditionalHealthEndpointPath.from("server:/my-path"))
|
||||
.isEqualTo(AdditionalHealthEndpointPath.from("server:/my-path"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void pathsWithTheDifferentNamespaceAndSameValueAreNotEqual() {
|
||||
assertThat(AdditionalHealthEndpointPath.from("server:/my-path"))
|
||||
.isNotEqualTo((AdditionalHealthEndpointPath.from("management:/my-path")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void pathsWithTheSameNamespaceAndValuesWithNoSlashAreEqual() {
|
||||
assertThat(AdditionalHealthEndpointPath.from("server:/my-path"))
|
||||
.isEqualTo((AdditionalHealthEndpointPath.from("server:my-path")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void ofWithNullNamespaceShouldThrowException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> AdditionalHealthEndpointPath.of(null, "my-sub-path"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void ofWithNullPathShouldThrowException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> AdditionalHealthEndpointPath.of(WebServerNamespace.SERVER, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void ofWithMultipleSegmentValueShouldThrowException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> AdditionalHealthEndpointPath.of(WebServerNamespace.SERVER, "/my-path/my-subpath"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void ofShouldCreatePath() {
|
||||
AdditionalHealthEndpointPath additionalPath = AdditionalHealthEndpointPath.of(WebServerNamespace.SERVER,
|
||||
"my-path");
|
||||
assertThat(additionalPath.getValue()).isEqualTo("my-path");
|
||||
assertThat(additionalPath.getNamespace()).isEqualTo(WebServerNamespace.SERVER);
|
||||
}
|
||||
|
||||
}
|
|
@ -24,6 +24,7 @@ import org.junit.jupiter.api.Test;
|
|||
|
||||
import org.springframework.boot.actuate.endpoint.ApiVersion;
|
||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
|
||||
import org.springframework.boot.actuate.health.HealthEndpointSupport.HealthResult;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
@ -72,7 +73,7 @@ abstract class HealthEndpointSupportTests<R extends ContributorRegistry<C>, C, T
|
|||
@Test
|
||||
void getHealthWhenPathIsEmptyUsesPrimaryGroup() {
|
||||
this.registry.registerContributor("test", createContributor(this.up));
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, SecurityContext.NONE,
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, null, SecurityContext.NONE,
|
||||
false);
|
||||
assertThat(result.getGroup()).isEqualTo(this.primaryGroup);
|
||||
assertThat(getHealth(result)).isNotSameAs(this.up);
|
||||
|
@ -82,7 +83,7 @@ abstract class HealthEndpointSupportTests<R extends ContributorRegistry<C>, C, T
|
|||
@Test
|
||||
void getHealthWhenPathIsNotGroupReturnsResultFromPrimaryGroup() {
|
||||
this.registry.registerContributor("test", createContributor(this.up));
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, SecurityContext.NONE,
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, null, SecurityContext.NONE,
|
||||
false, "test");
|
||||
assertThat(result.getGroup()).isEqualTo(this.primaryGroup);
|
||||
assertThat(getHealth(result)).isEqualTo(this.up);
|
||||
|
@ -92,7 +93,7 @@ abstract class HealthEndpointSupportTests<R extends ContributorRegistry<C>, C, T
|
|||
@Test
|
||||
void getHealthWhenPathIsGroupReturnsResultFromGroup() {
|
||||
this.registry.registerContributor("atest", createContributor(this.up));
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, SecurityContext.NONE,
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, null, SecurityContext.NONE,
|
||||
false, "alltheas", "atest");
|
||||
assertThat(result.getGroup()).isEqualTo(this.allTheAs);
|
||||
assertThat(getHealth(result)).isEqualTo(this.up);
|
||||
|
@ -103,7 +104,7 @@ abstract class HealthEndpointSupportTests<R extends ContributorRegistry<C>, C, T
|
|||
C contributor = createContributor(this.up);
|
||||
C compositeContributor = createCompositeContributor(Collections.singletonMap("spring", contributor));
|
||||
this.registry.registerContributor("test", compositeContributor);
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, SecurityContext.NONE,
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, null, SecurityContext.NONE,
|
||||
false, "test");
|
||||
CompositeHealth health = (CompositeHealth) getHealth(result);
|
||||
assertThat(health.getComponents()).containsKey("spring");
|
||||
|
@ -116,9 +117,9 @@ abstract class HealthEndpointSupportTests<R extends ContributorRegistry<C>, C, T
|
|||
C compositeContributor = createCompositeContributor(Collections.singletonMap("spring", contributor));
|
||||
this.registry.registerContributor("test", compositeContributor);
|
||||
HealthEndpointSupport<C, T> endpoint = create(this.registry, this.groups);
|
||||
HealthResult<T> rootResult = endpoint.getHealth(ApiVersion.V3, SecurityContext.NONE, false);
|
||||
HealthResult<T> rootResult = endpoint.getHealth(ApiVersion.V3, null, SecurityContext.NONE, false);
|
||||
assertThat(((CompositeHealth) getHealth(rootResult)).getComponents()).isNullOrEmpty();
|
||||
HealthResult<T> componentResult = endpoint.getHealth(ApiVersion.V3, SecurityContext.NONE, false, "test");
|
||||
HealthResult<T> componentResult = endpoint.getHealth(ApiVersion.V3, null, SecurityContext.NONE, false, "test");
|
||||
assertThat(componentResult).isNull();
|
||||
}
|
||||
|
||||
|
@ -129,16 +130,16 @@ abstract class HealthEndpointSupportTests<R extends ContributorRegistry<C>, C, T
|
|||
C compositeContributor = createCompositeContributor(Collections.singletonMap("spring", contributor));
|
||||
this.registry.registerContributor("test", compositeContributor);
|
||||
HealthEndpointSupport<C, T> endpoint = create(this.registry, this.groups);
|
||||
HealthResult<T> rootResult = endpoint.getHealth(ApiVersion.V3, SecurityContext.NONE, false);
|
||||
HealthResult<T> rootResult = endpoint.getHealth(ApiVersion.V3, null, SecurityContext.NONE, false);
|
||||
assertThat(((CompositeHealth) getHealth(rootResult)).getComponents()).containsKey("test");
|
||||
HealthResult<T> componentResult = endpoint.getHealth(ApiVersion.V3, SecurityContext.NONE, false, "test");
|
||||
HealthResult<T> componentResult = endpoint.getHealth(ApiVersion.V3, null, SecurityContext.NONE, false, "test");
|
||||
assertThat(((CompositeHealth) getHealth(componentResult)).getComponents()).containsKey("spring");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getHealthWhenAlwaysShowIsFalseAndGroupIsTrueShowsDetails() {
|
||||
this.registry.registerContributor("test", createContributor(this.up));
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, SecurityContext.NONE,
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, null, SecurityContext.NONE,
|
||||
false, "test");
|
||||
assertThat(((Health) getHealth(result)).getDetails()).containsEntry("spring", "boot");
|
||||
}
|
||||
|
@ -148,8 +149,8 @@ abstract class HealthEndpointSupportTests<R extends ContributorRegistry<C>, C, T
|
|||
this.primaryGroup.setShowDetails(false);
|
||||
this.registry.registerContributor("test", createContributor(this.up));
|
||||
HealthEndpointSupport<C, T> endpoint = create(this.registry, this.groups);
|
||||
HealthResult<T> rootResult = endpoint.getHealth(ApiVersion.V3, SecurityContext.NONE, false);
|
||||
HealthResult<T> componentResult = endpoint.getHealth(ApiVersion.V3, SecurityContext.NONE, false, "test");
|
||||
HealthResult<T> rootResult = endpoint.getHealth(ApiVersion.V3, null, SecurityContext.NONE, false);
|
||||
HealthResult<T> componentResult = endpoint.getHealth(ApiVersion.V3, null, SecurityContext.NONE, false, "test");
|
||||
assertThat(((CompositeHealth) getHealth(rootResult)).getStatus()).isEqualTo(Status.UP);
|
||||
assertThat(componentResult).isNull();
|
||||
}
|
||||
|
@ -158,8 +159,8 @@ abstract class HealthEndpointSupportTests<R extends ContributorRegistry<C>, C, T
|
|||
void getHealthWhenAlwaysShowIsTrueShowsDetails() {
|
||||
this.primaryGroup.setShowDetails(false);
|
||||
this.registry.registerContributor("test", createContributor(this.up));
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, SecurityContext.NONE, true,
|
||||
"test");
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, null, SecurityContext.NONE,
|
||||
true, "test");
|
||||
assertThat(((Health) getHealth(result)).getDetails()).containsEntry("spring", "boot");
|
||||
}
|
||||
|
||||
|
@ -169,7 +170,7 @@ abstract class HealthEndpointSupportTests<R extends ContributorRegistry<C>, C, T
|
|||
contributors.put("a", createContributor(this.up));
|
||||
contributors.put("b", createContributor(this.down));
|
||||
this.registry.registerContributor("test", createCompositeContributor(contributors));
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, SecurityContext.NONE,
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, null, SecurityContext.NONE,
|
||||
false);
|
||||
CompositeHealth root = (CompositeHealth) getHealth(result);
|
||||
CompositeHealth component = (CompositeHealth) root.getComponents().get("test");
|
||||
|
@ -180,7 +181,7 @@ abstract class HealthEndpointSupportTests<R extends ContributorRegistry<C>, C, T
|
|||
|
||||
@Test
|
||||
void getHealthWhenPathDoesNotExistReturnsNull() {
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, SecurityContext.NONE,
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, null, SecurityContext.NONE,
|
||||
false, "missing");
|
||||
assertThat(result).isNull();
|
||||
}
|
||||
|
@ -188,7 +189,7 @@ abstract class HealthEndpointSupportTests<R extends ContributorRegistry<C>, C, T
|
|||
@Test
|
||||
void getHealthWhenPathIsEmptyIncludesGroups() {
|
||||
this.registry.registerContributor("test", createContributor(this.up));
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, SecurityContext.NONE,
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, null, SecurityContext.NONE,
|
||||
false);
|
||||
assertThat(((SystemHealth) getHealth(result)).getGroups()).containsOnly("alltheas");
|
||||
}
|
||||
|
@ -196,7 +197,7 @@ abstract class HealthEndpointSupportTests<R extends ContributorRegistry<C>, C, T
|
|||
@Test
|
||||
void getHealthWhenPathIsGroupDoesNotIncludesGroups() {
|
||||
this.registry.registerContributor("atest", createContributor(this.up));
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, SecurityContext.NONE,
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, null, SecurityContext.NONE,
|
||||
false, "alltheas");
|
||||
assertThat(getHealth(result)).isNotInstanceOf(SystemHealth.class);
|
||||
}
|
||||
|
@ -204,7 +205,7 @@ abstract class HealthEndpointSupportTests<R extends ContributorRegistry<C>, C, T
|
|||
@Test
|
||||
void getHealthWithEmptyCompositeReturnsNullResult() { // gh-18687
|
||||
this.registry.registerContributor("test", createCompositeContributor(Collections.emptyMap()));
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, SecurityContext.NONE,
|
||||
HealthResult<T> result = create(this.registry, this.groups).getHealth(ApiVersion.V3, null, SecurityContext.NONE,
|
||||
false);
|
||||
assertThat(result).isNull();
|
||||
}
|
||||
|
@ -217,12 +218,53 @@ abstract class HealthEndpointSupportTests<R extends ContributorRegistry<C>, C, T
|
|||
TestHealthEndpointGroup testGroup = new TestHealthEndpointGroup((name) -> name.startsWith("test"));
|
||||
HealthEndpointGroups groups = HealthEndpointGroups.of(this.primaryGroup,
|
||||
Collections.singletonMap("testGroup", testGroup));
|
||||
HealthResult<T> result = create(this.registry, groups).getHealth(ApiVersion.V3, SecurityContext.NONE, false,
|
||||
"testGroup");
|
||||
HealthResult<T> result = create(this.registry, groups).getHealth(ApiVersion.V3, null, SecurityContext.NONE,
|
||||
false, "testGroup");
|
||||
CompositeHealth health = (CompositeHealth) getHealth(result);
|
||||
assertThat(health.getComponents()).containsKey("test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getHealthWhenGroupHasAdditionalPath() {
|
||||
this.registry.registerContributor("test", createContributor(this.up));
|
||||
TestHealthEndpointGroup testGroup = new TestHealthEndpointGroup((name) -> name.startsWith("test"));
|
||||
testGroup.setAdditionalPath(AdditionalHealthEndpointPath.from("server:/healthz"));
|
||||
HealthEndpointGroups groups = HealthEndpointGroups.of(this.primaryGroup,
|
||||
Collections.singletonMap("testGroup", testGroup));
|
||||
HealthResult<T> result = create(this.registry, groups).getHealth(ApiVersion.V3, WebServerNamespace.SERVER,
|
||||
SecurityContext.NONE, false, "healthz");
|
||||
CompositeHealth health = (CompositeHealth) getHealth(result);
|
||||
assertThat(health.getComponents()).containsKey("test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getHealthWhenGroupHasAdditionalPathAndShowComponentsFalse() {
|
||||
this.registry.registerContributor("test", createContributor(this.up));
|
||||
TestHealthEndpointGroup testGroup = new TestHealthEndpointGroup((name) -> name.startsWith("test"));
|
||||
testGroup.setAdditionalPath(AdditionalHealthEndpointPath.from("server:/healthz"));
|
||||
testGroup.setShowComponents(false);
|
||||
HealthEndpointGroups groups = HealthEndpointGroups.of(this.primaryGroup,
|
||||
Collections.singletonMap("testGroup", testGroup));
|
||||
HealthResult<T> result = create(this.registry, groups).getHealth(ApiVersion.V3, WebServerNamespace.SERVER,
|
||||
SecurityContext.NONE, false, "healthz");
|
||||
CompositeHealth health = (CompositeHealth) getHealth(result);
|
||||
assertThat(health.getStatus().getCode()).isEqualTo("UP");
|
||||
assertThat(health.getComponents()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getComponentHealthWhenGroupHasAdditionalPathAndShowComponentsFalse() {
|
||||
this.registry.registerContributor("test", createContributor(this.up));
|
||||
TestHealthEndpointGroup testGroup = new TestHealthEndpointGroup((name) -> name.startsWith("test"));
|
||||
testGroup.setAdditionalPath(AdditionalHealthEndpointPath.from("server:/healthz"));
|
||||
testGroup.setShowComponents(false);
|
||||
HealthEndpointGroups groups = HealthEndpointGroups.of(this.primaryGroup,
|
||||
Collections.singletonMap("testGroup", testGroup));
|
||||
HealthResult<T> result = create(this.registry, groups).getHealth(ApiVersion.V3, WebServerNamespace.SERVER,
|
||||
SecurityContext.NONE, false, "healthz", "test");
|
||||
assertThat(result).isEqualTo(null);
|
||||
}
|
||||
|
||||
protected abstract HealthEndpointSupport<C, T> create(R registry, HealthEndpointGroups groups);
|
||||
|
||||
protected abstract R createRegistry();
|
||||
|
|
|
@ -24,6 +24,7 @@ import org.junit.jupiter.api.Test;
|
|||
import org.springframework.boot.actuate.endpoint.ApiVersion;
|
||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
|
||||
import org.springframework.boot.actuate.health.HealthEndpointSupport.HealthResult;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
@ -42,7 +43,7 @@ class HealthEndpointWebExtensionTests
|
|||
void healthReturnsSystemHealth() {
|
||||
this.registry.registerContributor("test", createContributor(this.up));
|
||||
WebEndpointResponse<HealthComponent> response = create(this.registry, this.groups).health(ApiVersion.LATEST,
|
||||
SecurityContext.NONE);
|
||||
WebServerNamespace.SERVER, SecurityContext.NONE);
|
||||
HealthComponent health = response.getBody();
|
||||
assertThat(health.getStatus()).isEqualTo(Status.UP);
|
||||
assertThat(health).isInstanceOf(SystemHealth.class);
|
||||
|
@ -54,7 +55,7 @@ class HealthEndpointWebExtensionTests
|
|||
assertThat(this.registry).isEmpty();
|
||||
WebEndpointResponse<HealthComponent> response = create(this.registry,
|
||||
HealthEndpointGroups.of(mock(HealthEndpointGroup.class), Collections.emptyMap()))
|
||||
.health(ApiVersion.LATEST, SecurityContext.NONE);
|
||||
.health(ApiVersion.LATEST, WebServerNamespace.SERVER, SecurityContext.NONE);
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
HealthComponent health = response.getBody();
|
||||
assertThat(health.getStatus()).isEqualTo(Status.UP);
|
||||
|
@ -65,7 +66,7 @@ class HealthEndpointWebExtensionTests
|
|||
void healthWhenPathDoesNotExistReturnsHttp404() {
|
||||
this.registry.registerContributor("test", createContributor(this.up));
|
||||
WebEndpointResponse<HealthComponent> response = create(this.registry, this.groups).health(ApiVersion.LATEST,
|
||||
SecurityContext.NONE, "missing");
|
||||
WebServerNamespace.SERVER, SecurityContext.NONE, "missing");
|
||||
assertThat(response.getBody()).isNull();
|
||||
assertThat(response.getStatus()).isEqualTo(404);
|
||||
}
|
||||
|
@ -74,7 +75,7 @@ class HealthEndpointWebExtensionTests
|
|||
void healthWhenPathExistsReturnsHealth() {
|
||||
this.registry.registerContributor("test", createContributor(this.up));
|
||||
WebEndpointResponse<HealthComponent> response = create(this.registry, this.groups).health(ApiVersion.LATEST,
|
||||
SecurityContext.NONE, "test");
|
||||
WebServerNamespace.SERVER, SecurityContext.NONE, "test");
|
||||
assertThat(response.getBody()).isEqualTo(this.up);
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ class ReactiveHealthEndpointWebExtensionTests extends
|
|||
void healthReturnsSystemHealth() {
|
||||
this.registry.registerContributor("test", createContributor(this.up));
|
||||
WebEndpointResponse<? extends HealthComponent> response = create(this.registry, this.groups)
|
||||
.health(ApiVersion.LATEST, SecurityContext.NONE).block();
|
||||
.health(ApiVersion.LATEST, null, SecurityContext.NONE).block();
|
||||
HealthComponent health = response.getBody();
|
||||
assertThat(health.getStatus()).isEqualTo(Status.UP);
|
||||
assertThat(health).isInstanceOf(SystemHealth.class);
|
||||
|
@ -55,7 +55,7 @@ class ReactiveHealthEndpointWebExtensionTests extends
|
|||
assertThat(this.registry).isEmpty();
|
||||
WebEndpointResponse<? extends HealthComponent> response = create(this.registry,
|
||||
HealthEndpointGroups.of(mock(HealthEndpointGroup.class), Collections.emptyMap()))
|
||||
.health(ApiVersion.LATEST, SecurityContext.NONE).block();
|
||||
.health(ApiVersion.LATEST, null, SecurityContext.NONE).block();
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
HealthComponent health = response.getBody();
|
||||
assertThat(health.getStatus()).isEqualTo(Status.UP);
|
||||
|
@ -66,7 +66,7 @@ class ReactiveHealthEndpointWebExtensionTests extends
|
|||
void healthWhenPathDoesNotExistReturnsHttp404() {
|
||||
this.registry.registerContributor("test", createContributor(this.up));
|
||||
WebEndpointResponse<? extends HealthComponent> response = create(this.registry, this.groups)
|
||||
.health(ApiVersion.LATEST, SecurityContext.NONE, "missing").block();
|
||||
.health(ApiVersion.LATEST, null, SecurityContext.NONE, "missing").block();
|
||||
assertThat(response.getBody()).isNull();
|
||||
assertThat(response.getStatus()).isEqualTo(404);
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ class ReactiveHealthEndpointWebExtensionTests extends
|
|||
void healthWhenPathExistsReturnsHealth() {
|
||||
this.registry.registerContributor("test", createContributor(this.up));
|
||||
WebEndpointResponse<? extends HealthComponent> response = create(this.registry, this.groups)
|
||||
.health(ApiVersion.LATEST, SecurityContext.NONE, "test").block();
|
||||
.health(ApiVersion.LATEST, null, SecurityContext.NONE, "test").block();
|
||||
assertThat(response.getBody()).isEqualTo(this.up);
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2019 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.
|
||||
|
@ -37,6 +37,8 @@ class TestHealthEndpointGroup implements HealthEndpointGroup {
|
|||
|
||||
private boolean showDetails = true;
|
||||
|
||||
private AdditionalHealthEndpointPath additionalPath;
|
||||
|
||||
TestHealthEndpointGroup() {
|
||||
this((name) -> true);
|
||||
}
|
||||
|
@ -78,4 +80,13 @@ class TestHealthEndpointGroup implements HealthEndpointGroup {
|
|||
return this.httpCodeStatusMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdditionalHealthEndpointPath getAdditionalPath() {
|
||||
return this.additionalPath;
|
||||
}
|
||||
|
||||
void setAdditionalPath(AdditionalHealthEndpointPath additionalPath) {
|
||||
this.additionalPath = additionalPath;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -934,6 +934,20 @@ It's also possible to override the `show-details` and `roles` properties if requ
|
|||
|
||||
TIP: You can use `@Qualifier("groupname")` if you need to register custom `StatusAggregator` or `HttpCodeStatusMapper` beans for use with the group.
|
||||
|
||||
Health groups can be made available at an additional path on either the main or management port.
|
||||
This is useful in cloud environments such as Kubernetes, where it is quite common to use a separate management port for the actuator endpoints for security purposes.
|
||||
Having a separate port could lead to unreliable health checks because the main application might not work properly even if the health check is successful.
|
||||
The health group can be configured with an additional path as follows:
|
||||
|
||||
[source,properties,indent=0,subs="verbatim"]
|
||||
----
|
||||
management.endpoint.health.group.live.additional-path="server:/healthz"
|
||||
----
|
||||
|
||||
This would make the `live` health group available on the main server port at `/healthz`.
|
||||
The prefix is mandatory and must be either `server:` (represents the main server port) or `management:` (represents the management port, if configured.)
|
||||
The path must be a single path segment.
|
||||
|
||||
|
||||
|
||||
[[actuator.endpoints.health.datasource]]
|
||||
|
@ -983,8 +997,18 @@ You can enable them in any environment using the configprop:management.endpoint.
|
|||
NOTE: If an application takes longer to start than the configured liveness period, Kubernetes mention the `"startupProbe"` as a possible solution.
|
||||
The `"startupProbe"` is not necessarily needed here as the `"readinessProbe"` fails until all startup tasks are done, see <<actuator#actuator.endpoints.kubernetes-probes.lifecycle,how Probes behave during the application lifecycle>>.
|
||||
|
||||
WARNING: If your Actuator endpoints are deployed on a separate management context, be aware that endpoints are then not using the same web infrastructure (port, connection pools, framework components) as the main application.
|
||||
If your Actuator endpoints are deployed on a separate management context, the endpoints do not use the same web infrastructure (port, connection pools, framework components) as the main application.
|
||||
In this case, a probe check could be successful even if the main application does not work properly (for example, it cannot accept new connections).
|
||||
For this reason, is it a good idea to make the `liveness` and `readiness` health groups available on the main server port.
|
||||
This can be done by setting the following property:
|
||||
|
||||
[source,properties,indent=0,subs="verbatim"]
|
||||
----
|
||||
management.endpoint.health.probes.add-additional-paths=true
|
||||
----
|
||||
|
||||
This would make `liveness` available at `/livez` and `readiness` at `readyz` on the main server port.
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2019 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.
|
||||
|
@ -58,4 +58,18 @@ public interface WebServerApplicationContext extends ApplicationContext {
|
|||
.nullSafeEquals(((WebServerApplicationContext) context).getServerNamespace(), serverNamespace);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the server namespace if the specified context is a
|
||||
* {@link WebServerApplicationContext}.
|
||||
* @param context the context
|
||||
* @return the server namespace or {@code null} if the context is not a
|
||||
* {@link WebServerApplicationContext}
|
||||
* @since 2.6.0
|
||||
*/
|
||||
static String getServerNamepace(ApplicationContext context) {
|
||||
return (context instanceof WebServerApplicationContext)
|
||||
? ((WebServerApplicationContext) context).getServerNamespace() : null;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue