Avoid retrying on same instance (#834)

* cherry-pick switching to properties

* Pass information on previous ServiceInstance to RequestContext.

# Conflicts:
#	spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/blocking/retry/BlockingLoadBalancedRetryPolicy.java

* Add a RoundRobinLoadBalancer implementation that avoids same service instance while retrying.

* Wrap instances in ArrayList. Add tests.

* Enable AvoidPreviousInstanceRoundRobinLoadBalancer by default if SpringRetry on classpath.

* Fix failing tests. Add javadocs and author tags.

* Fix properties.

* Add documentation.

* Fix docs after review.

* Fix docs after review.

* Handle avoiding previous instance with ServiceInstanceListSupplier in place of LoadBalancer.

* Fix property name.

* Change spelling.
This commit is contained in:
Olga Maciaszek-Sharma
2020-09-29 12:36:54 -05:00
committed by GitHub
parent cbe4bb1139
commit 25c17082ee
26 changed files with 521 additions and 220 deletions

View File

@@ -16,7 +16,10 @@
package org.springframework.cloud.loadbalancer.annotation;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.AllNestedConditions;
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.ConditionalOnProperty;
import org.springframework.cloud.client.ConditionalOnBlockingDiscoveryEnabled;
@@ -26,14 +29,18 @@ import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient;
import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
import org.springframework.cloud.loadbalancer.core.RetryAwareServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.core.RoundRobinLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.Environment;
import org.springframework.retry.support.RetryTemplate;
/**
* @author Spencer Gibb
@@ -129,4 +136,42 @@ public class LoadBalancerClientConfiguration {
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnBlockingDiscoveryEnabled
@ConditionalOnClass(RetryTemplate.class)
@Conditional(OnAvoidPreviousInstanceAndRetryEnabledCondition.class)
@AutoConfigureAfter(BlockingSupportConfiguration.class)
@ConditionalOnBean(ServiceInstanceListSupplier.class)
public static class BlockingRetryConfiguration {
@Bean
@ConditionalOnBean(DiscoveryClient.class)
@Primary
public ServiceInstanceListSupplier retryAwareDiscoveryClientServiceInstanceListSupplier(
ServiceInstanceListSupplier delegate) {
return new RetryAwareServiceInstanceListSupplier(delegate);
}
}
static final class OnAvoidPreviousInstanceAndRetryEnabledCondition extends AllNestedConditions {
private OnAvoidPreviousInstanceAndRetryEnabledCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled", havingValue = "true",
matchIfMissing = true)
static class LoadBalancerRetryEnabled {
}
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.avoid-previous-instance", havingValue = "true",
matchIfMissing = true)
static class AvoidPreviousInstanceEnabled {
}
}
}

View File

@@ -18,8 +18,8 @@ package org.springframework.cloud.loadbalancer.blocking.retry;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy;
import org.springframework.cloud.client.loadbalancer.LoadBalancerRetryProperties;
import org.springframework.cloud.client.loadbalancer.ServiceInstanceChooser;
import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerProperties;
import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient;
/**
@@ -31,15 +31,15 @@ import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalanc
*/
public class BlockingLoadBalancedRetryFactory implements LoadBalancedRetryFactory {
private final LoadBalancerRetryProperties retryProperties;
private final LoadBalancerProperties loadBalancerProperties;
public BlockingLoadBalancedRetryFactory(LoadBalancerRetryProperties retryProperties) {
this.retryProperties = retryProperties;
public BlockingLoadBalancedRetryFactory(LoadBalancerProperties loadBalancerProperties) {
this.loadBalancerProperties = loadBalancerProperties;
}
@Override
public LoadBalancedRetryPolicy createRetryPolicy(String serviceId, ServiceInstanceChooser serviceInstanceChooser) {
return new BlockingLoadBalancedRetryPolicy(serviceId, serviceInstanceChooser, retryProperties);
return new BlockingLoadBalancedRetryPolicy(loadBalancerProperties);
}
}

View File

@@ -18,8 +18,7 @@ package org.springframework.cloud.loadbalancer.blocking.retry;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryContext;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy;
import org.springframework.cloud.client.loadbalancer.LoadBalancerRetryProperties;
import org.springframework.cloud.client.loadbalancer.ServiceInstanceChooser;
import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerProperties;
import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient;
import org.springframework.http.HttpMethod;
@@ -33,37 +32,30 @@ import org.springframework.http.HttpMethod;
*/
public class BlockingLoadBalancedRetryPolicy implements LoadBalancedRetryPolicy {
private final LoadBalancerRetryProperties retryProperties;
private final ServiceInstanceChooser serviceInstanceChooser;
private final String serviceId;
private final LoadBalancerProperties properties;
private int sameServerCount = 0;
private int nextServerCount = 0;
public BlockingLoadBalancedRetryPolicy(String serviceId, ServiceInstanceChooser serviceInstanceChooser,
LoadBalancerRetryProperties retryProperties) {
this.serviceId = serviceId;
this.serviceInstanceChooser = serviceInstanceChooser;
this.retryProperties = retryProperties;
public BlockingLoadBalancedRetryPolicy(LoadBalancerProperties properties) {
this.properties = properties;
}
public boolean canRetry(LoadBalancedRetryContext context) {
HttpMethod method = context.getRequest().getMethod();
return HttpMethod.GET.equals(method) || retryProperties.isRetryOnAllOperations();
return HttpMethod.GET.equals(method) || properties.getRetry().isRetryOnAllOperations();
}
@Override
public boolean canRetrySameServer(LoadBalancedRetryContext context) {
return sameServerCount < retryProperties.getMaxRetriesOnSameServiceInstance() && canRetry(context);
return sameServerCount < properties.getRetry().getMaxRetriesOnSameServiceInstance() && canRetry(context);
}
@Override
public boolean canRetryNextServer(LoadBalancedRetryContext context) {
// After the failure, we increment first and then check, hence the equality check
return nextServerCount <= retryProperties.getMaxRetriesOnNextServiceInstance() && canRetry(context);
return nextServerCount <= properties.getRetry().getMaxRetriesOnNextServiceInstance() && canRetry(context);
}
@Override
@@ -81,7 +73,10 @@ public class BlockingLoadBalancedRetryPolicy implements LoadBalancedRetryPolicy
context.setExhaustedOnly();
}
else {
context.setServiceInstance(serviceInstanceChooser.choose(serviceId));
// We want the service instance to be set by
// `RetryLoadBalancerInterceptor`
// in order to get the entire data of the request
context.setServiceInstance(null);
}
}
else {
@@ -91,7 +86,7 @@ public class BlockingLoadBalancedRetryPolicy implements LoadBalancedRetryPolicy
@Override
public boolean retryableStatusCode(int statusCode) {
return retryProperties.getRetryableStatusCodes().contains(statusCode);
return properties.getRetry().getRetryableStatusCodes().contains(statusCode);
}
}

View File

@@ -25,7 +25,6 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.cloud.client.loadbalancer.AsyncLoadBalancerAutoConfiguration;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerRetryProperties;
import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerProperties;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient;
@@ -60,13 +59,13 @@ public class BlockingLoadBalancerClientAutoConfiguration {
@Configuration
@ConditionalOnClass(RetryTemplate.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
@EnableConfigurationProperties(LoadBalancerProperties.class)
protected static class BlockingLoadBalancerRetryConfig {
@Bean
@ConditionalOnMissingBean
LoadBalancedRetryFactory loadBalancedRetryFactory(LoadBalancerRetryProperties retryProperties) {
return new BlockingLoadBalancedRetryFactory(retryProperties);
LoadBalancedRetryFactory loadBalancedRetryFactory(LoadBalancerProperties properties) {
return new BlockingLoadBalancedRetryFactory(properties);
}
}

View File

@@ -0,0 +1,85 @@
/*
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.loadbalancer.core;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Flux;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.Request;
import org.springframework.cloud.client.loadbalancer.RetryableRequestContext;
/**
* A {@link ServiceInstanceListSupplier} implementation that avoids picking the same
* service instance while retrying requests.
*
* @author Olga Maciaszek-Sharma
* @since 3.0.0
*/
public class RetryAwareServiceInstanceListSupplier extends DelegatingServiceInstanceListSupplier {
private final Log LOG = LogFactory.getLog(RetryAwareServiceInstanceListSupplier.class);
public RetryAwareServiceInstanceListSupplier(ServiceInstanceListSupplier delegate) {
super(delegate);
}
@Override
public String getServiceId() {
return delegate.getServiceId();
}
@Override
public Flux<List<ServiceInstance>> get(Request request) {
if (!(request.getContext() instanceof RetryableRequestContext)) {
return get();
}
RetryableRequestContext context = (RetryableRequestContext) request.getContext();
ServiceInstance previousServiceInstance = context.getPreviousServiceInstance();
if (previousServiceInstance == null) {
return get();
}
return get().map(instances -> filteredByPreviousInstance(instances, previousServiceInstance));
}
private List<ServiceInstance> filteredByPreviousInstance(List<ServiceInstance> instances,
ServiceInstance previousServiceInstance) {
List<ServiceInstance> filteredInstances = new ArrayList<>(instances);
if (previousServiceInstance != null) {
filteredInstances.remove(previousServiceInstance);
}
if (filteredInstances.size() > 0) {
return filteredInstances;
}
if (LOG.isWarnEnabled()) {
LOG.warn(String.format(
"No instances found after removing previously used service instance from the search (%s). Returning all found instances.",
previousServiceInstance));
}
return instances;
}
@Override
public Flux<List<ServiceInstance>> get() {
return delegate.get();
}
}

View File

@@ -41,11 +41,11 @@ public class RoundRobinLoadBalancer implements ReactorServiceInstanceLoadBalance
private static final Log log = LogFactory.getLog(RoundRobinLoadBalancer.class);
private final AtomicInteger position;
final AtomicInteger position;
private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
final String serviceId;
private final String serviceId;
ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
/**
* @param serviceInstanceListSupplierProvider a provider of
@@ -78,12 +78,14 @@ public class RoundRobinLoadBalancer implements ReactorServiceInstanceLoadBalance
public Mono<Response<ServiceInstance>> choose(Request request) {
ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
.getIfAvailable(NoopServiceInstanceListSupplier::new);
return supplier.get().next().map(this::getInstanceResponse);
return supplier.get(request).next().map(this::getInstanceResponse);
}
private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {
Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {
if (instances.isEmpty()) {
log.warn("No servers available for service: " + this.serviceId);
if (log.isWarnEnabled()) {
log.warn("No servers available for service: " + serviceId);
}
return new EmptyResponse();
}
// TODO: enforce order?

View File

@@ -24,6 +24,7 @@ import reactor.core.publisher.Flux;
import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.Request;
import org.springframework.core.env.Environment;
import static org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory.PROPERTY_NAME;
@@ -38,6 +39,10 @@ public interface ServiceInstanceListSupplier extends Supplier<Flux<List<ServiceI
String getServiceId();
default Flux<List<ServiceInstance>> get(Request request) {
return get();
}
static ServiceInstanceListSupplierBuilder builder() {
return new ServiceInstanceListSupplierBuilder();
}

View File

@@ -172,6 +172,12 @@ public final class ServiceInstanceListSupplierBuilder {
return this;
}
public ServiceInstanceListSupplierBuilder withRetryAwareness() {
DelegateCreator creator = (context, delegate) -> new RetryAwareServiceInstanceListSupplier(delegate);
creators.add(creator);
return this;
}
/**
* Builds the {@link ServiceInstanceListSupplier} hierarchy.
* @param context application context

View File

@@ -26,7 +26,7 @@ import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
/**
* Utility class for service instances.
* Utility class for service instance list suppliers.
*
* @author Spencer Gibb
* @author Olga Maciaszek-Sharma

View File

@@ -21,6 +21,12 @@
"name": "spring.cloud.loadbalancer.cache.enabled",
"description": "Enables Spring Cloud LoadBalancer caching mechanism.",
"type": "java.lang.Boolean"
},
{
"defaultValue": true,
"name": "spring.cloud.loadbalancer.retry.avoid-previous-instance",
"description": "Enables wrapping ServiceInstanceListSupplier beans with `RetryAwareServiceInstanceListSupplier` if Spring-Retry is in the classpath.",
"type": "java.lang.Boolean"
}
]
}

View File

@@ -19,6 +19,7 @@ package org.springframework.cloud.loadbalancer.annotation;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.cloud.client.discovery.composite.CompositeDiscoveryClientAutoConfiguration;
import org.springframework.cloud.client.discovery.composite.reactive.ReactiveCompositeDiscoveryClientAutoConfiguration;
@@ -29,10 +30,12 @@ import org.springframework.cloud.loadbalancer.core.CachingServiceInstanceListSup
import org.springframework.cloud.loadbalancer.core.DelegatingServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.core.DiscoveryClientServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.core.HealthCheckServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.core.RetryAwareServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.core.ZonePreferenceServiceInstanceListSupplier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.web.reactive.function.client.WebClient;
import static org.assertj.core.api.BDDAssertions.then;
@@ -50,6 +53,12 @@ class LoadBalancerClientConfigurationTests {
LoadBalancerClientConfiguration.class));
ApplicationContextRunner blockingDiscoveryClientRunner = new ApplicationContextRunner()
.withClassLoader(new FilteredClassLoader(RetryTemplate.class))
.withConfiguration(AutoConfigurations.of(CompositeDiscoveryClientAutoConfiguration.class,
LoadBalancerCacheAutoConfiguration.class, LoadBalancerAutoConfiguration.class,
LoadBalancerClientConfiguration.class));
ApplicationContextRunner blockingDiscoveryClientRunnerWithRetry = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(CompositeDiscoveryClientAutoConfiguration.class,
LoadBalancerCacheAutoConfiguration.class, LoadBalancerAutoConfiguration.class,
LoadBalancerClientConfiguration.class));
@@ -126,6 +135,30 @@ class LoadBalancerClientConfigurationTests {
});
}
@Test
void shouldWrapWithRetryAwareSupplierWhenRetryTemplateOnClasspath() {
blockingDiscoveryClientRunnerWithRetry.run(context -> {
ServiceInstanceListSupplier supplier = context.getBean(ServiceInstanceListSupplier.class);
then(supplier).isInstanceOf(RetryAwareServiceInstanceListSupplier.class);
then(((DelegatingServiceInstanceListSupplier) supplier).getDelegate())
.isInstanceOf(CachingServiceInstanceListSupplier.class);
then(((DelegatingServiceInstanceListSupplier) ((DelegatingServiceInstanceListSupplier) supplier)
.getDelegate()).getDelegate()).isInstanceOf(DiscoveryClientServiceInstanceListSupplier.class);
});
}
@Test
void shouldNotWrapWithRetryAwareSupplierWhenRetryTemplateOnClasspath() {
blockingDiscoveryClientRunner.withPropertyValues("spring.cloud.loadbalancer.retry.avoidPreviousInstance=false")
.run(context -> {
ServiceInstanceListSupplier supplier = context.getBean(ServiceInstanceListSupplier.class);
then(supplier).isInstanceOf(CachingServiceInstanceListSupplier.class);
then(((DelegatingServiceInstanceListSupplier) supplier).getDelegate())
.isInstanceOf(DiscoveryClientServiceInstanceListSupplier.class);
});
}
@Configuration
protected static class TestConfig {

View File

@@ -23,7 +23,7 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryContext;
import org.springframework.cloud.client.loadbalancer.LoadBalancerRetryProperties;
import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerProperties;
import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRequest;
@@ -47,7 +47,7 @@ class BlockingLoadBalancedRetryPolicyTests {
private final LoadBalancedRetryContext context = mock(LoadBalancedRetryContext.class);
private final LoadBalancerRetryProperties retryProperties = new LoadBalancerRetryProperties();
private final LoadBalancerProperties properties = new LoadBalancerProperties();
private final UnsupportedOperationException exception = new UnsupportedOperationException();
@@ -59,8 +59,8 @@ class BlockingLoadBalancedRetryPolicyTests {
@Test
void shouldExecuteIndicatedNumberOfSameAndNextInstanceRetriesAndCloseRetryContext() {
retryProperties.setMaxRetriesOnSameServiceInstance(1);
BlockingLoadBalancedRetryPolicy retryPolicy = getRetryPolicy(retryProperties);
properties.getRetry().setMaxRetriesOnSameServiceInstance(1);
BlockingLoadBalancedRetryPolicy retryPolicy = getRetryPolicy(properties);
assertThat(retryPolicy.canRetrySameServer(context)).isTrue();
assertThat(retryPolicy.canRetryNextServer(context)).isTrue();
@@ -88,7 +88,7 @@ class BlockingLoadBalancedRetryPolicyTests {
void shouldNotRetryWhenMethodNotGet() {
when(httpRequest.getMethod()).thenReturn(HttpMethod.POST);
when(context.getRequest()).thenReturn(httpRequest);
BlockingLoadBalancedRetryPolicy retryPolicy = getRetryPolicy(retryProperties);
BlockingLoadBalancedRetryPolicy retryPolicy = getRetryPolicy(properties);
boolean canRetry = retryPolicy.canRetry(context);
@@ -99,8 +99,8 @@ class BlockingLoadBalancedRetryPolicyTests {
void shouldRetryOnPostWhenEnabled() {
when(httpRequest.getMethod()).thenReturn(HttpMethod.POST);
when(context.getRequest()).thenReturn(httpRequest);
retryProperties.setRetryOnAllOperations(true);
BlockingLoadBalancedRetryPolicy retryPolicy = getRetryPolicy(retryProperties);
properties.getRetry().setRetryOnAllOperations(true);
BlockingLoadBalancedRetryPolicy retryPolicy = getRetryPolicy(properties);
boolean canRetry = retryPolicy.canRetry(context);
@@ -109,16 +109,16 @@ class BlockingLoadBalancedRetryPolicyTests {
@Test
void shouldResolveRetryableStatusCode() {
retryProperties.setRetryableStatusCodes(new HashSet<>(Arrays.asList(404, 502)));
BlockingLoadBalancedRetryPolicy retryPolicy = getRetryPolicy(retryProperties);
properties.getRetry().setRetryableStatusCodes(new HashSet<>(Arrays.asList(404, 502)));
BlockingLoadBalancedRetryPolicy retryPolicy = getRetryPolicy(properties);
boolean retryableStatusCode = retryPolicy.retryableStatusCode(404);
assertThat(retryableStatusCode).isTrue();
}
private BlockingLoadBalancedRetryPolicy getRetryPolicy(LoadBalancerRetryProperties retryProperties) {
return new BlockingLoadBalancedRetryPolicy("test", loadBalancerClient, retryProperties);
private BlockingLoadBalancedRetryPolicy getRetryPolicy(LoadBalancerProperties properties) {
return new BlockingLoadBalancedRetryPolicy(properties);
}
}

View File

@@ -0,0 +1,81 @@
/*
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.loadbalancer.core;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.DefaultRequest;
import org.springframework.cloud.client.loadbalancer.RetryableRequestContext;
import org.springframework.cloud.loadbalancer.support.ServiceInstanceListSuppliers;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link RetryAwareServiceInstanceListSupplier}.
*
* @author Olga Maciaszek-Sharma
*/
class RetryAwareServiceInstanceListSupplierTests {
private final String serviceId = "test";
private static DefaultServiceInstance instance(String serviceId, String host, boolean secure) {
return new DefaultServiceInstance(serviceId, serviceId, host, 80, secure);
}
@Test
void shouldReturnEmptyListIfNoInstances() {
ServiceInstanceListSupplier delegate = ServiceInstanceListSuppliers.from(serviceId);
ServiceInstanceListSupplier supplier = new RetryAwareServiceInstanceListSupplier(delegate);
List<ServiceInstance> returnedInstances = supplier.get(new DefaultRequest<>(new RetryableRequestContext(null)))
.blockFirst();
assertThat(returnedInstances).isEmpty();
}
@Test
void shouldReturnFilteredInstances() {
ServiceInstance firstInstance = instance(serviceId, "1host", false);
ServiceInstance secondInstance = instance(serviceId, "2host-secure", true);
ServiceInstanceListSupplier delegate = ServiceInstanceListSuppliers.from(serviceId, firstInstance,
secondInstance);
ServiceInstanceListSupplier supplier = new RetryAwareServiceInstanceListSupplier(delegate);
List<ServiceInstance> returnedInstances = supplier
.get(new DefaultRequest<>(new RetryableRequestContext(firstInstance))).blockFirst();
assertThat(returnedInstances).containsExactly(secondInstance);
}
@Test
void shouldReturnAllInstancesIfFilteredInstancesEmpty() {
ServiceInstance firstInstance = instance(serviceId, "1host", false);
ServiceInstanceListSupplier delegate = ServiceInstanceListSuppliers.from(serviceId, firstInstance);
ServiceInstanceListSupplier supplier = new RetryAwareServiceInstanceListSupplier(delegate);
List<ServiceInstance> returnedInstances = supplier
.get(new DefaultRequest<>(new RetryableRequestContext(firstInstance))).blockFirst();
assertThat(returnedInstances).containsExactly(firstInstance);
}
}