Merge branch '2.2.x'
This commit is contained in:
@@ -30,9 +30,11 @@
|
||||
|spring.cloud.loadbalancer.cache.caffeine.spec | | The spec to use to create caches. See CaffeineSpec for more details on the spec format.
|
||||
|spring.cloud.loadbalancer.cache.capacity | 256 | Initial cache capacity expressed as int.
|
||||
|spring.cloud.loadbalancer.cache.ttl | 30s | Time To Live - time counted from writing of the record, after which cache entries are expired, expressed as a {@link Duration}. The property {@link String} has to be in keeping with the appropriate syntax as specified in Spring Boot <code>StringToDurationConverter</code>. @see <a href= "https://github.com/spring-projects/spring-boot/blob/master/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/StringToDurationConverter.java">StringToDurationConverter.java</a>
|
||||
|spring.cloud.loadbalancer.health-check.initial-delay | 0 | Initial delay value for the HealthCheck scheduler.
|
||||
|spring.cloud.loadbalancer.health-check.interval | 30s | Interval for rerunning the HealthCheck scheduler.
|
||||
|spring.cloud.loadbalancer.health-check.path | |
|
||||
|spring.cloud.loadbalancer.retry.enabled | true |
|
||||
|spring.cloud.loadbalancer.ribbon.enabled | true | Causes `RibbonLoadBalancerClient` to be used by default.
|
||||
|spring.cloud.loadbalancer.zone | | A {@link String} representation of the <code>zone</code> used for filtering instances by zoned load-balancing implementations.
|
||||
|spring.cloud.refresh.enabled | true | Enables autoconfiguration for the refresh scope and associated features.
|
||||
|spring.cloud.refresh.extra-refreshable | true | Additional class names for beans to post process into refresh scope.
|
||||
|spring.cloud.service-registry.auto-registration.enabled | true | Whether service auto-registration is enabled. Defaults to true.
|
||||
|
||||
@@ -906,12 +906,71 @@ public class CustomLoadBalancerConfiguration {
|
||||
@Bean
|
||||
public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier(
|
||||
ReactiveDiscoveryClient discoveryClient, Environment environment,
|
||||
LoadBalancerProperties loadBalancerProperties,
|
||||
LoadBalancerZoneConfig zoneConfig,
|
||||
ApplicationContext context) {
|
||||
DiscoveryClientServiceInstanceListSupplier firstDelegate = new DiscoveryClientServiceInstanceListSupplier(
|
||||
discoveryClient, environment);
|
||||
ZonePreferenceServiceInstanceListSupplier delegate = new ZonePreferenceServiceInstanceListSupplier(firstDelegate,
|
||||
loadBalancerProperties);
|
||||
zoneConfig);
|
||||
ObjectProvider<LoadBalancerCacheManager> cacheManagerProvider = context
|
||||
.getBeanProvider(LoadBalancerCacheManager.class);
|
||||
if (cacheManagerProvider.getIfAvailable() != null) {
|
||||
return new CachingServiceInstanceListSupplier(delegate,
|
||||
cacheManagerProvider.getIfAvailable());
|
||||
}
|
||||
return delegate;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
=== Instance Health-Check for LoadBalancer
|
||||
|
||||
It is possible to enable a scheduled HealthCheck for the LoadBalancer. The `HealthCheckServiceInstanceListSupplier`
|
||||
is provided for that. It regularly verifies if the instances provided by a delegate
|
||||
`ServiceInstanceListSupplier` are still alive and only returns the healthy instances,
|
||||
unless there are none - then it returns all the retrieved instances.
|
||||
|
||||
TIP: This mechanism is particularly helpful while using the `SimpleDiscoveryClient`. For the
|
||||
clients backed by an actual Service Registry, it's not necessary to use, as we already get
|
||||
healthy instances after querying the external ServiceDiscovery.
|
||||
|
||||
The `HealthCheckServiceInstanceListSupplier` uses `InstanceHealthChecker` to verify if the instances are
|
||||
alive. We provide a default `PingHealthChecker` instance. It uses `WebClient` to execute
|
||||
requests against the `health` endpoint of the instance. You can also provide your own implementation
|
||||
of `InstanceHealthChecker` instead.
|
||||
|
||||
`HealthCheckServiceInstanceListSupplier` uses properties prefixed with
|
||||
`spring.cloud.loadbalancer.healthcheck`. You can set the `initialDelay` and `interval`
|
||||
for the scheduler.
|
||||
|
||||
For the `PingHealthChecker`, you can set the default path for the healthcheck URL by setting
|
||||
the value of the `spring.cloud.loadbalancer.healthcheck.path.default`. You can also set a specific value
|
||||
for any given service by setting the value of the `spring.cloud.loadbalancer.healthcheck.path.[SERVICE_ID]`,
|
||||
substituting the `[SERVICE_ID]` with the correct ID of your service. If the path is not set,
|
||||
`/actuator/health` is used by default.
|
||||
|
||||
In order to use the health-check scheduler approach, you will have to instantiate a `HealthCheckServiceInstanceListSupplier` bean in a <<custom-loadbalancer-configuration,custom configuration>>.
|
||||
|
||||
We use delegates to work with `ServiceInstanceListSupplier` beans.
|
||||
We suggest passing a `DiscoveryClientServiceInstanceListSupplier` delegate in the constructor of `HealthCheckServiceInstanceListSupplier` and, in turn, wrapping the latter with a `CachingServiceInstanceListSupplier` to leverage <<loadbalancer-caching, LoadBalancer caching mechanism>>.
|
||||
|
||||
You could use this sample configuration to set it up:
|
||||
|
||||
[[zoned-based-custom-loadbalancer-configuration]]
|
||||
[source,java,indent=0]
|
||||
----
|
||||
public class CustomLoadBalancerConfiguration {
|
||||
|
||||
@Bean
|
||||
public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier(
|
||||
ReactiveDiscoveryClient discoveryClient, Environment environment,
|
||||
LoadBalancerProperties loadBalancerProperties,
|
||||
ApplicationContext context,
|
||||
InstanceHealthChecker healthChecker) {
|
||||
DiscoveryClientServiceInstanceListSupplier firstDelegate = new DiscoveryClientServiceInstanceListSupplier(
|
||||
discoveryClient, environment);
|
||||
HealthCheckServiceInstanceListSupplier delegate = new HealthCheckServiceInstanceListSupplier(firstDelegate,
|
||||
loadBalancerProperties, healthChecker);
|
||||
ObjectProvider<LoadBalancerCacheManager> cacheManagerProvider = context
|
||||
.getBeanProvider(LoadBalancerCacheManager.class);
|
||||
if (cacheManagerProvider.getIfAvailable() != null) {
|
||||
|
||||
@@ -16,7 +16,11 @@
|
||||
|
||||
package org.springframework.cloud.client.loadbalancer.reactive;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.util.LinkedCaseInsensitiveMap;
|
||||
|
||||
/**
|
||||
* A {@link ConfigurationProperties} bean for Spring Cloud LoadBalancer.
|
||||
@@ -28,17 +32,56 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
public class LoadBalancerProperties {
|
||||
|
||||
/**
|
||||
* A {@link String} representation of the <code>zone</code> used for filtering
|
||||
* instances by zoned load-balancing implementations.
|
||||
* Properties for <code>HealthCheckServiceInstanceListSupplier</code>.
|
||||
*/
|
||||
private String zone;
|
||||
private HealthCheck healthCheck = new HealthCheck();
|
||||
|
||||
public String getZone() {
|
||||
return zone;
|
||||
public HealthCheck getHealthCheck() {
|
||||
return healthCheck;
|
||||
}
|
||||
|
||||
public void setZone(String zone) {
|
||||
this.zone = zone;
|
||||
public void setHealthCheck(HealthCheck healthCheck) {
|
||||
this.healthCheck = healthCheck;
|
||||
}
|
||||
|
||||
public static class HealthCheck {
|
||||
|
||||
/**
|
||||
* Initial delay value for the HealthCheck scheduler.
|
||||
*/
|
||||
private int initialDelay = 0;
|
||||
|
||||
/**
|
||||
* Interval for rerunning the HealthCheck scheduler.
|
||||
*/
|
||||
private Duration interval = Duration.ofSeconds(30);
|
||||
|
||||
private Map<String, String> path = new LinkedCaseInsensitiveMap<>();
|
||||
|
||||
public int getInitialDelay() {
|
||||
return initialDelay;
|
||||
}
|
||||
|
||||
public void setInitialDelay(int initialDelay) {
|
||||
this.initialDelay = initialDelay;
|
||||
}
|
||||
|
||||
public Map<String, String> getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public void setPath(Map<String, String> path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public Duration getInterval() {
|
||||
return interval;
|
||||
}
|
||||
|
||||
public void setInterval(Duration interval) {
|
||||
this.interval = interval;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,14 +19,12 @@ package org.springframework.cloud.loadbalancer.annotation;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.client.ConditionalOnBlockingDiscoveryEnabled;
|
||||
import org.springframework.cloud.client.ConditionalOnDiscoveryEnabled;
|
||||
import org.springframework.cloud.client.ConditionalOnReactiveDiscoveryEnabled;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.discovery.DiscoveryClient;
|
||||
import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerProperties;
|
||||
import org.springframework.cloud.loadbalancer.cache.LoadBalancerCacheManager;
|
||||
import org.springframework.cloud.loadbalancer.core.CachingServiceInstanceListSupplier;
|
||||
import org.springframework.cloud.loadbalancer.core.CachingServiceInstanceSupplier;
|
||||
@@ -49,18 +47,11 @@ import org.springframework.core.env.Environment;
|
||||
* @author Tim Ysewyn
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableConfigurationProperties(LoadBalancerProperties.class)
|
||||
@ConditionalOnDiscoveryEnabled
|
||||
public class LoadBalancerClientConfiguration {
|
||||
|
||||
private static final int REACTIVE_SERVICE_INSTANCE_SUPPLIER_ORDER = 193827465;
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
LoadBalancerProperties loadBalancerProperties() {
|
||||
return new LoadBalancerProperties();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(
|
||||
|
||||
@@ -22,7 +22,9 @@ import java.util.List;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerBeanPostProcessorAutoConfiguration;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerProperties;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancerAutoConfiguration;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerClientAutoConfiguration;
|
||||
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClientSpecification;
|
||||
@@ -30,6 +32,7 @@ import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
|
||||
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
@@ -37,6 +40,7 @@ import org.springframework.context.annotation.Configuration;
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@LoadBalancerClients
|
||||
@EnableConfigurationProperties(LoadBalancerProperties.class)
|
||||
@AutoConfigureBefore({ ReactorLoadBalancerClientAutoConfiguration.class,
|
||||
LoadBalancerBeanPostProcessorAutoConfiguration.class,
|
||||
ReactiveLoadBalancerAutoConfiguration.class })
|
||||
@@ -49,6 +53,13 @@ public class LoadBalancerAutoConfiguration {
|
||||
this.configurations = configurations;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public LoadBalancerZoneConfig zoneConfig(Environment environment) {
|
||||
return new LoadBalancerZoneConfig(
|
||||
environment.getProperty("spring.cloud.loadbalancer.zone"));
|
||||
}
|
||||
|
||||
@ConditionalOnMissingBean
|
||||
@Bean
|
||||
public LoadBalancerClientFactory loadBalancerClientFactory() {
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.loadbalancer.config;
|
||||
|
||||
/**
|
||||
* @author Olga Maciaszek-Sharma
|
||||
*/
|
||||
public class LoadBalancerZoneConfig {
|
||||
|
||||
/**
|
||||
* A {@link String} representation of the <code>zone</code> used for filtering
|
||||
* instances by zoned load-balancing implementations.
|
||||
*/
|
||||
private String zone;
|
||||
|
||||
public LoadBalancerZoneConfig(String zone) {
|
||||
this.zone = zone;
|
||||
}
|
||||
|
||||
public String getZone() {
|
||||
return zone;
|
||||
}
|
||||
|
||||
public void setZone(String zone) {
|
||||
this.zone = zone;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.loadbalancer.core;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.FluxSink;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerProperties;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
* A {@link ServiceInstanceListSupplier} implementation that verifies whether the
|
||||
* instances are alive and only returns the healthy one, unless there are none. Uses
|
||||
* {@link WebClient} to ping the <code>health</code> endpoint of the instances.
|
||||
*
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @since 2.2.0
|
||||
*/
|
||||
public class HealthCheckServiceInstanceListSupplier
|
||||
implements ServiceInstanceListSupplier {
|
||||
|
||||
private static final Log LOG = LogFactory
|
||||
.getLog(HealthCheckServiceInstanceListSupplier.class);
|
||||
|
||||
private final ServiceInstanceListSupplier delegate;
|
||||
|
||||
private final LoadBalancerProperties.HealthCheck healthCheck;
|
||||
|
||||
private final WebClient webClient;
|
||||
|
||||
private final String defaultHealthCheckPath;
|
||||
|
||||
private List<ServiceInstance> instances = Collections
|
||||
.synchronizedList(new ArrayList<>());
|
||||
|
||||
private List<ServiceInstance> healthyInstances = Collections
|
||||
.synchronizedList(new ArrayList<>());
|
||||
|
||||
public HealthCheckServiceInstanceListSupplier(ServiceInstanceListSupplier delegate,
|
||||
LoadBalancerProperties.HealthCheck healthCheck, WebClient webClient) {
|
||||
this.delegate = delegate;
|
||||
this.healthCheck = healthCheck;
|
||||
defaultHealthCheckPath = healthCheck.getPath().getOrDefault("default",
|
||||
"/actuator/health");
|
||||
this.webClient = webClient;
|
||||
initInstances();
|
||||
|
||||
}
|
||||
|
||||
private void initInstances() {
|
||||
delegate.get().subscribe(delegateInstances -> {
|
||||
instances.clear();
|
||||
instances.addAll(delegateInstances);
|
||||
});
|
||||
|
||||
Flux<List<ServiceInstance>> healthCheckFlux = healthCheckFlux();
|
||||
|
||||
healthCheckFlux.subscribe(verifiedInstances -> {
|
||||
healthyInstances.clear();
|
||||
healthyInstances.addAll(verifiedInstances);
|
||||
});
|
||||
}
|
||||
|
||||
protected Flux<List<ServiceInstance>> healthCheckFlux() {
|
||||
return Flux.create(emitter -> Schedulers
|
||||
.newSingle("Health Check Verifier: " + getServiceId(), true)
|
||||
.schedulePeriodically(() -> {
|
||||
List<ServiceInstance> verifiedInstances = new ArrayList<>();
|
||||
Flux.fromIterable(instances).filterWhen(this::isAlive)
|
||||
.subscribe(serviceInstance -> {
|
||||
verifiedInstances.add(serviceInstance);
|
||||
emitter.next(verifiedInstances);
|
||||
});
|
||||
}, healthCheck.getInitialDelay(), healthCheck.getInterval().toMillis(),
|
||||
TimeUnit.MILLISECONDS),
|
||||
FluxSink.OverflowStrategy.LATEST);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServiceId() {
|
||||
return delegate.getServiceId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<List<ServiceInstance>> get() {
|
||||
if (!healthyInstances.isEmpty()) {
|
||||
return Flux.defer(() -> Flux.fromIterable(healthyInstances).collectList());
|
||||
}
|
||||
// If there are no healthy instances, it might be better to still retry on all of
|
||||
// them
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn(
|
||||
"No verified healthy instances were found, returning all listed instances.");
|
||||
}
|
||||
return Flux.defer(() -> Flux.fromIterable(instances).collectList());
|
||||
}
|
||||
|
||||
protected Mono<Boolean> isAlive(ServiceInstance serviceInstance) {
|
||||
String healthCheckPropertyValue = healthCheck.getPath()
|
||||
.get(serviceInstance.getServiceId());
|
||||
String healthCheckPath = healthCheckPropertyValue != null
|
||||
? healthCheckPropertyValue : defaultHealthCheckPath;
|
||||
return webClient.get()
|
||||
.uri(UriComponentsBuilder.fromUri(serviceInstance.getUri())
|
||||
.path(healthCheckPath).build().toUri())
|
||||
.exchange()
|
||||
.map(clientResponse -> HttpStatus.OK.equals(clientResponse.statusCode()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -23,7 +23,7 @@ import java.util.Map;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerProperties;
|
||||
import org.springframework.cloud.loadbalancer.config.LoadBalancerZoneConfig;
|
||||
|
||||
/**
|
||||
* An implementation of {@link ServiceInstanceListSupplier} that filters instances
|
||||
@@ -42,14 +42,14 @@ public class ZonePreferenceServiceInstanceListSupplier
|
||||
|
||||
private final ServiceInstanceListSupplier delegate;
|
||||
|
||||
private final LoadBalancerProperties loadBalancerProperties;
|
||||
private final LoadBalancerZoneConfig zoneConfig;
|
||||
|
||||
private String zone;
|
||||
|
||||
public ZonePreferenceServiceInstanceListSupplier(ServiceInstanceListSupplier delegate,
|
||||
LoadBalancerProperties loadBalancerProperties) {
|
||||
LoadBalancerZoneConfig zoneConfig) {
|
||||
this.delegate = delegate;
|
||||
this.loadBalancerProperties = loadBalancerProperties;
|
||||
this.zoneConfig = zoneConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -64,7 +64,7 @@ public class ZonePreferenceServiceInstanceListSupplier
|
||||
|
||||
private List<ServiceInstance> filteredByZone(List<ServiceInstance> serviceInstances) {
|
||||
if (zone == null) {
|
||||
zone = loadBalancerProperties.getZone();
|
||||
zone = zoneConfig.getZone();
|
||||
}
|
||||
if (zone != null) {
|
||||
List<ServiceInstance> filteredInstances = new ArrayList<>();
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.loadbalancer.core;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.web.server.LocalServerPort;
|
||||
import org.springframework.cloud.client.DefaultServiceInstance;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link HealthCheckServiceInstanceListSupplier}.
|
||||
*
|
||||
* @author Olga Maciaszek-Sharma
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@SpringBootTest(
|
||||
classes = HealthCheckServiceInstanceListSupplierTests.TestApplication.class,
|
||||
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
class HealthCheckServiceInstanceListSupplierTests {
|
||||
|
||||
@LocalServerPort
|
||||
private int port;
|
||||
|
||||
private final WebClient webClient = WebClient.create();
|
||||
|
||||
private LoadBalancerProperties.HealthCheck healthCheck = new LoadBalancerProperties.HealthCheck();
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@Test
|
||||
void shouldCheckInstanceWithProvidedHealthCheckPath() {
|
||||
healthCheck.getPath().put("ignored-service", "/health");
|
||||
HealthCheckServiceInstanceListSupplier listSupplier = new HealthCheckServiceInstanceListSupplier(
|
||||
ServiceInstanceListSupplier.FixedServiceInstanceListSupplier
|
||||
.with(new MockEnvironment()).build(),
|
||||
healthCheck, webClient);
|
||||
ServiceInstance serviceInstance = new DefaultServiceInstance("ignored-service-1",
|
||||
"ignored-service", "127.0.0.1", port, false);
|
||||
|
||||
boolean alive = listSupplier.isAlive(serviceInstance).block();
|
||||
|
||||
assertThat(alive).isTrue();
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@Test
|
||||
void shouldCheckInstanceWithDefaultHealthCheckPath() {
|
||||
HealthCheckServiceInstanceListSupplier listSupplier = new HealthCheckServiceInstanceListSupplier(
|
||||
ServiceInstanceListSupplier.FixedServiceInstanceListSupplier
|
||||
.with(new MockEnvironment()).build(),
|
||||
healthCheck, webClient);
|
||||
ServiceInstance serviceInstance = new DefaultServiceInstance("ignored-service-1",
|
||||
"ignored-service", "127.0.0.1", port, false);
|
||||
|
||||
boolean alive = listSupplier.isAlive(serviceInstance).block();
|
||||
|
||||
assertThat(alive).isTrue();
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@Test
|
||||
void shouldReturnFalseIfEndpointNotFound() {
|
||||
healthCheck.getPath().put("ignored-service", "/test");
|
||||
HealthCheckServiceInstanceListSupplier listSupplier = new HealthCheckServiceInstanceListSupplier(
|
||||
ServiceInstanceListSupplier.FixedServiceInstanceListSupplier
|
||||
.with(new MockEnvironment()).build(),
|
||||
healthCheck, webClient);
|
||||
ServiceInstance serviceInstance = new DefaultServiceInstance("ignored-service-1",
|
||||
"ignored-service", "127.0.0.1", port, false);
|
||||
|
||||
boolean alive = listSupplier.isAlive(serviceInstance).block();
|
||||
|
||||
assertThat(alive).isFalse();
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableAutoConfiguration
|
||||
@RestController
|
||||
static class TestApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(
|
||||
HealthCheckServiceInstanceListSupplierTests.TestApplication.class,
|
||||
args);
|
||||
}
|
||||
|
||||
@GetMapping("/health")
|
||||
void healthCheck() {
|
||||
|
||||
}
|
||||
|
||||
@GetMapping("/actuator/health")
|
||||
void defaultHealthCheck() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -27,7 +27,7 @@ import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.cloud.client.DefaultServiceInstance;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerProperties;
|
||||
import org.springframework.cloud.loadbalancer.config.LoadBalancerZoneConfig;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||
@@ -44,10 +44,10 @@ class ZonePreferenceServiceInstanceListSupplierTests {
|
||||
private DiscoveryClientServiceInstanceListSupplier delegate = mock(
|
||||
DiscoveryClientServiceInstanceListSupplier.class);
|
||||
|
||||
private LoadBalancerProperties loadBalancerProperties = new LoadBalancerProperties();
|
||||
private LoadBalancerZoneConfig zoneConfig = new LoadBalancerZoneConfig(null);
|
||||
|
||||
private ZonePreferenceServiceInstanceListSupplier supplier = new ZonePreferenceServiceInstanceListSupplier(
|
||||
delegate, loadBalancerProperties);
|
||||
delegate, zoneConfig);
|
||||
|
||||
private ServiceInstance first = serviceInstance("test-1", buildZoneMetadata("zone1"));
|
||||
|
||||
@@ -63,7 +63,7 @@ class ZonePreferenceServiceInstanceListSupplierTests {
|
||||
|
||||
@Test
|
||||
void shouldFilterInstancesByZone() {
|
||||
loadBalancerProperties.setZone("zone1");
|
||||
zoneConfig.setZone("zone1");
|
||||
when(delegate.get()).thenReturn(
|
||||
Flux.just(Arrays.asList(first, second, third, fourth, fifth)));
|
||||
|
||||
@@ -78,7 +78,7 @@ class ZonePreferenceServiceInstanceListSupplierTests {
|
||||
|
||||
@Test
|
||||
void shouldReturnAllInstancesIfNoZoneInstances() {
|
||||
loadBalancerProperties.setZone("zone1");
|
||||
zoneConfig.setZone("zone1");
|
||||
when(delegate.get()).thenReturn(Flux.just(Arrays.asList(third, fourth)));
|
||||
|
||||
List<ServiceInstance> filtered = supplier.get().blockFirst();
|
||||
@@ -89,7 +89,7 @@ class ZonePreferenceServiceInstanceListSupplierTests {
|
||||
|
||||
@Test
|
||||
void shouldNotThrowNPEIfNullInstanceMetadata() {
|
||||
loadBalancerProperties.setZone("zone1");
|
||||
zoneConfig.setZone("zone1");
|
||||
when(delegate.get()).thenReturn(
|
||||
Flux.just(Collections.singletonList(serviceInstance("test-6", null))));
|
||||
assertThatCode(() -> supplier.get().blockFirst()).doesNotThrowAnyException();
|
||||
@@ -97,7 +97,7 @@ class ZonePreferenceServiceInstanceListSupplierTests {
|
||||
|
||||
@Test
|
||||
void shouldReturnAllInstancesIfNoZone() {
|
||||
loadBalancerProperties.setZone(null);
|
||||
zoneConfig.setZone(null);
|
||||
when(delegate.get())
|
||||
.thenReturn(Flux.just(Arrays.asList(first, second, third, fourth)));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user