From 5fb77bd666b54839b93288ae64d536054e04c21a Mon Sep 17 00:00:00 2001 From: Andrii Bohutskyi Date: Thu, 8 Apr 2021 11:35:02 +0300 Subject: [PATCH 1/4] Support configuration per client for BlockingLoadBalancedRetryFactory --- .../LoadBalancerPropertiesFactory.java | 43 +++++++++++++++++++ .../LoadBalancerServiceProperties.java | 41 ++++++++++++++++++ .../BlockingLoadBalancedRetryFactory.java | 21 ++++++++- ...ngLoadBalancerClientAutoConfiguration.java | 5 ++- .../config/LoadBalancerAutoConfiguration.java | 12 +++++- 5 files changed, 117 insertions(+), 5 deletions(-) create mode 100644 spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerPropertiesFactory.java create mode 100644 spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerServiceProperties.java diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerPropertiesFactory.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerPropertiesFactory.java new file mode 100644 index 000000000..8f2e398a1 --- /dev/null +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerPropertiesFactory.java @@ -0,0 +1,43 @@ +/* + * 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.cloud.client.loadbalancer; + +public class LoadBalancerPropertiesFactory { + + private final LoadBalancerServiceProperties servicesProperties; + + private final LoadBalancerProperties properties; + + private final boolean isServiceProperties; + + public LoadBalancerPropertiesFactory(LoadBalancerProperties properties, + LoadBalancerServiceProperties servicesProperties, boolean isServiceProperties) { + this.properties = properties; + this.servicesProperties = servicesProperties; + this.isServiceProperties = isServiceProperties; + } + + public LoadBalancerProperties getLoadBalancerProperties(String serviceName) { + return isServiceProperties ? getLoadBalancerServiceProperties(serviceName) : properties; + } + + private LoadBalancerProperties getLoadBalancerServiceProperties(String serviceName) { + return servicesProperties.getServices().getOrDefault(serviceName, + servicesProperties.getDefaultServiceProperties()); + } + +} diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerServiceProperties.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerServiceProperties.java new file mode 100644 index 000000000..3e7fd2f87 --- /dev/null +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerServiceProperties.java @@ -0,0 +1,41 @@ +/* + * 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.cloud.client.loadbalancer; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("spring.cloud.loadbalancer") +public class LoadBalancerServiceProperties { + + private Map services = new HashMap<>(); + + public Map getServices() { + return services; + } + + public void setServices(Map services) { + this.services = services; + } + + public LoadBalancerProperties getDefaultServiceProperties() { + return services.getOrDefault("default", new LoadBalancerProperties()); + } + +} diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/blocking/retry/BlockingLoadBalancedRetryFactory.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/blocking/retry/BlockingLoadBalancedRetryFactory.java index 92a063756..7407f6633 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/blocking/retry/BlockingLoadBalancedRetryFactory.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/blocking/retry/BlockingLoadBalancedRetryFactory.java @@ -19,6 +19,7 @@ import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy; import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; +import org.springframework.cloud.client.loadbalancer.LoadBalancerPropertiesFactory; import org.springframework.cloud.client.loadbalancer.ServiceInstanceChooser; import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient; @@ -31,15 +32,31 @@ */ public class BlockingLoadBalancedRetryFactory implements LoadBalancedRetryFactory { - private final LoadBalancerProperties loadBalancerProperties; + private LoadBalancerProperties loadBalancerProperties; + private LoadBalancerPropertiesFactory factoryProperties; + + @Deprecated public BlockingLoadBalancedRetryFactory(LoadBalancerProperties loadBalancerProperties) { this.loadBalancerProperties = loadBalancerProperties; } + public BlockingLoadBalancedRetryFactory(LoadBalancerPropertiesFactory factoryProperties) { + this.factoryProperties = factoryProperties; + } + @Override public LoadBalancedRetryPolicy createRetryPolicy(String serviceId, ServiceInstanceChooser serviceInstanceChooser) { - return new BlockingLoadBalancedRetryPolicy(loadBalancerProperties); + return new BlockingLoadBalancedRetryPolicy(getLoadBalancerProperties(serviceId)); + } + + @Deprecated + private LoadBalancerProperties getLoadBalancerProperties(String serviceId) { + if (factoryProperties != null) { + return factoryProperties.getLoadBalancerProperties(serviceId); + } else { + return loadBalancerProperties; + } } } diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/config/BlockingLoadBalancerClientAutoConfiguration.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/config/BlockingLoadBalancerClientAutoConfiguration.java index f9fad6065..b3aca1038 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/config/BlockingLoadBalancerClientAutoConfiguration.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/config/BlockingLoadBalancerClientAutoConfiguration.java @@ -27,6 +27,7 @@ import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory; import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; +import org.springframework.cloud.client.loadbalancer.LoadBalancerPropertiesFactory; import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients; import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient; import org.springframework.cloud.loadbalancer.blocking.retry.BlockingLoadBalancedRetryFactory; @@ -75,8 +76,8 @@ protected static class BlockingLoadBalancerRetryConfig { @Bean @ConditionalOnMissingBean - LoadBalancedRetryFactory loadBalancedRetryFactory(LoadBalancerProperties properties) { - return new BlockingLoadBalancedRetryFactory(properties); + LoadBalancedRetryFactory loadBalancedRetryFactory(LoadBalancerPropertiesFactory propertiesFactory) { + return new BlockingLoadBalancedRetryFactory(propertiesFactory); } } diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/config/LoadBalancerAutoConfiguration.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/config/LoadBalancerAutoConfiguration.java index f18168307..5a6dc7a7a 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/config/LoadBalancerAutoConfiguration.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/config/LoadBalancerAutoConfiguration.java @@ -20,10 +20,13 @@ import java.util.List; import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Value; 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.LoadBalancerProperties; +import org.springframework.cloud.client.loadbalancer.LoadBalancerPropertiesFactory; +import org.springframework.cloud.client.loadbalancer.LoadBalancerServiceProperties; import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerBeanPostProcessorAutoConfiguration; import org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerClientAutoConfiguration; import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClientSpecification; @@ -39,7 +42,7 @@ */ @Configuration(proxyBeanMethods = false) @LoadBalancerClients -@EnableConfigurationProperties(LoadBalancerProperties.class) +@EnableConfigurationProperties({ LoadBalancerProperties.class, LoadBalancerServiceProperties.class }) @AutoConfigureBefore({ ReactorLoadBalancerClientAutoConfiguration.class, LoadBalancerBeanPostProcessorAutoConfiguration.class }) public class LoadBalancerAutoConfiguration { @@ -64,4 +67,11 @@ public LoadBalancerClientFactory loadBalancerClientFactory() { return clientFactory; } + @Bean + public LoadBalancerPropertiesFactory loadBalancerPropertiesFactory(LoadBalancerProperties properties, + LoadBalancerServiceProperties servicesProperties, + @Value("${spring.cloud.loadbalancer.service.configuration.enabled:false}") boolean isServiceProperties) { + return new LoadBalancerPropertiesFactory(properties, servicesProperties, isServiceProperties); + } + } From 60e8626f5fe794fd73111b7ef54ce2a53e8307ed Mon Sep 17 00:00:00 2001 From: Andrii Bohutskyi Date: Thu, 22 Apr 2021 14:04:22 +0300 Subject: [PATCH 2/4] Support configuration per client --- .../main/asciidoc/spring-cloud-commons.adoc | 19 + .../LoadBalancerAutoConfiguration.java | 17 +- .../LoadBalancerPropertiesFactory.java | 9 +- .../LoadBalancerServiceProperties.java | 7 +- .../RetryLoadBalancerInterceptor.java | 31 +- .../reactive/LoadBalancerRetryPolicy.java | 51 ++ ...orLoadBalancerClientAutoConfiguration.java | 17 +- ...torLoadBalancerExchangeFilterFunction.java | 37 +- ...FilterFunctionLoadBalancerRetryPolicy.java | 49 ++ ...bleLoadBalancerExchangeFilterFunction.java | 70 +- ...oadBalancerInterceptorPerServiceTests.java | 611 ++++++++++++++++++ .../RetryLoadBalancerInterceptorTests.java | 28 +- ...dBalancerClientRequestTransformerTest.java | 13 +- ...dBalancerClientAutoConfigurationTests.java | 13 + ...adBalancerExchangeFilterFunctionTests.java | 13 + ...xchangeFilterFunctionIntegrationTests.java | 18 +- ...terFunctionPerServiceIntegrationTests.java | 346 ++++++++++ ...ExchangeFilterFunctionPerServiceTests.java | 164 +++++ ...adBalancerExchangeFilterFunctionTests.java | 18 +- .../client/BlockingLoadBalancerClient.java | 27 +- .../BlockingLoadBalancedRetryFactory.java | 19 +- ...ngLoadBalancerClientAutoConfiguration.java | 17 +- ...ancerServiceInstanceCookieTransformer.java | 27 +- .../ServiceInstanceListSupplierBuilder.java | 28 +- ...chingServiceInstanceListSupplierTests.java | 17 +- ...tanceCookieTransformerPerServiceTests.java | 98 +++ ...ServiceInstanceCookieTransformerTests.java | 11 +- ...rviceInstanceListSupplierBuilderTests.java | 13 + 28 files changed, 1707 insertions(+), 81 deletions(-) create mode 100644 spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/RetryLoadBalancerInterceptorPerServiceTests.java create mode 100644 spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionPerServiceIntegrationTests.java create mode 100644 spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionPerServiceTests.java create mode 100644 spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/LoadBalancerServiceInstanceCookieTransformerPerServiceTests.java diff --git a/docs/src/main/asciidoc/spring-cloud-commons.adoc b/docs/src/main/asciidoc/spring-cloud-commons.adoc index 635afc045..e83d74196 100644 --- a/docs/src/main/asciidoc/spring-cloud-commons.adoc +++ b/docs/src/main/asciidoc/spring-cloud-commons.adoc @@ -1228,6 +1228,25 @@ NOTE: The meters are registered in the registry when at least one record is adde TIP: You can further configure the behavior of those metrics (for example, add https://micrometer.io/docs/concepts#_histograms_and_percentiles[publishing percentiles and histograms]) by https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html#production-ready-metrics-per-meter-properties[adding `MeterFilters`]. +=== Spring Cloud LoadBalancer per client configuration +You can configure multiple loadBalancer clients by using `spring.cloud.loadbalancer.client.services[]` property: +==== +[source,yaml] +---- +spring: + cloud: + loadBalancer: + client: + services: + foo1: + retry: + retryOnAllOperations: true + foo2: + healthCheck: + refetchInstances: true +---- +==== + == Spring Cloud Circuit Breaker include::spring-cloud-circuitbreaker.adoc[leveloffset=+1] diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerAutoConfiguration.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerAutoConfiguration.java index ccafa07ee..158224b49 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerAutoConfiguration.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerAutoConfiguration.java @@ -23,6 +23,7 @@ import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.AnyNestedCondition; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -47,13 +48,22 @@ * @author Will Tran * @author Gang Li * @author Olga Maciaszek-Sharma + * @author Andrii Bohutskyi */ @Configuration(proxyBeanMethods = false) @ConditionalOnClass(RestTemplate.class) @ConditionalOnBean(LoadBalancerClient.class) -@EnableConfigurationProperties(LoadBalancerProperties.class) +@EnableConfigurationProperties({ LoadBalancerProperties.class, LoadBalancerServiceProperties.class }) public class LoadBalancerAutoConfiguration { + @Bean + @ConditionalOnMissingBean + public LoadBalancerPropertiesFactory loadBalancerPropertiesFactory(LoadBalancerProperties properties, + LoadBalancerServiceProperties servicesProperties, + @Value("${spring.cloud.loadbalancer.service.configuration.enabled:false}") boolean isServiceProperties) { + return new LoadBalancerPropertiesFactory(properties, servicesProperties, isServiceProperties); + } + @LoadBalanced @Autowired(required = false) private List restTemplates = Collections.emptyList(); @@ -149,9 +159,10 @@ public static class RetryInterceptorAutoConfiguration { public RetryLoadBalancerInterceptor loadBalancerInterceptor(LoadBalancerClient loadBalancerClient, LoadBalancerProperties properties, LoadBalancerRequestFactory requestFactory, LoadBalancedRetryFactory loadBalancedRetryFactory, - ReactiveLoadBalancer.Factory loadBalancerFactory) { + ReactiveLoadBalancer.Factory loadBalancerFactory, + LoadBalancerPropertiesFactory propertiesFactory) { return new RetryLoadBalancerInterceptor(loadBalancerClient, properties, requestFactory, - loadBalancedRetryFactory, loadBalancerFactory); + loadBalancedRetryFactory, loadBalancerFactory, propertiesFactory); } @Bean diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerPropertiesFactory.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerPropertiesFactory.java index 8f2e398a1..83d24393f 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerPropertiesFactory.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerPropertiesFactory.java @@ -16,12 +16,17 @@ package org.springframework.cloud.client.loadbalancer; +/** + * Factory class used to provide client properties. + * + * @author Andrii Bohutskyi + */ public class LoadBalancerPropertiesFactory { - private final LoadBalancerServiceProperties servicesProperties; - private final LoadBalancerProperties properties; + private final LoadBalancerServiceProperties servicesProperties; + private final boolean isServiceProperties; public LoadBalancerPropertiesFactory(LoadBalancerProperties properties, diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerServiceProperties.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerServiceProperties.java index 3e7fd2f87..d31eccdd0 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerServiceProperties.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerServiceProperties.java @@ -21,7 +21,12 @@ import org.springframework.boot.context.properties.ConfigurationProperties; -@ConfigurationProperties("spring.cloud.loadbalancer") +/** + * A {@link ConfigurationProperties} bean for client LoadBalancer. + * + * @author Andrii Bohutskyi + */ +@ConfigurationProperties("spring.cloud.loadbalancer.client") public class LoadBalancerServiceProperties { private Map services = new HashMap<>(); diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/RetryLoadBalancerInterceptor.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/RetryLoadBalancerInterceptor.java index e56922b72..266dc4c49 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/RetryLoadBalancerInterceptor.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/RetryLoadBalancerInterceptor.java @@ -42,6 +42,7 @@ * @author Will Tran * @author Gang Li * @author Olga Maciaszek-Sharma + * @author Andrii Bohutskyi */ @SuppressWarnings({ "unchecked", "rawtypes" }) public class RetryLoadBalancerInterceptor implements ClientHttpRequestInterceptor { @@ -58,6 +59,13 @@ public class RetryLoadBalancerInterceptor implements ClientHttpRequestIntercepto private final ReactiveLoadBalancer.Factory loadBalancerFactory; + private LoadBalancerPropertiesFactory propertiesFactory; + + /** + * @deprecated Deprecated in favor of + * {@link #RetryLoadBalancerInterceptor(LoadBalancerClient, LoadBalancerProperties, LoadBalancerRequestFactory, LoadBalancedRetryFactory, ReactiveLoadBalancer.Factory, LoadBalancerPropertiesFactory)}. + */ + @Deprecated public RetryLoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerProperties properties, LoadBalancerRequestFactory requestFactory, LoadBalancedRetryFactory lbRetryFactory, ReactiveLoadBalancer.Factory loadBalancerFactory) { @@ -68,6 +76,14 @@ public RetryLoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalance this.loadBalancerFactory = loadBalancerFactory; } + public RetryLoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerProperties properties, + LoadBalancerRequestFactory requestFactory, LoadBalancedRetryFactory lbRetryFactory, + ReactiveLoadBalancer.Factory loadBalancerFactory, + LoadBalancerPropertiesFactory propertiesFactory) { + this(loadBalancer, properties, requestFactory, lbRetryFactory, loadBalancerFactory); + this.propertiesFactory = propertiesFactory; + } + @Override public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException { @@ -159,15 +175,24 @@ private RetryTemplate createRetryTemplate(String serviceName, HttpRequest reques if (retryListeners != null && retryListeners.length != 0) { template.setListeners(retryListeners); } - template.setRetryPolicy(!properties.getRetry().isEnabled() || retryPolicy == null ? new NeverRetryPolicy() + template.setRetryPolicy(!getLoadBalancerProperties(serviceName).getRetry().isEnabled() || retryPolicy == null ? new NeverRetryPolicy() : new InterceptorRetryPolicy(request, retryPolicy, loadBalancer, serviceName)); return template; } private String getHint(String serviceId) { - String defaultHint = properties.getHint().getOrDefault("default", "default"); - String hintPropertyValue = properties.getHint().get(serviceId); + String defaultHint = getLoadBalancerProperties(serviceId).getHint().getOrDefault("default", "default"); + String hintPropertyValue = getLoadBalancerProperties(serviceId).getHint().get(serviceId); return hintPropertyValue != null ? hintPropertyValue : defaultHint; } + @Deprecated + private LoadBalancerProperties getLoadBalancerProperties(String serviceId) { + if (propertiesFactory != null) { + return propertiesFactory.getLoadBalancerProperties(serviceId); + } else { + return properties; + } + } + } diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/LoadBalancerRetryPolicy.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/LoadBalancerRetryPolicy.java index 0d2edad87..415dee65a 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/LoadBalancerRetryPolicy.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/LoadBalancerRetryPolicy.java @@ -23,6 +23,7 @@ * retried. * * @author Olga Maciaszek-Sharma + * @author Andrii Bohutskyi * @since 3.0.0 */ public interface LoadBalancerRetryPolicy { @@ -31,28 +32,78 @@ public interface LoadBalancerRetryPolicy { * Return true to retry on the same service instance. * @param context the context for the retry operation * @return true to retry on the same service instance + * @deprecated Deprecated in favor of + * {@link #canRetrySameServiceInstance(String, LoadBalancerRetryContext)} */ + @Deprecated boolean canRetrySameServiceInstance(LoadBalancerRetryContext context); + /** + * Return true to retry on the same service instance. + * @param serviceId the serviceId for the retry operation + * @param context the context for the retry operation + * @return true to retry on the same service instance + */ + default boolean canRetrySameServiceInstance(String serviceId, LoadBalancerRetryContext context) { + return canRetrySameServiceInstance(context); + } + /** * Return true to retry on the next service instance. * @param context the context for the retry operation * @return true to retry on the same service instance + * @deprecated Deprecated in favor of + * {@link #canRetryNextServiceInstance(String, LoadBalancerRetryContext)} */ + @Deprecated boolean canRetryNextServiceInstance(LoadBalancerRetryContext context); + /** + * Return true to retry on the next service instance. + * @param serviceId the serviceId for the retry operation + * @param context the context for the retry operation + * @return true to retry on the same service instance + */ + default boolean canRetryNextServiceInstance(String serviceId, LoadBalancerRetryContext context) { + return canRetryNextServiceInstance(context); + } + /** * Return true to retry on the provided HTTP status code. * @param statusCode the HTTP status code * @return true to retry on the provided HTTP status code + * @deprecated Deprecated in favor of {@link #retryableStatusCode(String, int)} */ + @Deprecated boolean retryableStatusCode(int statusCode); + /** + * Return true to retry on the provided HTTP status code. + * @param serviceId the serviceId for the retry operation + * @param statusCode the HTTP status code + * @return true to retry on the provided HTTP status code + */ + default boolean retryableStatusCode(String serviceId, int statusCode) { + return retryableStatusCode(statusCode); + } + /** * Return true to retry on the provided HTTP method. * @param method the HTTP request method * @return true to retry on the provided HTTP method + * @deprecated Deprecated in favor of {@link #canRetryOnMethod(String, HttpMethod)} */ + @Deprecated boolean canRetryOnMethod(HttpMethod method); + /** + * Return true to retry on the provided HTTP method. + * @param serviceId the serviceId for the retry operation + * @param method the HTTP request method + * @return true to retry on the provided HTTP method + */ + default boolean canRetryOnMethod(String serviceId, HttpMethod method) { + return canRetryOnMethod(method); + } + } diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerClientAutoConfiguration.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerClientAutoConfiguration.java index 42135a3cd..daf800fe5 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerClientAutoConfiguration.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerClientAutoConfiguration.java @@ -27,6 +27,7 @@ import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; +import org.springframework.cloud.client.loadbalancer.LoadBalancerPropertiesFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.reactive.function.client.WebClient; @@ -37,6 +38,7 @@ * {@link ReactiveLoadBalancer} used under the hood. * * @author Olga Maciaszek-Sharma + * @author Andrii Bohutskyi * @since 2.2.0 */ @Configuration(proxyBeanMethods = false) @@ -50,9 +52,10 @@ public class ReactorLoadBalancerClientAutoConfiguration { @Bean public ReactorLoadBalancerExchangeFilterFunction loadBalancerExchangeFilterFunction( ReactiveLoadBalancer.Factory loadBalancerFactory, LoadBalancerProperties properties, - ObjectProvider> transformers) { + ObjectProvider> transformers, + LoadBalancerPropertiesFactory propertiesFactory) { return new ReactorLoadBalancerExchangeFilterFunction(loadBalancerFactory, properties, - transformers.getIfAvailable(Collections::emptyList)); + transformers.getIfAvailable(Collections::emptyList), propertiesFactory); } @ConditionalOnMissingBean @@ -61,16 +64,18 @@ public ReactorLoadBalancerExchangeFilterFunction loadBalancerExchangeFilterFunct public RetryableLoadBalancerExchangeFilterFunction retryableLoadBalancerExchangeFilterFunction( ReactiveLoadBalancer.Factory loadBalancerFactory, LoadBalancerProperties properties, LoadBalancerRetryPolicy retryPolicy, - ObjectProvider> transformers) { + ObjectProvider> transformers, + LoadBalancerPropertiesFactory propertiesFactory) { return new RetryableLoadBalancerExchangeFilterFunction(retryPolicy, loadBalancerFactory, properties, - transformers.getIfAvailable(Collections::emptyList)); + transformers.getIfAvailable(Collections::emptyList), propertiesFactory); } @ConditionalOnMissingBean @ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled", havingValue = "true") @Bean - public LoadBalancerRetryPolicy loadBalancerRetryPolicy(LoadBalancerProperties properties) { - return new RetryableExchangeFilterFunctionLoadBalancerRetryPolicy(properties); + public LoadBalancerRetryPolicy loadBalancerRetryPolicy(LoadBalancerProperties properties, + LoadBalancerPropertiesFactory propertiesFactory) { + return new RetryableExchangeFilterFunctionLoadBalancerRetryPolicy(properties, propertiesFactory); } } diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerExchangeFilterFunction.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerExchangeFilterFunction.java index d5735b66d..7ebb907b3 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerExchangeFilterFunction.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerExchangeFilterFunction.java @@ -32,6 +32,7 @@ import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycle; import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycleValidator; import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; +import org.springframework.cloud.client.loadbalancer.LoadBalancerPropertiesFactory; import org.springframework.cloud.client.loadbalancer.Request; import org.springframework.cloud.client.loadbalancer.RequestData; import org.springframework.cloud.client.loadbalancer.RequestDataContext; @@ -52,6 +53,7 @@ * requests against a correct {@link ServiceInstance}. * * @author Olga Maciaszek-Sharma + * @author Andrii Bohutskyi * @since 2.2.0 */ @SuppressWarnings({ "rawtypes", "unchecked" }) @@ -65,6 +67,8 @@ public class ReactorLoadBalancerExchangeFilterFunction implements LoadBalancedEx private final List transformers; + private LoadBalancerPropertiesFactory propertiesFactory; + /** * @deprecated Deprecated in favor of * {@link #ReactorLoadBalancerExchangeFilterFunction(ReactiveLoadBalancer.Factory, LoadBalancerProperties, List)}. @@ -77,6 +81,11 @@ public ReactorLoadBalancerExchangeFilterFunction(ReactiveLoadBalancer.Factory loadBalancerFactory, LoadBalancerProperties properties, List transformers) { this.loadBalancerFactory = loadBalancerFactory; @@ -84,6 +93,19 @@ public ReactorLoadBalancerExchangeFilterFunction(ReactiveLoadBalancer.Factory loadBalancerFactory, + LoadBalancerProperties properties, List transformers, + LoadBalancerPropertiesFactory propertiesFactory) { + this(loadBalancerFactory, properties, transformers); + this.propertiesFactory = propertiesFactory; + } + @Override public Mono filter(ClientRequest clientRequest, ExchangeFunction next) { URI originalUrl = clientRequest.url(); @@ -99,7 +121,7 @@ public Mono filter(ClientRequest clientRequest, ExchangeFunction .getSupportedLifecycleProcessors( loadBalancerFactory.getInstances(serviceId, LoadBalancerLifecycle.class), RequestDataContext.class, ResponseData.class, ServiceInstance.class); - String hint = getHint(serviceId, properties.getHint()); + String hint = getHint(serviceId, getLoadBalancerProperties(serviceId).getHint()); RequestData requestData = new RequestData(clientRequest); DefaultRequest lbRequest = new DefaultRequest<>(new RequestDataContext(requestData, hint)); supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest)); @@ -120,7 +142,8 @@ public Mono filter(ClientRequest clientRequest, ExchangeFunction LOG.debug(String.format("LoadBalancer has retrieved the instance for service %s: %s", serviceId, instance.getUri())); } - LoadBalancerProperties.StickySession stickySessionProperties = properties.getStickySession(); + LoadBalancerProperties.StickySession stickySessionProperties = getLoadBalancerProperties(serviceId) + .getStickySession(); ClientRequest newRequest = buildClientRequest(clientRequest, instance, stickySessionProperties.getInstanceIdCookieName(), stickySessionProperties.isAddServiceInstanceCookie(), transformers); @@ -143,4 +166,14 @@ protected Mono> choose(String serviceId, Request transformers; + private LoadBalancerPropertiesFactory propertiesFactory; + /** * @deprecated Deprecated in favor of * {@link #RetryableLoadBalancerExchangeFilterFunction(LoadBalancerRetryPolicy, ReactiveLoadBalancer.Factory, LoadBalancerProperties, List)}. @@ -87,6 +91,11 @@ public RetryableLoadBalancerExchangeFilterFunction(LoadBalancerRetryPolicy retry this(retryPolicy, loadBalancerFactory, properties, Collections.emptyList()); } + /** + * @deprecated Deprecated in favor of + * {@link #RetryableLoadBalancerExchangeFilterFunction(LoadBalancerRetryPolicy, ReactiveLoadBalancer.Factory, LoadBalancerProperties, List, LoadBalancerPropertiesFactory)} + */ + @Deprecated public RetryableLoadBalancerExchangeFilterFunction(LoadBalancerRetryPolicy retryPolicy, ReactiveLoadBalancer.Factory loadBalancerFactory, LoadBalancerProperties properties, List transformers) { @@ -96,13 +105,16 @@ public RetryableLoadBalancerExchangeFilterFunction(LoadBalancerRetryPolicy retry this.transformers = transformers; } + public RetryableLoadBalancerExchangeFilterFunction(LoadBalancerRetryPolicy retryPolicy, + ReactiveLoadBalancer.Factory loadBalancerFactory, LoadBalancerProperties properties, + List transformers, LoadBalancerPropertiesFactory propertiesFactory) { + this(retryPolicy, loadBalancerFactory, properties, transformers); + this.propertiesFactory = propertiesFactory; + } + @SuppressWarnings({ "rawtypes", "unchecked" }) @Override public Mono filter(ClientRequest clientRequest, ExchangeFunction next) { - LoadBalancerRetryContext loadBalancerRetryContext = new LoadBalancerRetryContext(clientRequest); - Retry exchangeRetry = buildRetrySpec(properties.getRetry().getMaxRetriesOnSameServiceInstance(), true); - Retry filterRetry = buildRetrySpec(properties.getRetry().getMaxRetriesOnNextServiceInstance(), false); - URI originalUrl = clientRequest.url(); String serviceId = originalUrl.getHost(); if (serviceId == null) { @@ -112,11 +124,18 @@ public Mono filter(ClientRequest clientRequest, ExchangeFunction } return Mono.just(ClientResponse.create(HttpStatus.BAD_REQUEST).body(message).build()); } + + LoadBalancerRetryContext loadBalancerRetryContext = new LoadBalancerRetryContext(clientRequest); + Retry exchangeRetry = buildRetrySpec(serviceId, + getLoadBalancerProperties(serviceId).getRetry().getMaxRetriesOnSameServiceInstance(), true); + Retry filterRetry = buildRetrySpec(serviceId, + getLoadBalancerProperties(serviceId).getRetry().getMaxRetriesOnNextServiceInstance(), false); + Set supportedLifecycleProcessors = LoadBalancerLifecycleValidator .getSupportedLifecycleProcessors( loadBalancerFactory.getInstances(serviceId, LoadBalancerLifecycle.class), RetryableRequestContext.class, ResponseData.class, ServiceInstance.class); - String hint = getHint(serviceId, properties.getHint()); + String hint = getHint(serviceId, getLoadBalancerProperties(serviceId).getHint()); RequestData requestData = new RequestData(clientRequest); DefaultRequest lbRequest = new DefaultRequest<>( new RetryableRequestContext(null, requestData, hint)); @@ -140,7 +159,8 @@ public Mono filter(ClientRequest clientRequest, ExchangeFunction LOG.debug(String.format("LoadBalancer has retrieved the instance for service %s: %s", serviceId, instance.getUri())); } - LoadBalancerProperties.StickySession stickySessionProperties = properties.getStickySession(); + LoadBalancerProperties.StickySession stickySessionProperties = getLoadBalancerProperties(serviceId) + .getStickySession(); ClientRequest newRequest = buildClientRequest(clientRequest, instance, stickySessionProperties.getInstanceIdCookieName(), stickySessionProperties.isAddServiceInstanceCookie(), transformers); @@ -154,7 +174,7 @@ public Mono filter(ClientRequest clientRequest, ExchangeFunction lbRequest, lbResponse, new ResponseData(clientResponse, requestData))))) .map(clientResponse -> { loadBalancerRetryContext.setClientResponse(clientResponse); - if (shouldRetrySameServiceInstance(loadBalancerRetryContext)) { + if (shouldRetrySameServiceInstance(serviceId, loadBalancerRetryContext)) { if (LOG.isDebugEnabled()) { LOG.debug(String.format("Retrying on status code: %d", clientResponse.statusCode().value())); @@ -166,7 +186,7 @@ lbRequest, lbResponse, new ResponseData(clientResponse, requestData))))) }); }).map(clientResponse -> { loadBalancerRetryContext.setClientResponse(clientResponse); - if (shouldRetryNextServiceInstance(loadBalancerRetryContext)) { + if (shouldRetryNextServiceInstance(serviceId, loadBalancerRetryContext)) { if (LOG.isDebugEnabled()) { LOG.debug(String.format("Retrying on status code: %d", clientResponse.statusCode().value())); } @@ -177,8 +197,9 @@ lbRequest, lbResponse, new ResponseData(clientResponse, requestData))))) }).retryWhen(exchangeRetry)).retryWhen(filterRetry); } - private Retry buildRetrySpec(int max, boolean transientErrors) { - LoadBalancerProperties.Retry.Backoff backoffProperties = properties.getRetry().getBackoff(); + private Retry buildRetrySpec(String serviceId, int max, boolean transientErrors) { + LoadBalancerProperties.Retry.Backoff backoffProperties = getLoadBalancerProperties(serviceId).getRetry() + .getBackoff(); if (backoffProperties.isEnabled()) { return RetrySpec.backoff(max, backoffProperties.getMinBackoff()).filter(this::isRetryException) .maxBackoff(backoffProperties.getMaxBackoff()).jitter(backoffProperties.getJitter()) @@ -187,20 +208,24 @@ private Retry buildRetrySpec(int max, boolean transientErrors) { return RetrySpec.max(max).filter(this::isRetryException).transientErrors(transientErrors); } - private boolean shouldRetrySameServiceInstance(LoadBalancerRetryContext loadBalancerRetryContext) { - boolean shouldRetry = retryPolicy.retryableStatusCode(loadBalancerRetryContext.getResponseStatusCode()) - && retryPolicy.canRetryOnMethod(loadBalancerRetryContext.getRequestMethod()) - && retryPolicy.canRetrySameServiceInstance(loadBalancerRetryContext); + private boolean shouldRetrySameServiceInstance(String serviceId, + LoadBalancerRetryContext loadBalancerRetryContext) { + boolean shouldRetry = retryPolicy.retryableStatusCode(serviceId, + loadBalancerRetryContext.getResponseStatusCode()) + && retryPolicy.canRetryOnMethod(serviceId, loadBalancerRetryContext.getRequestMethod()) + && retryPolicy.canRetrySameServiceInstance(serviceId, loadBalancerRetryContext); if (shouldRetry) { loadBalancerRetryContext.incrementRetriesSameServiceInstance(); } return shouldRetry; } - private boolean shouldRetryNextServiceInstance(LoadBalancerRetryContext loadBalancerRetryContext) { - boolean shouldRetry = retryPolicy.retryableStatusCode(loadBalancerRetryContext.getResponseStatusCode()) - && retryPolicy.canRetryOnMethod(loadBalancerRetryContext.getRequestMethod()) - && retryPolicy.canRetryNextServiceInstance(loadBalancerRetryContext); + private boolean shouldRetryNextServiceInstance(String serviceId, + LoadBalancerRetryContext loadBalancerRetryContext) { + boolean shouldRetry = retryPolicy.retryableStatusCode(serviceId, + loadBalancerRetryContext.getResponseStatusCode()) + && retryPolicy.canRetryOnMethod(serviceId, loadBalancerRetryContext.getRequestMethod()) + && retryPolicy.canRetryNextServiceInstance(serviceId, loadBalancerRetryContext); if (shouldRetry) { loadBalancerRetryContext.incrementRetriesNextServiceInstance(); loadBalancerRetryContext.resetRetriesSameServiceInstance(); @@ -223,4 +248,13 @@ protected Mono> choose(String serviceId, Request lbFactory; + + @Before + public void setUp() { + client = mock(LoadBalancerClient.class); + lbRequestFactory = mock(LoadBalancerRequestFactory.class); + serviceProperties = new LoadBalancerServiceProperties(); + serviceProperties.getServices().put("foo", new LoadBalancerProperties()); + propertiesFactory = new LoadBalancerPropertiesFactory(new LoadBalancerProperties(), serviceProperties, true); + lbFactory = mock(ReactiveLoadBalancer.Factory.class); + } + + @After + public void tearDown() { + client = null; + } + + @Test(expected = IOException.class) + public void interceptDisableRetry() throws Throwable { + HttpRequest request = mock(HttpRequest.class); + when(request.getURI()).thenReturn(new URI("http://foo")); + ServiceInstance serviceInstance = mock(ServiceInstance.class); + when(client.choose(eq("foo"), any())).thenReturn(serviceInstance); + when(client.execute(eq("foo"), eq(serviceInstance), any(LoadBalancerRequest.class))) + .thenThrow(new IOException()); + serviceProperties.getServices().get("foo").getRetry().setEnabled(false); + RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, + new LoadBalancerProperties(), lbRequestFactory, loadBalancedRetryFactory, lbFactory, propertiesFactory); + byte[] body = new byte[] {}; + ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class); + + when(lbRequestFactory.createRequest(any(), any(), any())).thenReturn(mock(LoadBalancerRequest.class)); + + interceptor.intercept(request, body, execution); + verify(lbRequestFactory).createRequest(request, body, execution); + } + + @Test(expected = IllegalStateException.class) + public void interceptInvalidHost() throws Throwable { + HttpRequest request = mock(HttpRequest.class); + when(request.getURI()).thenReturn(new URI("http://foo_underscore")); + serviceProperties.getServices().get("foo").getRetry().setEnabled(true); + RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, + new LoadBalancerProperties(), lbRequestFactory, loadBalancedRetryFactory, lbFactory, propertiesFactory); + byte[] body = new byte[] {}; + ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class); + interceptor.intercept(request, body, execution); + } + + @Test + public void interceptNeverRetry() throws Throwable { + HttpRequest request = mock(HttpRequest.class); + when(request.getURI()).thenReturn(new URI("http://foo")); + ClientHttpResponse clientHttpResponse = new MockClientHttpResponse(new byte[] {}, HttpStatus.OK); + ServiceInstance serviceInstance = mock(ServiceInstance.class); + when(client.choose(eq("foo"), any())).thenReturn(serviceInstance); + when(client.execute(eq("foo"), eq(serviceInstance), any(LoadBalancerRequest.class))) + .thenReturn(clientHttpResponse); + when(lbRequestFactory.createRequest(any(), any(), any())).thenReturn(mock(LoadBalancerRequest.class)); + serviceProperties.getServices().get("foo").getRetry().setEnabled(true); + RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, + new LoadBalancerProperties(), lbRequestFactory, loadBalancedRetryFactory, lbFactory, propertiesFactory); + byte[] body = new byte[] {}; + ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class); + interceptor.intercept(request, body, execution); + verify(lbRequestFactory).createRequest(request, body, execution); + } + + @Test + public void interceptSuccess() throws Throwable { + HttpRequest request = mock(HttpRequest.class); + when(request.getURI()).thenReturn(new URI("http://foo")); + ClientHttpResponse clientHttpResponse = new MockClientHttpResponse(new byte[] {}, HttpStatus.OK); + LoadBalancedRetryPolicy policy = mock(LoadBalancedRetryPolicy.class); + ServiceInstance serviceInstance = mock(ServiceInstance.class); + when(client.choose(eq("foo"), any())).thenReturn(serviceInstance); + when(client.execute(eq("foo"), eq(serviceInstance), any(LoadBalancerRequest.class))) + .thenReturn(clientHttpResponse); + when(lbRequestFactory.createRequest(any(), any(), any())).thenReturn(mock(LoadBalancerRequest.class)); + serviceProperties.getServices().get("foo").getRetry().setEnabled(true); + RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, + new LoadBalancerProperties(), lbRequestFactory, new MyLoadBalancedRetryFactory(policy), lbFactory, + propertiesFactory); + byte[] body = new byte[] {}; + ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class); + ClientHttpResponse rsp = interceptor.intercept(request, body, execution); + then(rsp).isEqualTo(clientHttpResponse); + verify(lbRequestFactory).createRequest(request, body, execution); + } + + @Test + public void interceptRetryOnStatusCode() throws Throwable { + HttpRequest request = mock(HttpRequest.class); + when(request.getURI()).thenReturn(new URI("http://foo")); + InputStream notFoundStream = mock(InputStream.class); + when(notFoundStream.read(any(byte[].class))).thenReturn(-1); + ClientHttpResponse clientHttpResponseNotFound = new MockClientHttpResponse(notFoundStream, + HttpStatus.NOT_FOUND); + ClientHttpResponse clientHttpResponseOk = new MockClientHttpResponse(new byte[] {}, HttpStatus.OK); + LoadBalancedRetryPolicy policy = mock(LoadBalancedRetryPolicy.class); + when(policy.retryableStatusCode(eq(HttpStatus.NOT_FOUND.value()))).thenReturn(true); + when(policy.canRetryNextServer(any(LoadBalancedRetryContext.class))).thenReturn(true); + ServiceInstance serviceInstance = mock(ServiceInstance.class); + when(client.choose(eq("foo"), any())).thenReturn(serviceInstance); + when(client.execute(eq("foo"), eq(serviceInstance), nullable(LoadBalancerRequest.class))) + .thenReturn(clientHttpResponseNotFound).thenReturn(clientHttpResponseOk); + serviceProperties.getServices().get("foo").getRetry().setEnabled(true); + RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, + new LoadBalancerProperties(), lbRequestFactory, new MyLoadBalancedRetryFactory(policy), lbFactory, + propertiesFactory); + byte[] body = new byte[] {}; + ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class); + ClientHttpResponse rsp = interceptor.intercept(request, body, execution); + verify(client, times(2)).execute(eq("foo"), eq(serviceInstance), nullable(LoadBalancerRequest.class)); + verify(notFoundStream, times(1)).close(); + then(rsp).isEqualTo(clientHttpResponseOk); + verify(lbRequestFactory, times(2)).createRequest(request, body, execution); + } + + @Test + public void interceptRetryFailOnStatusCode() throws Throwable { + HttpRequest request = mock(HttpRequest.class); + when(request.getURI()).thenReturn(new URI("http://foo")); + + InputStream notFoundStream = new ByteArrayInputStream("foo".getBytes()); + ClientHttpResponse clientHttpResponseNotFound = new MockClientHttpResponse(notFoundStream, + HttpStatus.NOT_FOUND); + + LoadBalancedRetryPolicy policy = mock(LoadBalancedRetryPolicy.class); + when(policy.retryableStatusCode(eq(HttpStatus.NOT_FOUND.value()))).thenReturn(true); + when(policy.canRetryNextServer(any(LoadBalancedRetryContext.class))).thenReturn(false); + + ServiceInstance serviceInstance = mock(ServiceInstance.class); + when(client.choose(eq("foo"), any())).thenReturn(serviceInstance); + when(client.execute(eq("foo"), eq(serviceInstance), + ArgumentMatchers.>any())) + .thenReturn(clientHttpResponseNotFound); + + serviceProperties.getServices().get("foo").getRetry().setEnabled(true); + byte[] body = new byte[] {}; + ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class); + RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, + new LoadBalancerProperties(), lbRequestFactory, new MyLoadBalancedRetryFactory(policy), lbFactory, + propertiesFactory); + ClientHttpResponse rsp = interceptor.intercept(request, body, execution); + + verify(client, times(1)).execute(eq("foo"), eq(serviceInstance), + ArgumentMatchers.>any()); + verify(lbRequestFactory, times(1)).createRequest(request, body, execution); + verify(policy, times(2)).canRetryNextServer(any(LoadBalancedRetryContext.class)); + + // call twice in a retry attempt + byte[] content = new byte[1024]; + int length = rsp.getBody().read(content); + then(length).isEqualTo("foo".getBytes().length); + then(new String(content, 0, length)).isEqualTo("foo"); + } + + @Test + public void interceptRetry() throws Throwable { + HttpRequest request = mock(HttpRequest.class); + when(request.getURI()).thenReturn(new URI("http://foo")); + ClientHttpResponse clientHttpResponse = new MockClientHttpResponse(new byte[] {}, HttpStatus.OK); + LoadBalancedRetryPolicy policy = mock(LoadBalancedRetryPolicy.class); + when(policy.canRetryNextServer(any(LoadBalancedRetryContext.class))).thenReturn(true); + MyBackOffPolicy backOffPolicy = new MyBackOffPolicy(); + ServiceInstance serviceInstance = mock(ServiceInstance.class); + when(client.choose(eq("foo"), any())).thenReturn(serviceInstance); + when(client.execute(eq("foo"), eq(serviceInstance), any(LoadBalancerRequest.class))) + .thenThrow(new IOException()).thenReturn(clientHttpResponse); + when(lbRequestFactory.createRequest(any(), any(), any())).thenReturn(mock(LoadBalancerRequest.class)); + serviceProperties.getServices().get("foo").getRetry().setEnabled(true); + RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, + new LoadBalancerProperties(), lbRequestFactory, new MyLoadBalancedRetryFactory(policy, backOffPolicy), + lbFactory, propertiesFactory); + byte[] body = new byte[] {}; + ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class); + ClientHttpResponse rsp = interceptor.intercept(request, body, execution); + verify(client, times(2)).execute(eq("foo"), eq(serviceInstance), any(LoadBalancerRequest.class)); + then(rsp).isEqualTo(clientHttpResponse); + verify(lbRequestFactory, times(2)).createRequest(request, body, execution); + then(backOffPolicy.getBackoffAttempts()).isEqualTo(1); + } + + @Test(expected = IOException.class) + public void interceptFailedRetry() throws Exception { + HttpRequest request = mock(HttpRequest.class); + when(request.getURI()).thenReturn(new URI("http://foo")); + ClientHttpResponse clientHttpResponse = new MockClientHttpResponse(new byte[] {}, HttpStatus.OK); + LoadBalancedRetryPolicy policy = mock(LoadBalancedRetryPolicy.class); + when(policy.canRetryNextServer(any(LoadBalancedRetryContext.class))).thenReturn(false); + ServiceInstance serviceInstance = mock(ServiceInstance.class); + when(client.choose(eq("foo"), any())).thenReturn(serviceInstance); + when(client.execute(eq("foo"), eq(serviceInstance), any(LoadBalancerRequest.class))) + .thenThrow(new IOException()).thenReturn(clientHttpResponse); + when(lbRequestFactory.createRequest(any(), any(), any())).thenReturn(mock(LoadBalancerRequest.class)); + serviceProperties.getServices().get("foo").getRetry().setEnabled(true); + RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, + new LoadBalancerProperties(), lbRequestFactory, new MyLoadBalancedRetryFactory(policy), lbFactory, + propertiesFactory); + byte[] body = new byte[] {}; + ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class); + interceptor.intercept(request, body, execution); + verify(lbRequestFactory).createRequest(request, body, execution); + } + + private static ServiceInstance defaultServiceInstance() { + return new DefaultServiceInstance("testInstance", "test", "testHost", 80, false); + } + + @Test + public void retryListenerTest() throws Throwable { + HttpRequest request = mock(HttpRequest.class); + when(request.getURI()).thenReturn(new URI("http://listener")); + ClientHttpResponse clientHttpResponse = new MockClientHttpResponse(new byte[] {}, HttpStatus.OK); + LoadBalancedRetryPolicy policy = mock(LoadBalancedRetryPolicy.class); + when(policy.canRetryNextServer(any(LoadBalancedRetryContext.class))).thenReturn(true); + MyBackOffPolicy backOffPolicy = new MyBackOffPolicy(); + ServiceInstance serviceInstance = mock(ServiceInstance.class); + when(client.choose(eq("listener"), any())).thenReturn(serviceInstance); + when(client.execute(eq("listener"), eq(serviceInstance), any(LoadBalancerRequest.class))) + .thenThrow(new IOException()).thenReturn(clientHttpResponse); + serviceProperties.getServices().get("foo").getRetry().setEnabled(true); + MyRetryListener retryListener = new MyRetryListener(); + when(lbRequestFactory.createRequest(any(), any(), any())).thenReturn(mock(LoadBalancerRequest.class)); + RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, + new LoadBalancerProperties(), lbRequestFactory, + new MyLoadBalancedRetryFactory(policy, backOffPolicy, new RetryListener[] { retryListener }), lbFactory, + propertiesFactory); + byte[] body = new byte[] {}; + ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class); + ClientHttpResponse rsp = interceptor.intercept(request, body, execution); + verify(client, times(2)).execute(eq("listener"), eq(serviceInstance), any(LoadBalancerRequest.class)); + then(rsp).isEqualTo(clientHttpResponse); + verify(lbRequestFactory, times(2)).createRequest(request, body, execution); + then(backOffPolicy.getBackoffAttempts()).isEqualTo(1); + then(retryListener.getOnError()).isEqualTo(1); + } + + @Test + public void retryWithDefaultConstructorTest() throws Throwable { + HttpRequest request = mock(HttpRequest.class); + when(request.getURI()).thenReturn(new URI("http://default")); + ClientHttpResponse clientHttpResponse = new MockClientHttpResponse(new byte[] {}, HttpStatus.OK); + LoadBalancedRetryPolicy policy = mock(LoadBalancedRetryPolicy.class); + when(policy.canRetryNextServer(any(LoadBalancedRetryContext.class))).thenReturn(true); + MyBackOffPolicy backOffPolicy = new MyBackOffPolicy(); + ServiceInstance serviceInstance = mock(ServiceInstance.class); + when(client.choose(eq("default"), any())).thenReturn(serviceInstance); + when(client.execute(eq("default"), eq(serviceInstance), any(LoadBalancerRequest.class))) + .thenThrow(new IOException()).thenReturn(clientHttpResponse); + serviceProperties.getServices().get("foo").getRetry().setEnabled(true); + when(lbRequestFactory.createRequest(any(), any(), any())).thenReturn(mock(LoadBalancerRequest.class)); + RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, + new LoadBalancerProperties(), lbRequestFactory, new MyLoadBalancedRetryFactory(policy, backOffPolicy), + lbFactory, propertiesFactory); + byte[] body = new byte[] {}; + ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class); + ClientHttpResponse rsp = interceptor.intercept(request, body, execution); + verify(client, times(2)).execute(eq("default"), eq(serviceInstance), any(LoadBalancerRequest.class)); + then(rsp).isEqualTo(clientHttpResponse); + verify(lbRequestFactory, times(2)).createRequest(request, body, execution); + then(backOffPolicy.getBackoffAttempts()).isEqualTo(1); + } + + @Test(expected = TerminatedRetryException.class) + public void retryListenerTestNoRetry() throws Throwable { + HttpRequest request = mock(HttpRequest.class); + when(request.getURI()).thenReturn(new URI("http://noRetry")); + LoadBalancedRetryPolicy policy = mock(LoadBalancedRetryPolicy.class); + MyBackOffPolicy backOffPolicy = new MyBackOffPolicy(); + serviceProperties.getServices().put("noRetry", new LoadBalancerProperties()); + serviceProperties.getServices().get("noRetry").getRetry().setEnabled(true); + RetryListener myRetryListener = new RetryListenerSupport() { + @Override + public boolean open(RetryContext context, RetryCallback callback) { + return false; + } + }; + RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, + new LoadBalancerProperties(), lbRequestFactory, + new MyLoadBalancedRetryFactory(policy, backOffPolicy, new RetryListener[] { myRetryListener }), + lbFactory, propertiesFactory); + ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class); + interceptor.intercept(request, new byte[] {}, execution); + } + + @Test + public void shouldNotDuplicateLifecycleCalls() throws IOException, URISyntaxException { + Map lifecycleProcessors = new HashMap<>(); + lifecycleProcessors.put("testLifecycle", new TestLoadBalancerLifecycle()); + lifecycleProcessors.put("anotherLifecycle", new AnotherLoadBalancerLifecycle()); + when(lbFactory.getInstances("test", LoadBalancerLifecycle.class)).thenReturn(lifecycleProcessors); + HttpRequest request = mock(HttpRequest.class); + when(request.getURI()).thenReturn(new URI("http://test")); + TestLoadBalancerClient client = new TestLoadBalancerClient(); + RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, + new LoadBalancerProperties(), lbRequestFactory, loadBalancedRetryFactory, lbFactory, propertiesFactory); + + interceptor.intercept(request, new byte[] {}, mock(ClientHttpRequestExecution.class)); + + assertThat(((TestLoadBalancerLifecycle) lifecycleProcessors.get("testLifecycle")).getStartLog()).hasSize(1); + assertThat(((TestLoadBalancerLifecycle) lifecycleProcessors.get("testLifecycle")).getStartRequestLog()) + .hasSize(0); + assertThat(((TestLoadBalancerLifecycle) lifecycleProcessors.get("testLifecycle")).getCompleteLog()).hasSize(0); + assertThat(((TestLoadBalancerLifecycle) lifecycleProcessors.get("anotherLifecycle")).getStartLog()).hasSize(1); + assertThat(((TestLoadBalancerLifecycle) lifecycleProcessors.get("anotherLifecycle")).getStartRequestLog()) + .hasSize(0); + assertThat(((TestLoadBalancerLifecycle) lifecycleProcessors.get("anotherLifecycle")).getCompleteLog()) + .hasSize(0); + assertThat(((TestLoadBalancerLifecycle) client.getLifecycleProcessors().get("testLifecycle")).getStartLog()) + .hasSize(0); + assertThat( + ((TestLoadBalancerLifecycle) client.getLifecycleProcessors().get("testLifecycle")).getStartRequestLog()) + .hasSize(1); + assertThat(((TestLoadBalancerLifecycle) client.getLifecycleProcessors().get("testLifecycle")).getCompleteLog()) + .hasSize(1); + assertThat(((TestLoadBalancerLifecycle) client.getLifecycleProcessors().get("anotherLifecycle")).getStartLog()) + .hasSize(0); + assertThat( + ((TestLoadBalancerLifecycle) client.getLifecycleProcessors().get("testLifecycle")).getStartRequestLog()) + .hasSize(1); + assertThat( + ((TestLoadBalancerLifecycle) client.getLifecycleProcessors().get("anotherLifecycle")).getCompleteLog()) + .hasSize(1); + } + + static class MyLoadBalancedRetryFactory implements LoadBalancedRetryFactory { + + private final LoadBalancedRetryPolicy loadBalancedRetryPolicy; + + private BackOffPolicy backOffPolicy; + + private RetryListener[] retryListeners; + + MyLoadBalancedRetryFactory(LoadBalancedRetryPolicy loadBalancedRetryPolicy) { + this.loadBalancedRetryPolicy = loadBalancedRetryPolicy; + } + + MyLoadBalancedRetryFactory(LoadBalancedRetryPolicy loadBalancedRetryPolicy, BackOffPolicy backOffPolicy) { + this(loadBalancedRetryPolicy); + this.backOffPolicy = backOffPolicy; + } + + MyLoadBalancedRetryFactory(LoadBalancedRetryPolicy loadBalancedRetryPolicy, BackOffPolicy backOffPolicy, + RetryListener[] retryListeners) { + this(loadBalancedRetryPolicy, backOffPolicy); + this.retryListeners = retryListeners; + } + + @Override + public LoadBalancedRetryPolicy createRetryPolicy(String service, + ServiceInstanceChooser serviceInstanceChooser) { + return loadBalancedRetryPolicy; + } + + @Override + public BackOffPolicy createBackOffPolicy(String service) { + if (backOffPolicy == null) { + return new NoBackOffPolicy(); + } + else { + return backOffPolicy; + } + } + + @Override + public RetryListener[] createRetryListeners(String service) { + if (retryListeners == null) { + return new RetryListener[0]; + } + else { + return retryListeners; + } + } + + } + + static class MyBackOffPolicy implements BackOffPolicy { + + private int backoffAttempts = 0; + + @Override + public BackOffContext start(RetryContext retryContext) { + return new BackOffContext() { + @Override + protected Object clone() throws CloneNotSupportedException { + return super.clone(); + } + }; + } + + @Override + public void backOff(BackOffContext backOffContext) throws BackOffInterruptedException { + backoffAttempts++; + } + + int getBackoffAttempts() { + return backoffAttempts; + } + + } + + static class MyRetryListener extends RetryListenerSupport { + + private int onError = 0; + + @Override + public void onError(RetryContext retryContext, RetryCallback retryCallback, + Throwable throwable) { + onError++; + } + + int getOnError() { + return onError; + } + + } + + protected static class TestLoadBalancerClient implements LoadBalancerClient { + + private final Map lifecycleProcessors = new HashMap<>(); + + TestLoadBalancerClient() { + lifecycleProcessors.put("testLifecycle", new TestLoadBalancerLifecycle()); + lifecycleProcessors.put("anotherLifecycle", new AnotherLoadBalancerLifecycle()); + } + + @Override + public T execute(String serviceId, LoadBalancerRequest request) { + throw new UnsupportedOperationException("Not implemented"); + } + + @Override + public T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest request) { + Set supportedLoadBalancerProcessors = LoadBalancerLifecycleValidator + .getSupportedLifecycleProcessors(lifecycleProcessors, DefaultRequestContext.class, Object.class, + ServiceInstance.class); + supportedLoadBalancerProcessors.forEach(lifecycle -> lifecycle.onStartRequest(new DefaultRequest<>(), + new DefaultResponse(serviceInstance))); + T response = (T) new MockClientHttpResponse(new byte[] {}, HttpStatus.OK); + supportedLoadBalancerProcessors + .forEach(lifecycle -> lifecycle.onComplete(new CompletionContext(CompletionContext.Status.SUCCESS, + new DefaultRequest<>(), new DefaultResponse(defaultServiceInstance())))); + return response; + } + + @Override + public URI reconstructURI(ServiceInstance instance, URI original) { + throw new UnsupportedOperationException("Please, implement me."); + } + + @Override + public ServiceInstance choose(String serviceId) { + return defaultServiceInstance(); + } + + @Override + public ServiceInstance choose(String serviceId, Request request) { + return defaultServiceInstance(); + } + + Map getLifecycleProcessors() { + return lifecycleProcessors; + } + + } + + protected static class TestLoadBalancerLifecycle implements LoadBalancerLifecycle { + + final ConcurrentHashMap> startLog = new ConcurrentHashMap<>(); + + final ConcurrentHashMap> startRequestLog = new ConcurrentHashMap<>(); + + final ConcurrentHashMap> completeLog = new ConcurrentHashMap<>(); + + @Override + public boolean supports(Class requestContextClass, Class responseClass, Class serverTypeClass) { + return DefaultRequestContext.class.isAssignableFrom(requestContextClass) + && Object.class.isAssignableFrom(responseClass) + && ServiceInstance.class.isAssignableFrom(serverTypeClass); + } + + @Override + public void onStart(Request request) { + startLog.put(getName() + UUID.randomUUID(), request); + } + + @Override + public void onStartRequest(Request request, Response lbResponse) { + startRequestLog.put(getName() + UUID.randomUUID(), request); + } + + @Override + public void onComplete(CompletionContext completionContext) { + completeLog.put(getName() + UUID.randomUUID(), completionContext); + } + + ConcurrentHashMap> getStartLog() { + return startLog; + } + + ConcurrentHashMap> getCompleteLog() { + return completeLog; + } + + ConcurrentHashMap> getStartRequestLog() { + return startRequestLog; + } + + protected String getName() { + return getClass().getSimpleName(); + } + + } + + protected static class AnotherLoadBalancerLifecycle extends TestLoadBalancerLifecycle { + + @Override + protected String getName() { + return getClass().getSimpleName(); + } + + } + +} diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/RetryLoadBalancerInterceptorTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/RetryLoadBalancerInterceptorTests.java index 3a8126c9b..3956e77fe 100644 --- a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/RetryLoadBalancerInterceptorTests.java +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/RetryLoadBalancerInterceptorTests.java @@ -80,6 +80,10 @@ public class RetryLoadBalancerInterceptorTests { private LoadBalancerProperties properties; + private LoadBalancerServiceProperties serviceProperties; + + private LoadBalancerPropertiesFactory propertiesFactory; + private ReactiveLoadBalancer.Factory lbFactory; @Before @@ -87,6 +91,8 @@ public void setUp() { client = mock(LoadBalancerClient.class); lbRequestFactory = mock(LoadBalancerRequestFactory.class); properties = new LoadBalancerProperties(); + serviceProperties = new LoadBalancerServiceProperties(); + propertiesFactory = new LoadBalancerPropertiesFactory(properties, serviceProperties, false); lbFactory = mock(ReactiveLoadBalancer.Factory.class); } @@ -121,7 +127,7 @@ public void interceptInvalidHost() throws Throwable { when(request.getURI()).thenReturn(new URI("http://foo_underscore")); properties.getRetry().setEnabled(true); RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, properties, - lbRequestFactory, loadBalancedRetryFactory, lbFactory); + lbRequestFactory, loadBalancedRetryFactory, lbFactory, propertiesFactory); byte[] body = new byte[] {}; ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class); interceptor.intercept(request, body, execution); @@ -139,7 +145,7 @@ public void interceptNeverRetry() throws Throwable { when(lbRequestFactory.createRequest(any(), any(), any())).thenReturn(mock(LoadBalancerRequest.class)); properties.getRetry().setEnabled(true); RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, properties, - lbRequestFactory, loadBalancedRetryFactory, lbFactory); + lbRequestFactory, loadBalancedRetryFactory, lbFactory, propertiesFactory); byte[] body = new byte[] {}; ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class); interceptor.intercept(request, body, execution); @@ -159,7 +165,7 @@ public void interceptSuccess() throws Throwable { when(lbRequestFactory.createRequest(any(), any(), any())).thenReturn(mock(LoadBalancerRequest.class)); properties.getRetry().setEnabled(true); RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, properties, - lbRequestFactory, new MyLoadBalancedRetryFactory(policy), lbFactory); + lbRequestFactory, new MyLoadBalancedRetryFactory(policy), lbFactory, propertiesFactory); byte[] body = new byte[] {}; ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class); ClientHttpResponse rsp = interceptor.intercept(request, body, execution); @@ -185,7 +191,7 @@ public void interceptRetryOnStatusCode() throws Throwable { .thenReturn(clientHttpResponseNotFound).thenReturn(clientHttpResponseOk); properties.getRetry().setEnabled(true); RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, properties, - lbRequestFactory, new MyLoadBalancedRetryFactory(policy), lbFactory); + lbRequestFactory, new MyLoadBalancedRetryFactory(policy), lbFactory, propertiesFactory); byte[] body = new byte[] {}; ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class); ClientHttpResponse rsp = interceptor.intercept(request, body, execution); @@ -218,7 +224,7 @@ public void interceptRetryFailOnStatusCode() throws Throwable { byte[] body = new byte[] {}; ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class); RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, properties, - lbRequestFactory, new MyLoadBalancedRetryFactory(policy), lbFactory); + lbRequestFactory, new MyLoadBalancedRetryFactory(policy), lbFactory, propertiesFactory); ClientHttpResponse rsp = interceptor.intercept(request, body, execution); verify(client, times(1)).execute(eq("foo"), eq(serviceInstance), @@ -248,7 +254,7 @@ public void interceptRetry() throws Throwable { when(lbRequestFactory.createRequest(any(), any(), any())).thenReturn(mock(LoadBalancerRequest.class)); properties.getRetry().setEnabled(true); RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, properties, - lbRequestFactory, new MyLoadBalancedRetryFactory(policy, backOffPolicy), lbFactory); + lbRequestFactory, new MyLoadBalancedRetryFactory(policy, backOffPolicy), lbFactory, propertiesFactory); byte[] body = new byte[] {}; ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class); ClientHttpResponse rsp = interceptor.intercept(request, body, execution); @@ -272,7 +278,7 @@ public void interceptFailedRetry() throws Exception { when(lbRequestFactory.createRequest(any(), any(), any())).thenReturn(mock(LoadBalancerRequest.class)); properties.getRetry().setEnabled(true); RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, properties, - lbRequestFactory, new MyLoadBalancedRetryFactory(policy), lbFactory); + lbRequestFactory, new MyLoadBalancedRetryFactory(policy), lbFactory, propertiesFactory); byte[] body = new byte[] {}; ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class); interceptor.intercept(request, body, execution); @@ -300,8 +306,8 @@ public void retryListenerTest() throws Throwable { when(lbRequestFactory.createRequest(any(), any(), any())).thenReturn(mock(LoadBalancerRequest.class)); RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, properties, lbRequestFactory, - new MyLoadBalancedRetryFactory(policy, backOffPolicy, new RetryListener[] { retryListener }), - lbFactory); + new MyLoadBalancedRetryFactory(policy, backOffPolicy, new RetryListener[] { retryListener }), lbFactory, + propertiesFactory); byte[] body = new byte[] {}; ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class); ClientHttpResponse rsp = interceptor.intercept(request, body, execution); @@ -327,7 +333,7 @@ public void retryWithDefaultConstructorTest() throws Throwable { properties.getRetry().setEnabled(true); when(lbRequestFactory.createRequest(any(), any(), any())).thenReturn(mock(LoadBalancerRequest.class)); RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, properties, - lbRequestFactory, new MyLoadBalancedRetryFactory(policy, backOffPolicy), lbFactory); + lbRequestFactory, new MyLoadBalancedRetryFactory(policy, backOffPolicy), lbFactory, propertiesFactory); byte[] body = new byte[] {}; ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class); ClientHttpResponse rsp = interceptor.intercept(request, body, execution); @@ -368,7 +374,7 @@ public void shouldNotDuplicateLifecycleCalls() throws IOException, URISyntaxExce when(request.getURI()).thenReturn(new URI("http://test")); TestLoadBalancerClient client = new TestLoadBalancerClient(); RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, properties, - lbRequestFactory, loadBalancedRetryFactory, lbFactory); + lbRequestFactory, loadBalancedRetryFactory, lbFactory, propertiesFactory); interceptor.intercept(request, new byte[] {}, mock(ClientHttpRequestExecution.class)); diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/LoadBalancerClientRequestTransformerTest.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/LoadBalancerClientRequestTransformerTest.java index 5e4b27f90..a525c5cfd 100644 --- a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/LoadBalancerClientRequestTransformerTest.java +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/LoadBalancerClientRequestTransformerTest.java @@ -26,6 +26,8 @@ import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; +import org.springframework.cloud.client.loadbalancer.LoadBalancerPropertiesFactory; +import org.springframework.cloud.client.loadbalancer.LoadBalancerServiceProperties; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; @@ -49,8 +51,13 @@ class LoadBalancerClientRequestTransformerTest { private final LoadBalancerProperties properties = new LoadBalancerProperties(); + private final LoadBalancerServiceProperties serviceProperties = new LoadBalancerServiceProperties(); + + private final LoadBalancerPropertiesFactory propertiesFactory = new LoadBalancerPropertiesFactory(properties, + serviceProperties, false); + private final LoadBalancerRetryPolicy policy = new RetryableExchangeFilterFunctionLoadBalancerRetryPolicy( - properties); + properties, propertiesFactory); private final ReactiveLoadBalancer.Factory factory = mock(ReactiveLoadBalancer.Factory.class); @@ -75,7 +82,7 @@ void setUp() { void transformReactorLoadBalancerExchangeFilterFunction() { ArgumentCaptor captor = ArgumentCaptor.forClass(ClientRequest.class); ReactorLoadBalancerExchangeFilterFunction filterFunction = new ReactorLoadBalancerExchangeFilterFunction( - factory, properties, Arrays.asList(new Transformer1(), new Transformer2())); + factory, properties, Arrays.asList(new Transformer1(), new Transformer2()), propertiesFactory); filterFunction.filter(clientRequest, next).subscribe(); verify(next).exchange(captor.capture()); HttpHeaders headers = captor.getValue().headers(); @@ -87,7 +94,7 @@ void transformReactorLoadBalancerExchangeFilterFunction() { void transformRetryableLoadBalancerExchangeFilterFunction() { ArgumentCaptor captor = ArgumentCaptor.forClass(ClientRequest.class); RetryableLoadBalancerExchangeFilterFunction filterFunction = new RetryableLoadBalancerExchangeFilterFunction( - policy, factory, properties, Arrays.asList(new Transformer1(), new Transformer2())); + policy, factory, properties, Arrays.asList(new Transformer1(), new Transformer2()), propertiesFactory); filterFunction.filter(clientRequest, next).subscribe(); verify(next).exchange(captor.capture()); HttpHeaders headers = captor.getValue().headers(); diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerClientAutoConfigurationTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerClientAutoConfigurationTests.java index b5ffe50ca..9040a7a75 100644 --- a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerClientAutoConfigurationTests.java +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerClientAutoConfigurationTests.java @@ -25,6 +25,8 @@ import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory; import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; +import org.springframework.cloud.client.loadbalancer.LoadBalancerPropertiesFactory; +import org.springframework.cloud.client.loadbalancer.LoadBalancerServiceProperties; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -167,6 +169,17 @@ LoadBalancerProperties loadBalancerProperties() { return new LoadBalancerProperties(); } + @Bean + public LoadBalancerServiceProperties loadBalancerServiceProperties() { + return new LoadBalancerServiceProperties(); + } + + @Bean + public LoadBalancerPropertiesFactory loadBalancerPropertiesFactory(LoadBalancerProperties properties, + LoadBalancerServiceProperties serviceProperties) { + return new LoadBalancerPropertiesFactory(properties, serviceProperties, false); + } + } @Configuration diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerExchangeFilterFunctionTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerExchangeFilterFunctionTests.java index 5ddcf7a84..462181219 100644 --- a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerExchangeFilterFunctionTests.java +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerExchangeFilterFunctionTests.java @@ -42,6 +42,8 @@ import org.springframework.cloud.client.loadbalancer.DefaultRequestContext; import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycle; import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; +import org.springframework.cloud.client.loadbalancer.LoadBalancerPropertiesFactory; +import org.springframework.cloud.client.loadbalancer.LoadBalancerServiceProperties; import org.springframework.cloud.client.loadbalancer.Request; import org.springframework.cloud.client.loadbalancer.Response; import org.springframework.cloud.client.loadbalancer.ResponseData; @@ -206,6 +208,17 @@ LoadBalancerProperties loadBalancerProperties() { return new LoadBalancerProperties(); } + @Bean + public LoadBalancerServiceProperties loadBalancerServiceProperties() { + return new LoadBalancerServiceProperties(); + } + + @Bean + public LoadBalancerPropertiesFactory loadBalancerPropertiesFactory(LoadBalancerProperties properties, + LoadBalancerServiceProperties serviceProperties) { + return new LoadBalancerPropertiesFactory(properties, serviceProperties, false); + } + } protected static class TestLoadBalancerLifecycle implements LoadBalancerLifecycle { diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionIntegrationTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionIntegrationTests.java index a1c3eb3ca..276d03145 100644 --- a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionIntegrationTests.java +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionIntegrationTests.java @@ -44,6 +44,8 @@ import org.springframework.cloud.client.loadbalancer.DefaultRequestContext; import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycle; import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; +import org.springframework.cloud.client.loadbalancer.LoadBalancerPropertiesFactory; +import org.springframework.cloud.client.loadbalancer.LoadBalancerServiceProperties; import org.springframework.cloud.client.loadbalancer.Request; import org.springframework.cloud.client.loadbalancer.Response; import org.springframework.cloud.client.loadbalancer.ResponseData; @@ -264,11 +266,23 @@ LoadBalancerProperties loadBalancerProperties() { return new LoadBalancerProperties(); } + @Bean + public LoadBalancerServiceProperties loadBalancerServiceProperties() { + return new LoadBalancerServiceProperties(); + } + + @Bean + public LoadBalancerPropertiesFactory loadBalancerPropertiesFactory(LoadBalancerProperties properties, + LoadBalancerServiceProperties serviceProperties) { + return new LoadBalancerPropertiesFactory(properties, serviceProperties, false); + } + @Bean RetryableLoadBalancerExchangeFilterFunction exchangeFilterFunction(LoadBalancerProperties properties, - ReactiveLoadBalancer.Factory factory) { + ReactiveLoadBalancer.Factory factory, LoadBalancerPropertiesFactory propertiesFactory) { return new RetryableLoadBalancerExchangeFilterFunction( - new RetryableExchangeFilterFunctionLoadBalancerRetryPolicy(properties), factory, properties); + new RetryableExchangeFilterFunctionLoadBalancerRetryPolicy(properties, propertiesFactory), factory, + properties); } } diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionPerServiceIntegrationTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionPerServiceIntegrationTests.java new file mode 100644 index 000000000..7f4b62d1b --- /dev/null +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionPerServiceIntegrationTests.java @@ -0,0 +1,346 @@ +/* + * 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.cloud.client.loadbalancer.reactive; + +import java.net.URI; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.SpringBootConfiguration; +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.discovery.DiscoveryClient; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; +import org.springframework.cloud.client.discovery.simple.SimpleDiscoveryProperties; +import org.springframework.cloud.client.loadbalancer.CompletionContext; +import org.springframework.cloud.client.loadbalancer.DefaultRequestContext; +import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycle; +import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; +import org.springframework.cloud.client.loadbalancer.LoadBalancerPropertiesFactory; +import org.springframework.cloud.client.loadbalancer.LoadBalancerServiceProperties; +import org.springframework.cloud.client.loadbalancer.Request; +import org.springframework.cloud.client.loadbalancer.Response; +import org.springframework.cloud.client.loadbalancer.ResponseData; +import org.springframework.context.annotation.Bean; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.reactive.function.client.ClientResponse; +import org.springframework.web.reactive.function.client.WebClient; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.BDDAssertions.then; +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; + +/** + * Integration tests for {@link RetryableLoadBalancerExchangeFilterFunction}. + * + * @author Olga Maciaszek-Sharma + * @author Andrii Bohutskyi + * @since 3.0.0 + */ +@SpringBootTest(webEnvironment = RANDOM_PORT) +class RetryableLoadBalancerExchangeFilterFunctionPerServiceIntegrationTests { + + @Autowired + private RetryableLoadBalancerExchangeFilterFunction loadBalancerFunction; + + @Autowired + private SimpleDiscoveryProperties properties; + + @Autowired + private LoadBalancerServiceProperties serviceProperties; + + @Autowired + private ReactiveLoadBalancer.Factory factory; + + @LocalServerPort + private int port; + + @BeforeEach + void setUp() { + DefaultServiceInstance instance = new DefaultServiceInstance(); + instance.setServiceId("testservice"); + instance.setUri(URI.create("http://localhost:" + port)); + DefaultServiceInstance instanceWithNoLifecycleProcessors = new DefaultServiceInstance(); + instanceWithNoLifecycleProcessors.setServiceId("serviceWithNoLifecycleProcessors"); + instanceWithNoLifecycleProcessors.setUri(URI.create("http://localhost:" + port)); + properties.getInstances().put("testservice", Collections.singletonList(instance)); + properties.getInstances().put("serviceWithNoLifecycleProcessors", + Collections.singletonList(instanceWithNoLifecycleProcessors)); + serviceProperties.getServices().put("testservice", new LoadBalancerProperties()); + } + + @Test + void loadBalancerLifecycleCallbacksExecuted() { + final String callbackTestHint = "callbackTestHint"; + serviceProperties.getServices().get("testservice").getHint().put("testservice", "callbackTestHint"); + + ClientResponse clientResponse = WebClient.builder().baseUrl("http://testservice") + .filter(this.loadBalancerFunction).build().get().uri("/callback").exchange().block(); + + Collection> lifecycleLogRequests = ((TestLoadBalancerLifecycle) factory + .getInstances("testservice", LoadBalancerLifecycle.class).get("loadBalancerLifecycle")).getStartLog() + .values(); + Collection> lifecycleLogStartRequests = ((TestLoadBalancerLifecycle) factory + .getInstances("testservice", LoadBalancerLifecycle.class).get("loadBalancerLifecycle")) + .getStartRequestLog().values(); + Collection> anotherLifecycleLogRequests = ((AnotherLoadBalancerLifecycle) factory + .getInstances("testservice", LoadBalancerLifecycle.class).get("anotherLoadBalancerLifecycle")) + .getCompleteLog().values(); + then(clientResponse.statusCode()).isEqualTo(HttpStatus.OK); + assertThat(lifecycleLogRequests).extracting(request -> ((DefaultRequestContext) request.getContext()).getHint()) + .contains(callbackTestHint); + assertThat(lifecycleLogStartRequests) + .extracting(request -> ((DefaultRequestContext) request.getContext()).getHint()) + .contains(callbackTestHint); + assertThat(anotherLifecycleLogRequests) + .extracting(completionContext -> ((ResponseData) completionContext.getClientResponse()).getRequestData() + .getHttpMethod()) + .contains(HttpMethod.GET); + } + + @Test + void correctResponseReturnedForExistingHostAndInstancePresent() { + ClientResponse clientResponse = WebClient.builder().baseUrl("http://testservice") + .filter(this.loadBalancerFunction).build().get().uri("/hello").exchange().block(); + + then(clientResponse.statusCode()).isEqualTo(HttpStatus.OK); + then(clientResponse.bodyToMono(String.class).block()).isEqualTo("Hello World"); + } + + @Test + void correctResponseReturnedAfterRetryingOnSameServiceInstance() { + serviceProperties.getServices().get("testservice").getRetry().setMaxRetriesOnSameServiceInstance(1); + serviceProperties.getServices().get("testservice").getRetry().getRetryableStatusCodes().add(500); + + ClientResponse clientResponse = WebClient.builder().baseUrl("http://testservice") + .filter(this.loadBalancerFunction).build().get().uri("/exception").exchange().block(); + + then(clientResponse.statusCode()).isEqualTo(HttpStatus.OK); + then(clientResponse.bodyToMono(String.class).block()).isEqualTo("Hello World!"); + } + + @Test + void correctResponseReturnedAfterRetryingOnNextServiceInstanceWithBackoff() { + serviceProperties.getServices().put("retrytest", new LoadBalancerProperties()); + serviceProperties.getServices().get("retrytest").getRetry().getBackoff().setEnabled(true); + serviceProperties.getServices().get("retrytest").getRetry().setMaxRetriesOnSameServiceInstance(1); + + DefaultServiceInstance goodRetryTestInstance = new DefaultServiceInstance(); + goodRetryTestInstance.setServiceId("retrytest"); + goodRetryTestInstance.setUri(URI.create("http://localhost:" + port)); + DefaultServiceInstance badRetryTestInstance = new DefaultServiceInstance(); + badRetryTestInstance.setServiceId("retrytest"); + badRetryTestInstance.setUri(URI.create("http://localhost:" + 8080)); + properties.getInstances().put("retrytest", Arrays.asList(badRetryTestInstance, goodRetryTestInstance)); + serviceProperties.getServices().get("retrytest").getRetry().getRetryableStatusCodes().add(500); + + ClientResponse clientResponse = WebClient.builder().baseUrl("http://retrytest") + .filter(this.loadBalancerFunction).build().get().uri("/hello").exchange().block(); + + then(clientResponse.statusCode()).isEqualTo(HttpStatus.OK); + then(clientResponse.bodyToMono(String.class).block()).isEqualTo("Hello World"); + + ClientResponse secondClientResponse = WebClient.builder().baseUrl("http://retrytest") + .filter(this.loadBalancerFunction).build().get().uri("/hello").exchange().block(); + + then(secondClientResponse.statusCode()).isEqualTo(HttpStatus.OK); + then(secondClientResponse.bodyToMono(String.class).block()).isEqualTo("Hello World"); + } + + @Test + void serviceUnavailableReturnedWhenNoInstancePresent() { + ClientResponse clientResponse = WebClient.builder().baseUrl("http://xxx").filter(this.loadBalancerFunction) + .build().get().exchange().block(); + + then(clientResponse.statusCode()).isEqualTo(HttpStatus.SERVICE_UNAVAILABLE); + } + + @Test + @Disabled + // FIXME 3.0.0 + void badRequestReturnedForIncorrectHost() { + ClientResponse clientResponse = WebClient.builder().baseUrl("http:///xxx").filter(this.loadBalancerFunction) + .build().get().exchange().block(); + + then(clientResponse.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST); + } + + @Test + void exceptionNotThrownWhenFactoryReturnsNullLifecycleProcessorsMap() { + assertThatCode(() -> WebClient.builder().baseUrl("http://serviceWithNoLifecycleProcessors") + .filter(this.loadBalancerFunction).build().get().uri("/hello").exchange().block()) + .doesNotThrowAnyException(); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @EnableDiscoveryClient + @EnableAutoConfiguration + @SpringBootConfiguration(proxyBeanMethods = false) + @RestController + static class Config { + + AtomicInteger exceptionCallsCount = new AtomicInteger(); + + @GetMapping("/hello") + public String hello() { + return "Hello World"; + } + + @GetMapping("/callback") + String callbackTestResult() { + return "callbackTestResult"; + } + + @GetMapping("/exception") + String exception() { + int callCount = exceptionCallsCount.incrementAndGet(); + if (callCount % 2 != 0) { + throw new IllegalStateException("Test!"); + } + return "Hello World!"; + } + + @Bean + ReactiveLoadBalancer.Factory reactiveLoadBalancerFactory(DiscoveryClient discoveryClient) { + return new ReactiveLoadBalancer.Factory() { + + private final TestLoadBalancerLifecycle testLoadBalancerLifecycle = new TestLoadBalancerLifecycle(); + + private final TestLoadBalancerLifecycle anotherLoadBalancerLifecycle = new AnotherLoadBalancerLifecycle(); + + @Override + public ReactiveLoadBalancer getInstance(String serviceId) { + return new DiscoveryClientBasedReactiveLoadBalancer( + serviceId, discoveryClient); + } + + @Override + public Map getInstances(String name, Class type) { + if (name.equals("serviceWithNoLifecycleProcessors")) { + return null; + } + Map lifecycleProcessors = new HashMap<>(); + lifecycleProcessors.put("loadBalancerLifecycle", testLoadBalancerLifecycle); + lifecycleProcessors.put("anotherLoadBalancerLifecycle", anotherLoadBalancerLifecycle); + return lifecycleProcessors; + } + + @Override + public X getInstance(String name, Class clazz, Class... generics) { + return null; + } + }; + } + + @Bean + LoadBalancerProperties loadBalancerProperties() { + return new LoadBalancerProperties(); + } + + @Bean + public LoadBalancerServiceProperties loadBalancerServiceProperties() { + return new LoadBalancerServiceProperties(); + } + + @Bean + public LoadBalancerPropertiesFactory loadBalancerPropertiesFactory(LoadBalancerProperties properties, + LoadBalancerServiceProperties serviceProperties) { + return new LoadBalancerPropertiesFactory(properties, serviceProperties, true); + } + + @Bean + RetryableLoadBalancerExchangeFilterFunction exchangeFilterFunction( + LoadBalancerProperties properties, + ReactiveLoadBalancer.Factory factory, + LoadBalancerPropertiesFactory propertiesFactory) { + return new RetryableLoadBalancerExchangeFilterFunction( + new RetryableExchangeFilterFunctionLoadBalancerRetryPolicy(properties, propertiesFactory), + factory, properties, Collections.emptyList(), propertiesFactory); + } + + } + + protected static class TestLoadBalancerLifecycle implements LoadBalancerLifecycle { + + Map> startLog = new ConcurrentHashMap<>(); + + Map> startRequestLog = new ConcurrentHashMap<>(); + + Map> completeLog = new ConcurrentHashMap<>(); + + @Override + public void onStart(Request request) { + startLog.put(getName() + UUID.randomUUID(), request); + } + + @Override + public void onStartRequest(Request request, Response lbResponse) { + startRequestLog.put(getName() + UUID.randomUUID(), request); + } + + @Override + public void onComplete(CompletionContext completionContext) { + completeLog.clear(); + completeLog.put(getName() + UUID.randomUUID(), completionContext); + } + + Map> getStartLog() { + return startLog; + } + + Map> getCompleteLog() { + return completeLog; + } + + Map> getStartRequestLog() { + return startRequestLog; + } + + protected String getName() { + return this.getClass().getSimpleName(); + } + + } + + protected static class AnotherLoadBalancerLifecycle extends TestLoadBalancerLifecycle { + + @Override + protected String getName() { + return this.getClass().getSimpleName(); + } + + } + +} diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionPerServiceTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionPerServiceTests.java new file mode 100644 index 000000000..5b9e1a57b --- /dev/null +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionPerServiceTests.java @@ -0,0 +1,164 @@ +/* + * 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.client.loadbalancer.reactive; + +import java.io.IOException; +import java.net.URI; +import java.util.Collections; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InOrder; +import reactor.core.publisher.Mono; + +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; +import org.springframework.cloud.client.loadbalancer.LoadBalancerPropertiesFactory; +import org.springframework.cloud.client.loadbalancer.LoadBalancerServiceProperties; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.web.reactive.function.client.ClientRequest; +import org.springframework.web.reactive.function.client.ClientResponse; +import org.springframework.web.reactive.function.client.ExchangeFunction; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * Tests for {@link RetryableLoadBalancerExchangeFilterFunction}. + * + * @author Olga Maciaszek-Sharma + * @author Andrii Bohutskyi + * @since 3.0.0 + */ +@SuppressWarnings("unchecked") +class RetryableLoadBalancerExchangeFilterFunctionPerServiceTests { + + private final LoadBalancerServiceProperties serviceProperties = new LoadBalancerServiceProperties(); + + private final LoadBalancerPropertiesFactory propertiesFactory = new LoadBalancerPropertiesFactory(new LoadBalancerProperties(), + serviceProperties, true); + + private final LoadBalancerRetryPolicy policy = new RetryableExchangeFilterFunctionLoadBalancerRetryPolicy( + new LoadBalancerProperties(), propertiesFactory); + + private final ReactiveLoadBalancer.Factory factory = mock(ReactiveLoadBalancer.Factory.class); + + private final RetryableLoadBalancerExchangeFilterFunction filterFunction = new RetryableLoadBalancerExchangeFilterFunction( + policy, factory, new LoadBalancerProperties(), Collections.emptyList(), propertiesFactory); + + private final ClientRequest clientRequest = mock(ClientRequest.class); + + private final ExchangeFunction next = mock(ExchangeFunction.class); + + private final ClientResponse clientResponse = mock(ClientResponse.class); + + private final InOrder inOrder = inOrder(next, factory); + + @BeforeEach + void setUp() { + serviceProperties.getServices().put("test", new LoadBalancerProperties()); + serviceProperties.getServices().get("test").getRetry().setMaxRetriesOnSameServiceInstance(1); + serviceProperties.getServices().get("test").getRetry().getRetryableStatusCodes().add(404); + + when(clientRequest.url()).thenReturn(URI.create("http://test")); + when(factory.getInstance("test")).thenReturn(new TestReactiveLoadBalancer()); + when(clientRequest.headers()).thenReturn(new HttpHeaders()); + when(clientRequest.cookies()).thenReturn(new HttpHeaders()); + + } + + @Test + void shouldRetryOnSameAndNextServiceInstanceOnException() { + when(clientRequest.method()).thenReturn(HttpMethod.GET); + when(next.exchange(any())).thenThrow(new IllegalStateException(new IOException())); + + filterFunction.filter(clientRequest, next).subscribe(); + + inOrder.verify(factory, times(1)).getInstance(any()); + inOrder.verify(next, times(2)).exchange(any()); + inOrder.verify(factory, times(1)).getInstance(any()); + inOrder.verify(next, times(2)).exchange(any()); + } + + @Test + void shouldRetryOnSameAndNextServiceInstanceOnRetryableStatusCode() { + when(clientRequest.method()).thenReturn(HttpMethod.GET); + when(clientResponse.statusCode()).thenReturn(HttpStatus.NOT_FOUND); + when(next.exchange(any())).thenReturn(Mono.just(clientResponse)); + + filterFunction.filter(clientRequest, next).subscribe(); + + inOrder.verify(factory, times(1)).getInstance(any()); + inOrder.verify(next, times(2)).exchange(any()); + inOrder.verify(factory, times(1)).getInstance(any()); + inOrder.verify(next, times(2)).exchange(any()); + } + + @Test + void shouldNotRetryWhenNoRetryableExceptionOrStatusCode() { + when(clientRequest.method()).thenReturn(HttpMethod.GET); + when(clientResponse.statusCode()).thenReturn(HttpStatus.OK); + when(next.exchange(any())).thenReturn(Mono.just(clientResponse)); + + filterFunction.filter(clientRequest, next).subscribe(); + + verify(next, times(1)).exchange(any()); + verify(factory, times(1)).getInstance(any()); + } + + @Test + void shouldNotRetryOnMethodOtherThanGet() { + when(clientRequest.method()).thenReturn(HttpMethod.POST); + when(clientResponse.statusCode()).thenReturn(HttpStatus.NOT_FOUND); + when(next.exchange(any())).thenReturn(Mono.just(clientResponse)); + + filterFunction.filter(clientRequest, next).subscribe(); + + verify(next, times(1)).exchange(any()); + verify(factory, times(1)).getInstance(any()); + } + + @Test + void shouldRetryOnMethodOtherThanGetWhenEnabled() { + LoadBalancerProperties properties = new LoadBalancerProperties(); + properties.getRetry().setRetryOnAllOperations(true); + properties.getRetry().setMaxRetriesOnSameServiceInstance(1); + properties.getRetry().getRetryableStatusCodes().add(404); + final LoadBalancerPropertiesFactory propertiesFactory = new LoadBalancerPropertiesFactory(properties, + new LoadBalancerServiceProperties(), false); + LoadBalancerRetryPolicy policy = new RetryableExchangeFilterFunctionLoadBalancerRetryPolicy(properties, + propertiesFactory); + RetryableLoadBalancerExchangeFilterFunction filterFunction = new RetryableLoadBalancerExchangeFilterFunction( + policy, factory, properties, Collections.emptyList(), propertiesFactory); + when(clientRequest.method()).thenReturn(HttpMethod.POST); + when(clientResponse.statusCode()).thenReturn(HttpStatus.NOT_FOUND); + when(next.exchange(any())).thenReturn(Mono.just(clientResponse)); + + filterFunction.filter(clientRequest, next).subscribe(); + + inOrder.verify(factory, times(1)).getInstance(any()); + inOrder.verify(next, times(2)).exchange(any()); + inOrder.verify(factory, times(1)).getInstance(any()); + inOrder.verify(next, times(2)).exchange(any()); + } +} diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionTests.java index 5a183c8d0..7f0706030 100644 --- a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionTests.java +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionTests.java @@ -27,6 +27,8 @@ import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; +import org.springframework.cloud.client.loadbalancer.LoadBalancerPropertiesFactory; +import org.springframework.cloud.client.loadbalancer.LoadBalancerServiceProperties; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; @@ -52,13 +54,18 @@ class RetryableLoadBalancerExchangeFilterFunctionTests { private final LoadBalancerProperties properties = new LoadBalancerProperties(); + private final LoadBalancerServiceProperties serviceProperties = new LoadBalancerServiceProperties(); + + private final LoadBalancerPropertiesFactory propertiesFactory = new LoadBalancerPropertiesFactory(properties, + serviceProperties, false); + private final LoadBalancerRetryPolicy policy = new RetryableExchangeFilterFunctionLoadBalancerRetryPolicy( - properties); + properties, propertiesFactory); private final ReactiveLoadBalancer.Factory factory = mock(ReactiveLoadBalancer.Factory.class); private final RetryableLoadBalancerExchangeFilterFunction filterFunction = new RetryableLoadBalancerExchangeFilterFunction( - policy, factory, properties, Collections.emptyList()); + policy, factory, properties, Collections.emptyList(), propertiesFactory); private final ClientRequest clientRequest = mock(ClientRequest.class); @@ -136,9 +143,12 @@ void shouldRetryOnMethodOtherThanGetWhenEnabled() { properties.getRetry().setRetryOnAllOperations(true); properties.getRetry().setMaxRetriesOnSameServiceInstance(1); properties.getRetry().getRetryableStatusCodes().add(404); - LoadBalancerRetryPolicy policy = new RetryableExchangeFilterFunctionLoadBalancerRetryPolicy(properties); + final LoadBalancerPropertiesFactory propertiesFactory = new LoadBalancerPropertiesFactory(properties, + new LoadBalancerServiceProperties(), false); + LoadBalancerRetryPolicy policy = new RetryableExchangeFilterFunctionLoadBalancerRetryPolicy(properties, + propertiesFactory); RetryableLoadBalancerExchangeFilterFunction filterFunction = new RetryableLoadBalancerExchangeFilterFunction( - policy, factory, properties, Collections.emptyList()); + policy, factory, properties, Collections.emptyList(), propertiesFactory); when(clientRequest.method()).thenReturn(HttpMethod.POST); when(clientResponse.statusCode()).thenReturn(HttpStatus.NOT_FOUND); when(next.exchange(any())).thenReturn(Mono.just(clientResponse)); diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/blocking/client/BlockingLoadBalancerClient.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/blocking/client/BlockingLoadBalancerClient.java index e689588b0..f687e10e7 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/blocking/client/BlockingLoadBalancerClient.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/blocking/client/BlockingLoadBalancerClient.java @@ -32,6 +32,7 @@ import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycle; import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycleValidator; import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; +import org.springframework.cloud.client.loadbalancer.LoadBalancerPropertiesFactory; import org.springframework.cloud.client.loadbalancer.LoadBalancerRequest; import org.springframework.cloud.client.loadbalancer.LoadBalancerRequestAdapter; import org.springframework.cloud.client.loadbalancer.LoadBalancerUriTools; @@ -49,6 +50,7 @@ * The default {@link LoadBalancerClient} implementation. * * @author Olga Maciaszek-Sharma + * @author Andrii Bohutskyi * @since 2.2.0 */ @SuppressWarnings({ "unchecked", "rawtypes" }) @@ -58,11 +60,23 @@ public class BlockingLoadBalancerClient implements LoadBalancerClient { private final LoadBalancerProperties properties; + private LoadBalancerPropertiesFactory propertiesFactory; + + /** + * @deprecated Deprecated in favor of + * {@link #BlockingLoadBalancerClient(LoadBalancerClientFactory, LoadBalancerProperties, LoadBalancerPropertiesFactory)} + */ + @Deprecated public BlockingLoadBalancerClient(LoadBalancerClientFactory loadBalancerClientFactory, LoadBalancerProperties properties) { this.loadBalancerClientFactory = loadBalancerClientFactory; this.properties = properties; + } + public BlockingLoadBalancerClient(LoadBalancerClientFactory loadBalancerClientFactory, + LoadBalancerProperties properties, LoadBalancerPropertiesFactory propertiesFactory) { + this(loadBalancerClientFactory, properties); + this.propertiesFactory = propertiesFactory; } @Override @@ -155,9 +169,18 @@ public ServiceInstance choose(String serviceId, Request request) { } private String getHint(String serviceId) { - String defaultHint = properties.getHint().getOrDefault("default", "default"); - String hintPropertyValue = properties.getHint().get(serviceId); + String defaultHint = getLoadBalancerProperties(serviceId).getHint().getOrDefault("default", "default"); + String hintPropertyValue = getLoadBalancerProperties(serviceId).getHint().get(serviceId); return hintPropertyValue != null ? hintPropertyValue : defaultHint; } + @Deprecated + private LoadBalancerProperties getLoadBalancerProperties(String serviceId) { + if (propertiesFactory != null) { + return propertiesFactory.getLoadBalancerProperties(serviceId); + } else { + return properties; + } + } + } diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/blocking/retry/BlockingLoadBalancedRetryFactory.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/blocking/retry/BlockingLoadBalancedRetryFactory.java index 7407f6633..0bc10c62d 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/blocking/retry/BlockingLoadBalancedRetryFactory.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/blocking/retry/BlockingLoadBalancedRetryFactory.java @@ -28,21 +28,28 @@ * {@link BlockingLoadBalancerClient}. * * @author Olga Maciaszek-Sharma + * @author Andrii Bohutskyi * @since 2.2.6 */ public class BlockingLoadBalancedRetryFactory implements LoadBalancedRetryFactory { - private LoadBalancerProperties loadBalancerProperties; + private final LoadBalancerProperties loadBalancerProperties; - private LoadBalancerPropertiesFactory factoryProperties; + private LoadBalancerPropertiesFactory propertiesFactory; + /** + * @deprecated Deprecated in favor of + * {@link #BlockingLoadBalancedRetryFactory(LoadBalancerProperties, LoadBalancerPropertiesFactory)} + */ @Deprecated public BlockingLoadBalancedRetryFactory(LoadBalancerProperties loadBalancerProperties) { this.loadBalancerProperties = loadBalancerProperties; } - public BlockingLoadBalancedRetryFactory(LoadBalancerPropertiesFactory factoryProperties) { - this.factoryProperties = factoryProperties; + public BlockingLoadBalancedRetryFactory(LoadBalancerProperties loadBalancerProperties, + LoadBalancerPropertiesFactory propertiesFactory) { + this.loadBalancerProperties = loadBalancerProperties; + this.propertiesFactory = propertiesFactory; } @Override @@ -52,8 +59,8 @@ public LoadBalancedRetryPolicy createRetryPolicy(String serviceId, ServiceInstan @Deprecated private LoadBalancerProperties getLoadBalancerProperties(String serviceId) { - if (factoryProperties != null) { - return factoryProperties.getLoadBalancerProperties(serviceId); + if (propertiesFactory != null) { + return propertiesFactory.getLoadBalancerProperties(serviceId); } else { return loadBalancerProperties; } diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/config/BlockingLoadBalancerClientAutoConfiguration.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/config/BlockingLoadBalancerClientAutoConfiguration.java index b3aca1038..055832c32 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/config/BlockingLoadBalancerClientAutoConfiguration.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/config/BlockingLoadBalancerClientAutoConfiguration.java @@ -28,6 +28,7 @@ import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; import org.springframework.cloud.client.loadbalancer.LoadBalancerPropertiesFactory; +import org.springframework.cloud.client.loadbalancer.LoadBalancerServiceProperties; import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients; import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient; import org.springframework.cloud.loadbalancer.blocking.retry.BlockingLoadBalancedRetryFactory; @@ -42,6 +43,7 @@ * An autoconfiguration for {@link BlockingLoadBalancerClient}. * * @author Olga Maciaszek-Sharma + * @author Andrii Bohutskyi * @since 2.1.3 */ @Configuration(proxyBeanMethods = false) @@ -56,8 +58,8 @@ public class BlockingLoadBalancerClientAutoConfiguration { @ConditionalOnBean(LoadBalancerClientFactory.class) @ConditionalOnMissingBean public LoadBalancerClient blockingLoadBalancerClient(LoadBalancerClientFactory loadBalancerClientFactory, - LoadBalancerProperties properties) { - return new BlockingLoadBalancerClient(loadBalancerClientFactory, properties); + LoadBalancerProperties properties, LoadBalancerPropertiesFactory propertiesFactory) { + return new BlockingLoadBalancerClient(loadBalancerClientFactory, properties, propertiesFactory); } @Bean @@ -65,19 +67,20 @@ public LoadBalancerClient blockingLoadBalancerClient(LoadBalancerClientFactory l havingValue = "true") @ConditionalOnMissingBean(LoadBalancerServiceInstanceCookieTransformer.class) public LoadBalancerServiceInstanceCookieTransformer loadBalancerServiceInstanceCookieTransformer( - LoadBalancerProperties properties) { - return new LoadBalancerServiceInstanceCookieTransformer(properties.getStickySession()); + LoadBalancerProperties properties, LoadBalancerPropertiesFactory propertiesFactory) { + return new LoadBalancerServiceInstanceCookieTransformer(properties.getStickySession(), propertiesFactory); } @Configuration @ConditionalOnClass(RetryTemplate.class) - @EnableConfigurationProperties(LoadBalancerProperties.class) + @EnableConfigurationProperties({ LoadBalancerProperties.class, LoadBalancerServiceProperties.class }) protected static class BlockingLoadBalancerRetryConfig { @Bean @ConditionalOnMissingBean - LoadBalancedRetryFactory loadBalancedRetryFactory(LoadBalancerPropertiesFactory propertiesFactory) { - return new BlockingLoadBalancedRetryFactory(propertiesFactory); + LoadBalancedRetryFactory loadBalancedRetryFactory(LoadBalancerProperties properties, + LoadBalancerPropertiesFactory propertiesFactory) { + return new BlockingLoadBalancedRetryFactory(properties, propertiesFactory); } } diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/LoadBalancerServiceInstanceCookieTransformer.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/LoadBalancerServiceInstanceCookieTransformer.java index 08bcbd43a..fa3fdb78d 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/LoadBalancerServiceInstanceCookieTransformer.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/LoadBalancerServiceInstanceCookieTransformer.java @@ -22,6 +22,7 @@ import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; +import org.springframework.cloud.client.loadbalancer.LoadBalancerPropertiesFactory; import org.springframework.cloud.client.loadbalancer.LoadBalancerRequestTransformer; import org.springframework.http.HttpCookie; import org.springframework.http.HttpHeaders; @@ -33,22 +34,36 @@ * the {@link ServiceInstance} selected by the {@link LoadBalancerClient} in a cookie. * * @author Olga Maciaszek-Sharma + * @author Andrii Bohutskyi * @since 3.0.2 */ public class LoadBalancerServiceInstanceCookieTransformer implements LoadBalancerRequestTransformer { private final LoadBalancerProperties.StickySession stickySessionProperties; + private LoadBalancerPropertiesFactory propertiesFactory; + + /** + * @deprecated Deprecated in favor of + * {@link #LoadBalancerServiceInstanceCookieTransformer(LoadBalancerProperties.StickySession, LoadBalancerPropertiesFactory)} + */ + @Deprecated public LoadBalancerServiceInstanceCookieTransformer(LoadBalancerProperties.StickySession stickySessionProperties) { this.stickySessionProperties = stickySessionProperties; } + public LoadBalancerServiceInstanceCookieTransformer(LoadBalancerProperties.StickySession stickySessionProperties, + LoadBalancerPropertiesFactory propertiesFactory) { + this.stickySessionProperties = stickySessionProperties; + this.propertiesFactory = propertiesFactory; + } + @Override public HttpRequest transformRequest(HttpRequest request, ServiceInstance instance) { if (instance == null) { return request; } - String instanceIdCookieName = stickySessionProperties.getInstanceIdCookieName(); + String instanceIdCookieName = getLoadBalancerProperties(instance.getServiceId()).getInstanceIdCookieName(); if (!StringUtils.hasText(instanceIdCookieName)) { return request; } @@ -60,4 +75,14 @@ public HttpRequest transformRequest(HttpRequest request, ServiceInstance instanc return request; } + @Deprecated + private LoadBalancerProperties.StickySession getLoadBalancerProperties(String serviceId) { + if (propertiesFactory != null) { + return propertiesFactory.getLoadBalancerProperties(serviceId).getStickySession(); + } + else { + return stickySessionProperties; + } + } + } diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/ServiceInstanceListSupplierBuilder.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/ServiceInstanceListSupplierBuilder.java index 02843f6c1..e427d6d7c 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/ServiceInstanceListSupplierBuilder.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/ServiceInstanceListSupplierBuilder.java @@ -30,6 +30,7 @@ import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient; import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; +import org.springframework.cloud.client.loadbalancer.LoadBalancerPropertiesFactory; import org.springframework.cloud.loadbalancer.cache.LoadBalancerCacheManager; import org.springframework.cloud.loadbalancer.config.LoadBalancerZoneConfig; import org.springframework.context.ConfigurableApplicationContext; @@ -39,12 +40,15 @@ import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.util.UriComponentsBuilder; +import static org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory.PROPERTY_NAME; + /** * A Builder for creating a {@link ServiceInstanceListSupplier} hierarchy to be used in * {@link ReactorLoadBalancer} configuration. * * @author Spencer Gibb * @author Olga Maciaszek-Sharma + * @author Andrii Bohutskyi */ public final class ServiceInstanceListSupplierBuilder { @@ -113,7 +117,9 @@ public ServiceInstanceListSupplierBuilder withBase(ServiceInstanceListSupplier s */ public ServiceInstanceListSupplierBuilder withHealthChecks() { DelegateCreator creator = (context, delegate) -> { - LoadBalancerProperties properties = context.getBean(LoadBalancerProperties.class); + String serviceId = context.getEnvironment().getProperty(PROPERTY_NAME); + final LoadBalancerProperties properties = context.getBean(LoadBalancerPropertiesFactory.class) + .getLoadBalancerProperties(serviceId); WebClient.Builder webClient = context.getBean(WebClient.Builder.class); return healthCheckServiceInstanceListSupplier(webClient.build(), delegate, properties); }; @@ -129,7 +135,9 @@ public ServiceInstanceListSupplierBuilder withHealthChecks() { */ public ServiceInstanceListSupplierBuilder withHealthChecks(WebClient webClient) { DelegateCreator creator = (context, delegate) -> { - LoadBalancerProperties properties = context.getBean(LoadBalancerProperties.class); + String serviceId = context.getEnvironment().getProperty(PROPERTY_NAME); + final LoadBalancerProperties properties = context.getBean(LoadBalancerPropertiesFactory.class) + .getLoadBalancerProperties(serviceId); return healthCheckServiceInstanceListSupplier(webClient, delegate, properties); }; this.creators.add(creator); @@ -156,7 +164,9 @@ public ServiceInstanceListSupplierBuilder withSameInstancePreference() { public ServiceInstanceListSupplierBuilder withBlockingHealthChecks() { DelegateCreator creator = (context, delegate) -> { RestTemplate restTemplate = context.getBean(RestTemplate.class); - LoadBalancerProperties properties = context.getBean(LoadBalancerProperties.class); + String serviceId = context.getEnvironment().getProperty(PROPERTY_NAME); + final LoadBalancerProperties properties = context.getBean(LoadBalancerPropertiesFactory.class) + .getLoadBalancerProperties(serviceId); return blockingHealthCheckServiceInstanceListSupplier(restTemplate, delegate, properties); }; this.creators.add(creator); @@ -171,7 +181,9 @@ public ServiceInstanceListSupplierBuilder withBlockingHealthChecks() { */ public ServiceInstanceListSupplierBuilder withBlockingHealthChecks(RestTemplate restTemplate) { DelegateCreator creator = (context, delegate) -> { - LoadBalancerProperties properties = context.getBean(LoadBalancerProperties.class); + String serviceId = context.getEnvironment().getProperty(PROPERTY_NAME); + final LoadBalancerProperties properties = context.getBean(LoadBalancerPropertiesFactory.class) + .getLoadBalancerProperties(serviceId); return blockingHealthCheckServiceInstanceListSupplier(restTemplate, delegate, properties); }; this.creators.add(creator); @@ -199,7 +211,9 @@ public ServiceInstanceListSupplierBuilder withZonePreference() { */ public ServiceInstanceListSupplierBuilder withRequestBasedStickySession() { DelegateCreator creator = (context, delegate) -> { - LoadBalancerProperties properties = context.getBean(LoadBalancerProperties.class); + String serviceId = context.getEnvironment().getProperty(PROPERTY_NAME); + final LoadBalancerProperties properties = context.getBean(LoadBalancerPropertiesFactory.class) + .getLoadBalancerProperties(serviceId); return new RequestBasedStickySessionServiceInstanceListSupplier(delegate, properties); }; this.creators.add(creator); @@ -241,7 +255,9 @@ public ServiceInstanceListSupplierBuilder withRetryAwareness() { public ServiceInstanceListSupplierBuilder withHints() { DelegateCreator creator = (context, delegate) -> { - LoadBalancerProperties properties = context.getBean(LoadBalancerProperties.class); + String serviceId = context.getEnvironment().getProperty(PROPERTY_NAME); + final LoadBalancerProperties properties = context.getBean(LoadBalancerPropertiesFactory.class) + .getLoadBalancerProperties(serviceId); return new HintBasedServiceInstanceListSupplier(delegate, properties); }; creators.add(creator); diff --git a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/CachingServiceInstanceListSupplierTests.java b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/CachingServiceInstanceListSupplierTests.java index 916432a05..135829349 100644 --- a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/CachingServiceInstanceListSupplierTests.java +++ b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/CachingServiceInstanceListSupplierTests.java @@ -28,6 +28,8 @@ import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient; import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; +import org.springframework.cloud.client.loadbalancer.LoadBalancerPropertiesFactory; +import org.springframework.cloud.client.loadbalancer.LoadBalancerServiceProperties; import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient; import org.springframework.cloud.loadbalancer.cache.LoadBalancerCacheManager; import org.springframework.cloud.loadbalancer.config.LoadBalancerCacheAutoConfiguration; @@ -109,8 +111,8 @@ LoadBalancerClientFactory loadBalancerClientFactory() { @Bean BlockingLoadBalancerClient blockingLoadBalancerClient(LoadBalancerClientFactory loadBalancerClientFactory, - LoadBalancerProperties properties) { - return new BlockingLoadBalancerClient(loadBalancerClientFactory, properties); + LoadBalancerProperties properties, LoadBalancerPropertiesFactory propertiesFactory) { + return new BlockingLoadBalancerClient(loadBalancerClientFactory, properties, propertiesFactory); } @Bean @@ -118,6 +120,17 @@ public LoadBalancerProperties loadBalancerProperties() { return new LoadBalancerProperties(); } + @Bean + public LoadBalancerServiceProperties loadBalancerServiceProperties() { + return new LoadBalancerServiceProperties(); + } + + @Bean + public LoadBalancerPropertiesFactory loadBalancerPropertiesFactory(LoadBalancerProperties properties, + LoadBalancerServiceProperties serviceProperties) { + return new LoadBalancerPropertiesFactory(properties, serviceProperties, false); + } + @Bean public WebClient.Builder webClientBuilder() { return WebClient.builder(); diff --git a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/LoadBalancerServiceInstanceCookieTransformerPerServiceTests.java b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/LoadBalancerServiceInstanceCookieTransformerPerServiceTests.java new file mode 100644 index 000000000..03b13511b --- /dev/null +++ b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/LoadBalancerServiceInstanceCookieTransformerPerServiceTests.java @@ -0,0 +1,98 @@ +/* + * 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.cloud.loadbalancer.core; + +import org.junit.jupiter.api.Test; + +import org.springframework.cloud.client.DefaultServiceInstance; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; +import org.springframework.cloud.client.loadbalancer.LoadBalancerPropertiesFactory; +import org.springframework.cloud.client.loadbalancer.LoadBalancerServiceProperties; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpRequest; +import org.springframework.mock.http.client.MockClientHttpRequest; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link LoadBalancerServiceInstanceCookieTransformer}. + * + * @author Olga Maciaszek-Sharma + * @author Andrii Bohutskyi + */ +class LoadBalancerServiceInstanceCookieTransformerPerServiceTests { + + LoadBalancerServiceProperties serviceProperties = new LoadBalancerServiceProperties(); + + LoadBalancerPropertiesFactory propertiesFactory = new LoadBalancerPropertiesFactory(new LoadBalancerProperties(), + serviceProperties, true); + + LoadBalancerServiceInstanceCookieTransformer transformer = new LoadBalancerServiceInstanceCookieTransformer( + new LoadBalancerProperties().getStickySession(), propertiesFactory); + + ServiceInstance serviceInstance = new DefaultServiceInstance("test-01", "test", "host", 8080, false); + + HttpRequest request = new MockClientHttpRequest(); + + @Test + void shouldAddServiceInstanceCookieHeader() { + HttpRequest newRequest = transformer.transformRequest(request, serviceInstance); + + assertThat(newRequest.getHeaders().get(HttpHeaders.COOKIE)).hasSize(1); + assertThat(newRequest.getHeaders().get(HttpHeaders.COOKIE)).containsExactly("sc-lb-instance-id=test-01"); + } + + @Test + void shouldAppendServiceInstanceCookieHeaderIfCookiesPresent() { + request.getHeaders().add(HttpHeaders.COOKIE, "testCookieName=testCookieValue"); + + HttpRequest newRequest = transformer.transformRequest(request, serviceInstance); + + assertThat(newRequest.getHeaders().get(HttpHeaders.COOKIE)).containsExactly("testCookieName=testCookieValue", + "sc-lb-instance-id=test-01"); + } + + @Test + void shouldReturnPassedRequestWhenNoServiceInstance() { + HttpRequest newRequest = transformer.transformRequest(request, null); + + assertThat(newRequest.getHeaders()).doesNotContainKey(HttpHeaders.COOKIE); + } + + @Test + void shouldReturnPassedRequestWhenNullServiceInstanceCookieName() { + serviceProperties.getServices().put("test", new LoadBalancerProperties()); + serviceProperties.getServices().get("test").getStickySession().setInstanceIdCookieName(null); + + HttpRequest newRequest = transformer.transformRequest(request, serviceInstance); + + assertThat(newRequest.getHeaders()).doesNotContainKey(HttpHeaders.COOKIE); + } + + @Test + void shouldReturnPassedRequestWhenEmptyServiceInstanceCookieName() { + serviceProperties.getServices().put("test", new LoadBalancerProperties()); + serviceProperties.getServices().get("test").getStickySession().setInstanceIdCookieName(""); + + HttpRequest newRequest = transformer.transformRequest(request, serviceInstance); + + assertThat(newRequest.getHeaders()).doesNotContainKey(HttpHeaders.COOKIE); + + } + +} diff --git a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/LoadBalancerServiceInstanceCookieTransformerTests.java b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/LoadBalancerServiceInstanceCookieTransformerTests.java index d4a647be4..e03388e54 100644 --- a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/LoadBalancerServiceInstanceCookieTransformerTests.java +++ b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/LoadBalancerServiceInstanceCookieTransformerTests.java @@ -21,6 +21,8 @@ import org.springframework.cloud.client.DefaultServiceInstance; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; +import org.springframework.cloud.client.loadbalancer.LoadBalancerPropertiesFactory; +import org.springframework.cloud.client.loadbalancer.LoadBalancerServiceProperties; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpRequest; import org.springframework.mock.http.client.MockClientHttpRequest; @@ -34,10 +36,15 @@ */ class LoadBalancerServiceInstanceCookieTransformerTests { - LoadBalancerProperties.StickySession stickySessionProperties = new LoadBalancerProperties().getStickySession(); + LoadBalancerProperties loadBalancerProperties = new LoadBalancerProperties(); + + LoadBalancerProperties.StickySession stickySessionProperties = loadBalancerProperties.getStickySession(); + + LoadBalancerPropertiesFactory propertiesFactory = new LoadBalancerPropertiesFactory(loadBalancerProperties, + new LoadBalancerServiceProperties(), false); LoadBalancerServiceInstanceCookieTransformer transformer = new LoadBalancerServiceInstanceCookieTransformer( - stickySessionProperties); + stickySessionProperties, propertiesFactory); ServiceInstance serviceInstance = new DefaultServiceInstance("test-01", "test", "host", 8080, false); diff --git a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/ServiceInstanceListSupplierBuilderTests.java b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/ServiceInstanceListSupplierBuilderTests.java index 48d47c002..d4a490dfc 100644 --- a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/ServiceInstanceListSupplierBuilderTests.java +++ b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/ServiceInstanceListSupplierBuilderTests.java @@ -21,6 +21,8 @@ import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient; import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; +import org.springframework.cloud.client.loadbalancer.LoadBalancerPropertiesFactory; +import org.springframework.cloud.client.loadbalancer.LoadBalancerServiceProperties; import org.springframework.cloud.loadbalancer.cache.LoadBalancerCacheManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; @@ -93,6 +95,17 @@ public LoadBalancerProperties loadBalancerProperties() { return new LoadBalancerProperties(); } + @Bean + public LoadBalancerServiceProperties loadBalancerServiceProperties() { + return new LoadBalancerServiceProperties(); + } + + @Bean + public LoadBalancerPropertiesFactory loadBalancerPropertiesFactory(LoadBalancerProperties properties, + LoadBalancerServiceProperties serviceProperties) { + return new LoadBalancerPropertiesFactory(properties, serviceProperties, false); + } + @Bean public WebClient.Builder webClientBuilder() { return WebClient.builder(); From f0781abedcc72932d78ad256d3fa7f056364fb33 Mon Sep 17 00:00:00 2001 From: Andrii Bohutskyi Date: Tue, 27 Apr 2021 11:16:41 +0300 Subject: [PATCH 3/4] Support disable retry for reactive lb --- .../RetryableLoadBalancerExchangeFilterFunction.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunction.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunction.java index 498401eab..f869eb789 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunction.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunction.java @@ -125,6 +125,11 @@ public Mono filter(ClientRequest clientRequest, ExchangeFunction return Mono.just(ClientResponse.create(HttpStatus.BAD_REQUEST).body(message).build()); } + if (!getLoadBalancerProperties(serviceId).getRetry().isEnabled()) { + return new ReactorLoadBalancerExchangeFilterFunction(loadBalancerFactory, properties, transformers, + propertiesFactory).filter(clientRequest, next); + } + LoadBalancerRetryContext loadBalancerRetryContext = new LoadBalancerRetryContext(clientRequest); Retry exchangeRetry = buildRetrySpec(serviceId, getLoadBalancerProperties(serviceId).getRetry().getMaxRetriesOnSameServiceInstance(), true); @@ -252,7 +257,8 @@ protected Mono> choose(String serviceId, Request Date: Fri, 30 Apr 2021 17:00:45 +0300 Subject: [PATCH 4/4] Support disable retry for reactive lb --- docs/src/main/asciidoc/_configprops.adoc | 2 + .../main/asciidoc/spring-cloud-commons.adoc | 35 +++++++++--- .../LoadBalancerAutoConfiguration.java | 8 +-- ...java => LoadBalancerClientProperties.java} | 18 +++--- .../LoadBalancerPropertiesFactory.java | 20 ++++--- ...oadBalancerInterceptorPerServiceTests.java | 30 +++++----- .../RetryLoadBalancerInterceptorTests.java | 56 +++++++++---------- ...dBalancerClientRequestTransformerTest.java | 15 ++--- ...dBalancerClientAutoConfigurationTests.java | 12 ++-- ...adBalancerExchangeFilterFunctionTests.java | 12 ++-- ...xchangeFilterFunctionIntegrationTests.java | 15 ++--- ...terFunctionPerServiceIntegrationTests.java | 40 +++++++------ ...ExchangeFilterFunctionPerServiceTests.java | 16 +++--- ...adBalancerExchangeFilterFunctionTests.java | 18 +++--- ...ngLoadBalancerClientAutoConfiguration.java | 4 +- .../config/LoadBalancerAutoConfiguration.java | 12 ++-- ...itional-spring-configuration-metadata.json | 6 ++ ...chingServiceInstanceListSupplierTests.java | 12 ++-- ...tanceCookieTransformerPerServiceTests.java | 12 ++-- ...ServiceInstanceCookieTransformerTests.java | 10 ++-- ...rviceInstanceListSupplierBuilderTests.java | 12 ++-- 21 files changed, 198 insertions(+), 167 deletions(-) rename spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/{LoadBalancerServiceProperties.java => LoadBalancerClientProperties.java} (65%) diff --git a/docs/src/main/asciidoc/_configprops.adoc b/docs/src/main/asciidoc/_configprops.adoc index 68d83b6d4..fdb3d1d4d 100644 --- a/docs/src/main/asciidoc/_configprops.adoc +++ b/docs/src/main/asciidoc/_configprops.adoc @@ -27,6 +27,8 @@ |spring.cloud.loadbalancer.cache.capacity | `256` | Initial cache capacity expressed as int. |spring.cloud.loadbalancer.cache.enabled | `true` | Enables Spring Cloud LoadBalancer caching mechanism. |spring.cloud.loadbalancer.cache.ttl | `35s` | 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 StringToDurationConverter. @see StringToDurationConverter.java +|spring.cloud.loadbalancer.client | | +|spring.cloud.loadbalancer.client.configuration.enabled | `false` | Enables per client LoadBalancer configuration. |spring.cloud.loadbalancer.configurations | `default` | Enables a predefined LoadBalancer configuration. |spring.cloud.loadbalancer.health-check.initial-delay | `0` | Initial delay value for the HealthCheck scheduler. |spring.cloud.loadbalancer.health-check.interval | `25s` | Interval for rerunning the HealthCheck scheduler. diff --git a/docs/src/main/asciidoc/spring-cloud-commons.adoc b/docs/src/main/asciidoc/spring-cloud-commons.adoc index e83d74196..0fea4bf80 100644 --- a/docs/src/main/asciidoc/spring-cloud-commons.adoc +++ b/docs/src/main/asciidoc/spring-cloud-commons.adoc @@ -1229,7 +1229,28 @@ NOTE: The meters are registered in the registry when at least one record is adde TIP: You can further configure the behavior of those metrics (for example, add https://micrometer.io/docs/concepts#_histograms_and_percentiles[publishing percentiles and histograms]) by https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html#production-ready-metrics-per-meter-properties[adding `MeterFilters`]. === Spring Cloud LoadBalancer per client configuration -You can configure multiple loadBalancer clients by using `spring.cloud.loadbalancer.client.services[]` property: +By default, to preserve backward compatibility, Spring Cloud LoadBalancer uses general `spring.cloud.loadbalancer` configuration. +To enable per client configuration, set the `spring.cloud.loadbalancer.client.configuration.enabled` property to `true`. + +When per client configuration is enabled, you can configure multiple loadBalancer clients by using +`spring.cloud.loadbalancer.client.services[]` properties: +==== +[source,yaml] +---- +spring: + cloud: + loadBalancer: + client: + foo1: + retry: + retryOnAllOperations: true + foo2: + healthCheck: + refetchInstances: true +---- +==== + +You can also configure default properties for LoadBalancer: ==== [source,yaml] ---- @@ -1237,13 +1258,11 @@ spring: cloud: loadBalancer: client: - services: - foo1: - retry: - retryOnAllOperations: true - foo2: - healthCheck: - refetchInstances: true + default: + retry: + retryOnAllOperations: true + healthCheck: + refetchInstances: true ---- ==== diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerAutoConfiguration.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerAutoConfiguration.java index 158224b49..528679c00 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerAutoConfiguration.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerAutoConfiguration.java @@ -53,15 +53,15 @@ @Configuration(proxyBeanMethods = false) @ConditionalOnClass(RestTemplate.class) @ConditionalOnBean(LoadBalancerClient.class) -@EnableConfigurationProperties({ LoadBalancerProperties.class, LoadBalancerServiceProperties.class }) +@EnableConfigurationProperties({ LoadBalancerProperties.class, LoadBalancerClientProperties.class }) public class LoadBalancerAutoConfiguration { @Bean @ConditionalOnMissingBean - public LoadBalancerPropertiesFactory loadBalancerPropertiesFactory(LoadBalancerProperties properties, - LoadBalancerServiceProperties servicesProperties, + public LoadBalancerPropertiesFactory loadBalancerPropertiesFactory(LoadBalancerProperties globalProperties, + LoadBalancerClientProperties servicesProperties, @Value("${spring.cloud.loadbalancer.service.configuration.enabled:false}") boolean isServiceProperties) { - return new LoadBalancerPropertiesFactory(properties, servicesProperties, isServiceProperties); + return new LoadBalancerPropertiesFactory(globalProperties, servicesProperties, isServiceProperties); } @LoadBalanced diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerServiceProperties.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerClientProperties.java similarity index 65% rename from spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerServiceProperties.java rename to spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerClientProperties.java index d31eccdd0..6cbc9aec1 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerServiceProperties.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerClientProperties.java @@ -26,21 +26,21 @@ * * @author Andrii Bohutskyi */ -@ConfigurationProperties("spring.cloud.loadbalancer.client") -public class LoadBalancerServiceProperties { +@ConfigurationProperties("spring.cloud.loadbalancer") +public class LoadBalancerClientProperties { - private Map services = new HashMap<>(); + private Map client = new HashMap<>(); - public Map getServices() { - return services; + public Map getClient() { + return client; } - public void setServices(Map services) { - this.services = services; + public void setClient(Map client) { + this.client = client; } - public LoadBalancerProperties getDefaultServiceProperties() { - return services.getOrDefault("default", new LoadBalancerProperties()); + public LoadBalancerProperties getDefaultClientProperties() { + return client.getOrDefault("default", new LoadBalancerProperties()); } } diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerPropertiesFactory.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerPropertiesFactory.java index 83d24393f..bf5fbf061 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerPropertiesFactory.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerPropertiesFactory.java @@ -23,26 +23,30 @@ */ public class LoadBalancerPropertiesFactory { - private final LoadBalancerProperties properties; + private final LoadBalancerProperties globalProperties; - private final LoadBalancerServiceProperties servicesProperties; + private final LoadBalancerClientProperties servicesProperties; private final boolean isServiceProperties; - public LoadBalancerPropertiesFactory(LoadBalancerProperties properties, - LoadBalancerServiceProperties servicesProperties, boolean isServiceProperties) { - this.properties = properties; + public LoadBalancerPropertiesFactory(LoadBalancerProperties globalProperties, + LoadBalancerClientProperties servicesProperties, boolean isServiceProperties) { + this.globalProperties = globalProperties; this.servicesProperties = servicesProperties; this.isServiceProperties = isServiceProperties; } public LoadBalancerProperties getLoadBalancerProperties(String serviceName) { - return isServiceProperties ? getLoadBalancerServiceProperties(serviceName) : properties; + return isServiceProperties ? getLoadBalancerServiceProperties(serviceName) : globalProperties; + } + + public LoadBalancerProperties getGlobalLoadBalancerProperties() { + return globalProperties; } private LoadBalancerProperties getLoadBalancerServiceProperties(String serviceName) { - return servicesProperties.getServices().getOrDefault(serviceName, - servicesProperties.getDefaultServiceProperties()); + return servicesProperties.getClient().getOrDefault(serviceName, + servicesProperties.getDefaultClientProperties()); } } diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/RetryLoadBalancerInterceptorPerServiceTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/RetryLoadBalancerInterceptorPerServiceTests.java index 14bfce796..f1df4eb82 100644 --- a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/RetryLoadBalancerInterceptorPerServiceTests.java +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/RetryLoadBalancerInterceptorPerServiceTests.java @@ -79,7 +79,7 @@ public class RetryLoadBalancerInterceptorPerServiceTests { private final LoadBalancedRetryFactory loadBalancedRetryFactory = new LoadBalancedRetryFactory() { }; - private LoadBalancerServiceProperties serviceProperties; + private LoadBalancerClientProperties serviceProperties; private LoadBalancerPropertiesFactory propertiesFactory; @@ -89,8 +89,8 @@ public class RetryLoadBalancerInterceptorPerServiceTests { public void setUp() { client = mock(LoadBalancerClient.class); lbRequestFactory = mock(LoadBalancerRequestFactory.class); - serviceProperties = new LoadBalancerServiceProperties(); - serviceProperties.getServices().put("foo", new LoadBalancerProperties()); + serviceProperties = new LoadBalancerClientProperties(); + serviceProperties.getClient().put("foo", new LoadBalancerProperties()); propertiesFactory = new LoadBalancerPropertiesFactory(new LoadBalancerProperties(), serviceProperties, true); lbFactory = mock(ReactiveLoadBalancer.Factory.class); } @@ -108,7 +108,7 @@ public void interceptDisableRetry() throws Throwable { when(client.choose(eq("foo"), any())).thenReturn(serviceInstance); when(client.execute(eq("foo"), eq(serviceInstance), any(LoadBalancerRequest.class))) .thenThrow(new IOException()); - serviceProperties.getServices().get("foo").getRetry().setEnabled(false); + serviceProperties.getClient().get("foo").getRetry().setEnabled(false); RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, new LoadBalancerProperties(), lbRequestFactory, loadBalancedRetryFactory, lbFactory, propertiesFactory); byte[] body = new byte[] {}; @@ -124,7 +124,7 @@ public void interceptDisableRetry() throws Throwable { public void interceptInvalidHost() throws Throwable { HttpRequest request = mock(HttpRequest.class); when(request.getURI()).thenReturn(new URI("http://foo_underscore")); - serviceProperties.getServices().get("foo").getRetry().setEnabled(true); + serviceProperties.getClient().get("foo").getRetry().setEnabled(true); RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, new LoadBalancerProperties(), lbRequestFactory, loadBalancedRetryFactory, lbFactory, propertiesFactory); byte[] body = new byte[] {}; @@ -142,7 +142,7 @@ public void interceptNeverRetry() throws Throwable { when(client.execute(eq("foo"), eq(serviceInstance), any(LoadBalancerRequest.class))) .thenReturn(clientHttpResponse); when(lbRequestFactory.createRequest(any(), any(), any())).thenReturn(mock(LoadBalancerRequest.class)); - serviceProperties.getServices().get("foo").getRetry().setEnabled(true); + serviceProperties.getClient().get("foo").getRetry().setEnabled(true); RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, new LoadBalancerProperties(), lbRequestFactory, loadBalancedRetryFactory, lbFactory, propertiesFactory); byte[] body = new byte[] {}; @@ -162,7 +162,7 @@ public void interceptSuccess() throws Throwable { when(client.execute(eq("foo"), eq(serviceInstance), any(LoadBalancerRequest.class))) .thenReturn(clientHttpResponse); when(lbRequestFactory.createRequest(any(), any(), any())).thenReturn(mock(LoadBalancerRequest.class)); - serviceProperties.getServices().get("foo").getRetry().setEnabled(true); + serviceProperties.getClient().get("foo").getRetry().setEnabled(true); RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, new LoadBalancerProperties(), lbRequestFactory, new MyLoadBalancedRetryFactory(policy), lbFactory, propertiesFactory); @@ -189,7 +189,7 @@ public void interceptRetryOnStatusCode() throws Throwable { when(client.choose(eq("foo"), any())).thenReturn(serviceInstance); when(client.execute(eq("foo"), eq(serviceInstance), nullable(LoadBalancerRequest.class))) .thenReturn(clientHttpResponseNotFound).thenReturn(clientHttpResponseOk); - serviceProperties.getServices().get("foo").getRetry().setEnabled(true); + serviceProperties.getClient().get("foo").getRetry().setEnabled(true); RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, new LoadBalancerProperties(), lbRequestFactory, new MyLoadBalancedRetryFactory(policy), lbFactory, propertiesFactory); @@ -221,7 +221,7 @@ public void interceptRetryFailOnStatusCode() throws Throwable { ArgumentMatchers.>any())) .thenReturn(clientHttpResponseNotFound); - serviceProperties.getServices().get("foo").getRetry().setEnabled(true); + serviceProperties.getClient().get("foo").getRetry().setEnabled(true); byte[] body = new byte[] {}; ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class); RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, @@ -254,7 +254,7 @@ public void interceptRetry() throws Throwable { when(client.execute(eq("foo"), eq(serviceInstance), any(LoadBalancerRequest.class))) .thenThrow(new IOException()).thenReturn(clientHttpResponse); when(lbRequestFactory.createRequest(any(), any(), any())).thenReturn(mock(LoadBalancerRequest.class)); - serviceProperties.getServices().get("foo").getRetry().setEnabled(true); + serviceProperties.getClient().get("foo").getRetry().setEnabled(true); RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, new LoadBalancerProperties(), lbRequestFactory, new MyLoadBalancedRetryFactory(policy, backOffPolicy), lbFactory, propertiesFactory); @@ -279,7 +279,7 @@ public void interceptFailedRetry() throws Exception { when(client.execute(eq("foo"), eq(serviceInstance), any(LoadBalancerRequest.class))) .thenThrow(new IOException()).thenReturn(clientHttpResponse); when(lbRequestFactory.createRequest(any(), any(), any())).thenReturn(mock(LoadBalancerRequest.class)); - serviceProperties.getServices().get("foo").getRetry().setEnabled(true); + serviceProperties.getClient().get("foo").getRetry().setEnabled(true); RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, new LoadBalancerProperties(), lbRequestFactory, new MyLoadBalancedRetryFactory(policy), lbFactory, propertiesFactory); @@ -305,7 +305,7 @@ public void retryListenerTest() throws Throwable { when(client.choose(eq("listener"), any())).thenReturn(serviceInstance); when(client.execute(eq("listener"), eq(serviceInstance), any(LoadBalancerRequest.class))) .thenThrow(new IOException()).thenReturn(clientHttpResponse); - serviceProperties.getServices().get("foo").getRetry().setEnabled(true); + serviceProperties.getClient().get("foo").getRetry().setEnabled(true); MyRetryListener retryListener = new MyRetryListener(); when(lbRequestFactory.createRequest(any(), any(), any())).thenReturn(mock(LoadBalancerRequest.class)); RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, @@ -334,7 +334,7 @@ public void retryWithDefaultConstructorTest() throws Throwable { when(client.choose(eq("default"), any())).thenReturn(serviceInstance); when(client.execute(eq("default"), eq(serviceInstance), any(LoadBalancerRequest.class))) .thenThrow(new IOException()).thenReturn(clientHttpResponse); - serviceProperties.getServices().get("foo").getRetry().setEnabled(true); + serviceProperties.getClient().get("foo").getRetry().setEnabled(true); when(lbRequestFactory.createRequest(any(), any(), any())).thenReturn(mock(LoadBalancerRequest.class)); RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, new LoadBalancerProperties(), lbRequestFactory, new MyLoadBalancedRetryFactory(policy, backOffPolicy), @@ -354,8 +354,8 @@ public void retryListenerTestNoRetry() throws Throwable { when(request.getURI()).thenReturn(new URI("http://noRetry")); LoadBalancedRetryPolicy policy = mock(LoadBalancedRetryPolicy.class); MyBackOffPolicy backOffPolicy = new MyBackOffPolicy(); - serviceProperties.getServices().put("noRetry", new LoadBalancerProperties()); - serviceProperties.getServices().get("noRetry").getRetry().setEnabled(true); + serviceProperties.getClient().put("noRetry", new LoadBalancerProperties()); + serviceProperties.getClient().get("noRetry").getRetry().setEnabled(true); RetryListener myRetryListener = new RetryListenerSupport() { @Override public boolean open(RetryContext context, RetryCallback callback) { diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/RetryLoadBalancerInterceptorTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/RetryLoadBalancerInterceptorTests.java index 3956e77fe..4d028541c 100644 --- a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/RetryLoadBalancerInterceptorTests.java +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/RetryLoadBalancerInterceptorTests.java @@ -78,9 +78,9 @@ public class RetryLoadBalancerInterceptorTests { private final LoadBalancedRetryFactory loadBalancedRetryFactory = new LoadBalancedRetryFactory() { }; - private LoadBalancerProperties properties; + private LoadBalancerProperties globalProperties; - private LoadBalancerServiceProperties serviceProperties; + private LoadBalancerClientProperties serviceProperties; private LoadBalancerPropertiesFactory propertiesFactory; @@ -90,9 +90,9 @@ public class RetryLoadBalancerInterceptorTests { public void setUp() { client = mock(LoadBalancerClient.class); lbRequestFactory = mock(LoadBalancerRequestFactory.class); - properties = new LoadBalancerProperties(); - serviceProperties = new LoadBalancerServiceProperties(); - propertiesFactory = new LoadBalancerPropertiesFactory(properties, serviceProperties, false); + globalProperties = new LoadBalancerProperties(); + serviceProperties = new LoadBalancerClientProperties(); + propertiesFactory = new LoadBalancerPropertiesFactory(globalProperties, serviceProperties, false); lbFactory = mock(ReactiveLoadBalancer.Factory.class); } @@ -109,8 +109,8 @@ public void interceptDisableRetry() throws Throwable { when(client.choose(eq("foo"), any())).thenReturn(serviceInstance); when(client.execute(eq("foo"), eq(serviceInstance), any(LoadBalancerRequest.class))) .thenThrow(new IOException()); - properties.getRetry().setEnabled(false); - RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, properties, + globalProperties.getRetry().setEnabled(false); + RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, globalProperties, lbRequestFactory, loadBalancedRetryFactory, lbFactory); byte[] body = new byte[] {}; ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class); @@ -125,8 +125,8 @@ public void interceptDisableRetry() throws Throwable { public void interceptInvalidHost() throws Throwable { HttpRequest request = mock(HttpRequest.class); when(request.getURI()).thenReturn(new URI("http://foo_underscore")); - properties.getRetry().setEnabled(true); - RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, properties, + globalProperties.getRetry().setEnabled(true); + RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, globalProperties, lbRequestFactory, loadBalancedRetryFactory, lbFactory, propertiesFactory); byte[] body = new byte[] {}; ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class); @@ -143,8 +143,8 @@ public void interceptNeverRetry() throws Throwable { when(client.execute(eq("foo"), eq(serviceInstance), any(LoadBalancerRequest.class))) .thenReturn(clientHttpResponse); when(lbRequestFactory.createRequest(any(), any(), any())).thenReturn(mock(LoadBalancerRequest.class)); - properties.getRetry().setEnabled(true); - RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, properties, + globalProperties.getRetry().setEnabled(true); + RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, globalProperties, lbRequestFactory, loadBalancedRetryFactory, lbFactory, propertiesFactory); byte[] body = new byte[] {}; ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class); @@ -163,8 +163,8 @@ public void interceptSuccess() throws Throwable { when(client.execute(eq("foo"), eq(serviceInstance), any(LoadBalancerRequest.class))) .thenReturn(clientHttpResponse); when(lbRequestFactory.createRequest(any(), any(), any())).thenReturn(mock(LoadBalancerRequest.class)); - properties.getRetry().setEnabled(true); - RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, properties, + globalProperties.getRetry().setEnabled(true); + RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, globalProperties, lbRequestFactory, new MyLoadBalancedRetryFactory(policy), lbFactory, propertiesFactory); byte[] body = new byte[] {}; ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class); @@ -189,8 +189,8 @@ public void interceptRetryOnStatusCode() throws Throwable { when(client.choose(eq("foo"), any())).thenReturn(serviceInstance); when(client.execute(eq("foo"), eq(serviceInstance), nullable(LoadBalancerRequest.class))) .thenReturn(clientHttpResponseNotFound).thenReturn(clientHttpResponseOk); - properties.getRetry().setEnabled(true); - RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, properties, + globalProperties.getRetry().setEnabled(true); + RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, globalProperties, lbRequestFactory, new MyLoadBalancedRetryFactory(policy), lbFactory, propertiesFactory); byte[] body = new byte[] {}; ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class); @@ -220,10 +220,10 @@ public void interceptRetryFailOnStatusCode() throws Throwable { ArgumentMatchers.>any())) .thenReturn(clientHttpResponseNotFound); - properties.getRetry().setEnabled(true); + globalProperties.getRetry().setEnabled(true); byte[] body = new byte[] {}; ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class); - RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, properties, + RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, globalProperties, lbRequestFactory, new MyLoadBalancedRetryFactory(policy), lbFactory, propertiesFactory); ClientHttpResponse rsp = interceptor.intercept(request, body, execution); @@ -252,8 +252,8 @@ public void interceptRetry() throws Throwable { when(client.execute(eq("foo"), eq(serviceInstance), any(LoadBalancerRequest.class))) .thenThrow(new IOException()).thenReturn(clientHttpResponse); when(lbRequestFactory.createRequest(any(), any(), any())).thenReturn(mock(LoadBalancerRequest.class)); - properties.getRetry().setEnabled(true); - RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, properties, + globalProperties.getRetry().setEnabled(true); + RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, globalProperties, lbRequestFactory, new MyLoadBalancedRetryFactory(policy, backOffPolicy), lbFactory, propertiesFactory); byte[] body = new byte[] {}; ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class); @@ -276,8 +276,8 @@ public void interceptFailedRetry() throws Exception { when(client.execute(eq("foo"), eq(serviceInstance), any(LoadBalancerRequest.class))) .thenThrow(new IOException()).thenReturn(clientHttpResponse); when(lbRequestFactory.createRequest(any(), any(), any())).thenReturn(mock(LoadBalancerRequest.class)); - properties.getRetry().setEnabled(true); - RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, properties, + globalProperties.getRetry().setEnabled(true); + RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, globalProperties, lbRequestFactory, new MyLoadBalancedRetryFactory(policy), lbFactory, propertiesFactory); byte[] body = new byte[] {}; ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class); @@ -301,10 +301,10 @@ public void retryListenerTest() throws Throwable { when(client.choose(eq("listener"), any())).thenReturn(serviceInstance); when(client.execute(eq("listener"), eq(serviceInstance), any(LoadBalancerRequest.class))) .thenThrow(new IOException()).thenReturn(clientHttpResponse); - properties.getRetry().setEnabled(true); + globalProperties.getRetry().setEnabled(true); MyRetryListener retryListener = new MyRetryListener(); when(lbRequestFactory.createRequest(any(), any(), any())).thenReturn(mock(LoadBalancerRequest.class)); - RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, properties, + RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, globalProperties, lbRequestFactory, new MyLoadBalancedRetryFactory(policy, backOffPolicy, new RetryListener[] { retryListener }), lbFactory, propertiesFactory); @@ -330,9 +330,9 @@ public void retryWithDefaultConstructorTest() throws Throwable { when(client.choose(eq("default"), any())).thenReturn(serviceInstance); when(client.execute(eq("default"), eq(serviceInstance), any(LoadBalancerRequest.class))) .thenThrow(new IOException()).thenReturn(clientHttpResponse); - properties.getRetry().setEnabled(true); + globalProperties.getRetry().setEnabled(true); when(lbRequestFactory.createRequest(any(), any(), any())).thenReturn(mock(LoadBalancerRequest.class)); - RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, properties, + RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, globalProperties, lbRequestFactory, new MyLoadBalancedRetryFactory(policy, backOffPolicy), lbFactory, propertiesFactory); byte[] body = new byte[] {}; ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class); @@ -349,14 +349,14 @@ public void retryListenerTestNoRetry() throws Throwable { when(request.getURI()).thenReturn(new URI("http://noRetry")); LoadBalancedRetryPolicy policy = mock(LoadBalancedRetryPolicy.class); MyBackOffPolicy backOffPolicy = new MyBackOffPolicy(); - properties.getRetry().setEnabled(true); + globalProperties.getRetry().setEnabled(true); RetryListener myRetryListener = new RetryListenerSupport() { @Override public boolean open(RetryContext context, RetryCallback callback) { return false; } }; - RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, properties, + RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, globalProperties, lbRequestFactory, new MyLoadBalancedRetryFactory(policy, backOffPolicy, new RetryListener[] { myRetryListener }), lbFactory); @@ -373,7 +373,7 @@ public void shouldNotDuplicateLifecycleCalls() throws IOException, URISyntaxExce HttpRequest request = mock(HttpRequest.class); when(request.getURI()).thenReturn(new URI("http://test")); TestLoadBalancerClient client = new TestLoadBalancerClient(); - RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, properties, + RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(client, globalProperties, lbRequestFactory, loadBalancedRetryFactory, lbFactory, propertiesFactory); interceptor.intercept(request, new byte[] {}, mock(ClientHttpRequestExecution.class)); diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/LoadBalancerClientRequestTransformerTest.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/LoadBalancerClientRequestTransformerTest.java index a525c5cfd..ab057e194 100644 --- a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/LoadBalancerClientRequestTransformerTest.java +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/LoadBalancerClientRequestTransformerTest.java @@ -25,9 +25,9 @@ import reactor.core.publisher.Mono; import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.LoadBalancerClientProperties; import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; import org.springframework.cloud.client.loadbalancer.LoadBalancerPropertiesFactory; -import org.springframework.cloud.client.loadbalancer.LoadBalancerServiceProperties; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; @@ -49,15 +49,15 @@ */ class LoadBalancerClientRequestTransformerTest { - private final LoadBalancerProperties properties = new LoadBalancerProperties(); + private final LoadBalancerProperties globalProperties = new LoadBalancerProperties(); - private final LoadBalancerServiceProperties serviceProperties = new LoadBalancerServiceProperties(); + private final LoadBalancerClientProperties serviceProperties = new LoadBalancerClientProperties(); - private final LoadBalancerPropertiesFactory propertiesFactory = new LoadBalancerPropertiesFactory(properties, + private final LoadBalancerPropertiesFactory propertiesFactory = new LoadBalancerPropertiesFactory(globalProperties, serviceProperties, false); private final LoadBalancerRetryPolicy policy = new RetryableExchangeFilterFunctionLoadBalancerRetryPolicy( - properties, propertiesFactory); + globalProperties, propertiesFactory); private final ReactiveLoadBalancer.Factory factory = mock(ReactiveLoadBalancer.Factory.class); @@ -82,7 +82,7 @@ void setUp() { void transformReactorLoadBalancerExchangeFilterFunction() { ArgumentCaptor captor = ArgumentCaptor.forClass(ClientRequest.class); ReactorLoadBalancerExchangeFilterFunction filterFunction = new ReactorLoadBalancerExchangeFilterFunction( - factory, properties, Arrays.asList(new Transformer1(), new Transformer2()), propertiesFactory); + factory, globalProperties, Arrays.asList(new Transformer1(), new Transformer2()), propertiesFactory); filterFunction.filter(clientRequest, next).subscribe(); verify(next).exchange(captor.capture()); HttpHeaders headers = captor.getValue().headers(); @@ -94,7 +94,8 @@ void transformReactorLoadBalancerExchangeFilterFunction() { void transformRetryableLoadBalancerExchangeFilterFunction() { ArgumentCaptor captor = ArgumentCaptor.forClass(ClientRequest.class); RetryableLoadBalancerExchangeFilterFunction filterFunction = new RetryableLoadBalancerExchangeFilterFunction( - policy, factory, properties, Arrays.asList(new Transformer1(), new Transformer2()), propertiesFactory); + policy, factory, globalProperties, Arrays.asList(new Transformer1(), new Transformer2()), + propertiesFactory); filterFunction.filter(clientRequest, next).subscribe(); verify(next).exchange(captor.capture()); HttpHeaders headers = captor.getValue().headers(); diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerClientAutoConfigurationTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerClientAutoConfigurationTests.java index 9040a7a75..2965abfb1 100644 --- a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerClientAutoConfigurationTests.java +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerClientAutoConfigurationTests.java @@ -24,9 +24,9 @@ import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory; +import org.springframework.cloud.client.loadbalancer.LoadBalancerClientProperties; import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; import org.springframework.cloud.client.loadbalancer.LoadBalancerPropertiesFactory; -import org.springframework.cloud.client.loadbalancer.LoadBalancerServiceProperties; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -170,14 +170,14 @@ LoadBalancerProperties loadBalancerProperties() { } @Bean - public LoadBalancerServiceProperties loadBalancerServiceProperties() { - return new LoadBalancerServiceProperties(); + public LoadBalancerClientProperties loadBalancerServiceProperties() { + return new LoadBalancerClientProperties(); } @Bean - public LoadBalancerPropertiesFactory loadBalancerPropertiesFactory(LoadBalancerProperties properties, - LoadBalancerServiceProperties serviceProperties) { - return new LoadBalancerPropertiesFactory(properties, serviceProperties, false); + public LoadBalancerPropertiesFactory loadBalancerPropertiesFactory(LoadBalancerProperties globalProperties, + LoadBalancerClientProperties serviceProperties) { + return new LoadBalancerPropertiesFactory(globalProperties, serviceProperties, false); } } diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerExchangeFilterFunctionTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerExchangeFilterFunctionTests.java index 462181219..31b3a973a 100644 --- a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerExchangeFilterFunctionTests.java +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerExchangeFilterFunctionTests.java @@ -40,10 +40,10 @@ import org.springframework.cloud.client.discovery.simple.SimpleDiscoveryProperties; import org.springframework.cloud.client.loadbalancer.CompletionContext; import org.springframework.cloud.client.loadbalancer.DefaultRequestContext; +import org.springframework.cloud.client.loadbalancer.LoadBalancerClientProperties; import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycle; import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; import org.springframework.cloud.client.loadbalancer.LoadBalancerPropertiesFactory; -import org.springframework.cloud.client.loadbalancer.LoadBalancerServiceProperties; import org.springframework.cloud.client.loadbalancer.Request; import org.springframework.cloud.client.loadbalancer.Response; import org.springframework.cloud.client.loadbalancer.ResponseData; @@ -209,14 +209,14 @@ LoadBalancerProperties loadBalancerProperties() { } @Bean - public LoadBalancerServiceProperties loadBalancerServiceProperties() { - return new LoadBalancerServiceProperties(); + public LoadBalancerClientProperties loadBalancerServiceProperties() { + return new LoadBalancerClientProperties(); } @Bean - public LoadBalancerPropertiesFactory loadBalancerPropertiesFactory(LoadBalancerProperties properties, - LoadBalancerServiceProperties serviceProperties) { - return new LoadBalancerPropertiesFactory(properties, serviceProperties, false); + public LoadBalancerPropertiesFactory loadBalancerPropertiesFactory(LoadBalancerProperties globalProperties, + LoadBalancerClientProperties serviceProperties) { + return new LoadBalancerPropertiesFactory(globalProperties, serviceProperties, false); } } diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionIntegrationTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionIntegrationTests.java index 276d03145..cabe514da 100644 --- a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionIntegrationTests.java +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionIntegrationTests.java @@ -42,10 +42,10 @@ import org.springframework.cloud.client.discovery.simple.SimpleDiscoveryProperties; import org.springframework.cloud.client.loadbalancer.CompletionContext; import org.springframework.cloud.client.loadbalancer.DefaultRequestContext; +import org.springframework.cloud.client.loadbalancer.LoadBalancerClientProperties; import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycle; import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; import org.springframework.cloud.client.loadbalancer.LoadBalancerPropertiesFactory; -import org.springframework.cloud.client.loadbalancer.LoadBalancerServiceProperties; import org.springframework.cloud.client.loadbalancer.Request; import org.springframework.cloud.client.loadbalancer.Response; import org.springframework.cloud.client.loadbalancer.ResponseData; @@ -267,19 +267,20 @@ LoadBalancerProperties loadBalancerProperties() { } @Bean - public LoadBalancerServiceProperties loadBalancerServiceProperties() { - return new LoadBalancerServiceProperties(); + public LoadBalancerClientProperties loadBalancerServiceProperties() { + return new LoadBalancerClientProperties(); } @Bean - public LoadBalancerPropertiesFactory loadBalancerPropertiesFactory(LoadBalancerProperties properties, - LoadBalancerServiceProperties serviceProperties) { - return new LoadBalancerPropertiesFactory(properties, serviceProperties, false); + public LoadBalancerPropertiesFactory loadBalancerPropertiesFactory(LoadBalancerProperties globalProperties, + LoadBalancerClientProperties serviceProperties) { + return new LoadBalancerPropertiesFactory(globalProperties, serviceProperties, false); } @Bean RetryableLoadBalancerExchangeFilterFunction exchangeFilterFunction(LoadBalancerProperties properties, - ReactiveLoadBalancer.Factory factory, LoadBalancerPropertiesFactory propertiesFactory) { + ReactiveLoadBalancer.Factory factory, + LoadBalancerPropertiesFactory propertiesFactory) { return new RetryableLoadBalancerExchangeFilterFunction( new RetryableExchangeFilterFunctionLoadBalancerRetryPolicy(properties, propertiesFactory), factory, properties); diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionPerServiceIntegrationTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionPerServiceIntegrationTests.java index 7f4b62d1b..11bba6ad5 100644 --- a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionPerServiceIntegrationTests.java +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionPerServiceIntegrationTests.java @@ -42,10 +42,10 @@ import org.springframework.cloud.client.discovery.simple.SimpleDiscoveryProperties; import org.springframework.cloud.client.loadbalancer.CompletionContext; import org.springframework.cloud.client.loadbalancer.DefaultRequestContext; +import org.springframework.cloud.client.loadbalancer.LoadBalancerClientProperties; import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycle; import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; import org.springframework.cloud.client.loadbalancer.LoadBalancerPropertiesFactory; -import org.springframework.cloud.client.loadbalancer.LoadBalancerServiceProperties; import org.springframework.cloud.client.loadbalancer.Request; import org.springframework.cloud.client.loadbalancer.Response; import org.springframework.cloud.client.loadbalancer.ResponseData; @@ -79,7 +79,7 @@ class RetryableLoadBalancerExchangeFilterFunctionPerServiceIntegrationTests { private SimpleDiscoveryProperties properties; @Autowired - private LoadBalancerServiceProperties serviceProperties; + private LoadBalancerClientProperties serviceProperties; @Autowired private ReactiveLoadBalancer.Factory factory; @@ -98,13 +98,13 @@ void setUp() { properties.getInstances().put("testservice", Collections.singletonList(instance)); properties.getInstances().put("serviceWithNoLifecycleProcessors", Collections.singletonList(instanceWithNoLifecycleProcessors)); - serviceProperties.getServices().put("testservice", new LoadBalancerProperties()); + serviceProperties.getClient().put("testservice", new LoadBalancerProperties()); } @Test void loadBalancerLifecycleCallbacksExecuted() { final String callbackTestHint = "callbackTestHint"; - serviceProperties.getServices().get("testservice").getHint().put("testservice", "callbackTestHint"); + serviceProperties.getClient().get("testservice").getHint().put("testservice", "callbackTestHint"); ClientResponse clientResponse = WebClient.builder().baseUrl("http://testservice") .filter(this.loadBalancerFunction).build().get().uri("/callback").exchange().block(); @@ -141,8 +141,8 @@ void correctResponseReturnedForExistingHostAndInstancePresent() { @Test void correctResponseReturnedAfterRetryingOnSameServiceInstance() { - serviceProperties.getServices().get("testservice").getRetry().setMaxRetriesOnSameServiceInstance(1); - serviceProperties.getServices().get("testservice").getRetry().getRetryableStatusCodes().add(500); + serviceProperties.getClient().get("testservice").getRetry().setMaxRetriesOnSameServiceInstance(1); + serviceProperties.getClient().get("testservice").getRetry().getRetryableStatusCodes().add(500); ClientResponse clientResponse = WebClient.builder().baseUrl("http://testservice") .filter(this.loadBalancerFunction).build().get().uri("/exception").exchange().block(); @@ -153,9 +153,9 @@ void correctResponseReturnedAfterRetryingOnSameServiceInstance() { @Test void correctResponseReturnedAfterRetryingOnNextServiceInstanceWithBackoff() { - serviceProperties.getServices().put("retrytest", new LoadBalancerProperties()); - serviceProperties.getServices().get("retrytest").getRetry().getBackoff().setEnabled(true); - serviceProperties.getServices().get("retrytest").getRetry().setMaxRetriesOnSameServiceInstance(1); + serviceProperties.getClient().put("retrytest", new LoadBalancerProperties()); + serviceProperties.getClient().get("retrytest").getRetry().getBackoff().setEnabled(true); + serviceProperties.getClient().get("retrytest").getRetry().setMaxRetriesOnSameServiceInstance(1); DefaultServiceInstance goodRetryTestInstance = new DefaultServiceInstance(); goodRetryTestInstance.setServiceId("retrytest"); @@ -164,7 +164,7 @@ void correctResponseReturnedAfterRetryingOnNextServiceInstanceWithBackoff() { badRetryTestInstance.setServiceId("retrytest"); badRetryTestInstance.setUri(URI.create("http://localhost:" + 8080)); properties.getInstances().put("retrytest", Arrays.asList(badRetryTestInstance, goodRetryTestInstance)); - serviceProperties.getServices().get("retrytest").getRetry().getRetryableStatusCodes().add(500); + serviceProperties.getClient().get("retrytest").getRetry().getRetryableStatusCodes().add(500); ClientResponse clientResponse = WebClient.builder().baseUrl("http://retrytest") .filter(this.loadBalancerFunction).build().get().uri("/hello").exchange().block(); @@ -242,8 +242,7 @@ ReactiveLoadBalancer.Factory reactiveLoadBalancerFactory(Discov @Override public ReactiveLoadBalancer getInstance(String serviceId) { - return new DiscoveryClientBasedReactiveLoadBalancer( - serviceId, discoveryClient); + return new DiscoveryClientBasedReactiveLoadBalancer(serviceId, discoveryClient); } @Override @@ -270,24 +269,23 @@ LoadBalancerProperties loadBalancerProperties() { } @Bean - public LoadBalancerServiceProperties loadBalancerServiceProperties() { - return new LoadBalancerServiceProperties(); + public LoadBalancerClientProperties loadBalancerServiceProperties() { + return new LoadBalancerClientProperties(); } @Bean - public LoadBalancerPropertiesFactory loadBalancerPropertiesFactory(LoadBalancerProperties properties, - LoadBalancerServiceProperties serviceProperties) { - return new LoadBalancerPropertiesFactory(properties, serviceProperties, true); + public LoadBalancerPropertiesFactory loadBalancerPropertiesFactory(LoadBalancerProperties globalProperties, + LoadBalancerClientProperties serviceProperties) { + return new LoadBalancerPropertiesFactory(globalProperties, serviceProperties, true); } @Bean - RetryableLoadBalancerExchangeFilterFunction exchangeFilterFunction( - LoadBalancerProperties properties, + RetryableLoadBalancerExchangeFilterFunction exchangeFilterFunction(LoadBalancerProperties properties, ReactiveLoadBalancer.Factory factory, LoadBalancerPropertiesFactory propertiesFactory) { return new RetryableLoadBalancerExchangeFilterFunction( - new RetryableExchangeFilterFunctionLoadBalancerRetryPolicy(properties, propertiesFactory), - factory, properties, Collections.emptyList(), propertiesFactory); + new RetryableExchangeFilterFunctionLoadBalancerRetryPolicy(properties, propertiesFactory), factory, + properties, Collections.emptyList(), propertiesFactory); } } diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionPerServiceTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionPerServiceTests.java index 5b9e1a57b..2a969a883 100644 --- a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionPerServiceTests.java +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionPerServiceTests.java @@ -26,9 +26,9 @@ import reactor.core.publisher.Mono; import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.LoadBalancerClientProperties; import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; import org.springframework.cloud.client.loadbalancer.LoadBalancerPropertiesFactory; -import org.springframework.cloud.client.loadbalancer.LoadBalancerServiceProperties; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; @@ -53,10 +53,10 @@ @SuppressWarnings("unchecked") class RetryableLoadBalancerExchangeFilterFunctionPerServiceTests { - private final LoadBalancerServiceProperties serviceProperties = new LoadBalancerServiceProperties(); + private final LoadBalancerClientProperties serviceProperties = new LoadBalancerClientProperties(); - private final LoadBalancerPropertiesFactory propertiesFactory = new LoadBalancerPropertiesFactory(new LoadBalancerProperties(), - serviceProperties, true); + private final LoadBalancerPropertiesFactory propertiesFactory = new LoadBalancerPropertiesFactory( + new LoadBalancerProperties(), serviceProperties, true); private final LoadBalancerRetryPolicy policy = new RetryableExchangeFilterFunctionLoadBalancerRetryPolicy( new LoadBalancerProperties(), propertiesFactory); @@ -76,9 +76,9 @@ class RetryableLoadBalancerExchangeFilterFunctionPerServiceTests { @BeforeEach void setUp() { - serviceProperties.getServices().put("test", new LoadBalancerProperties()); - serviceProperties.getServices().get("test").getRetry().setMaxRetriesOnSameServiceInstance(1); - serviceProperties.getServices().get("test").getRetry().getRetryableStatusCodes().add(404); + serviceProperties.getClient().put("test", new LoadBalancerProperties()); + serviceProperties.getClient().get("test").getRetry().setMaxRetriesOnSameServiceInstance(1); + serviceProperties.getClient().get("test").getRetry().getRetryableStatusCodes().add(404); when(clientRequest.url()).thenReturn(URI.create("http://test")); when(factory.getInstance("test")).thenReturn(new TestReactiveLoadBalancer()); @@ -145,7 +145,7 @@ void shouldRetryOnMethodOtherThanGetWhenEnabled() { properties.getRetry().setMaxRetriesOnSameServiceInstance(1); properties.getRetry().getRetryableStatusCodes().add(404); final LoadBalancerPropertiesFactory propertiesFactory = new LoadBalancerPropertiesFactory(properties, - new LoadBalancerServiceProperties(), false); + new LoadBalancerClientProperties(), false); LoadBalancerRetryPolicy policy = new RetryableExchangeFilterFunctionLoadBalancerRetryPolicy(properties, propertiesFactory); RetryableLoadBalancerExchangeFilterFunction filterFunction = new RetryableLoadBalancerExchangeFilterFunction( diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionTests.java index 7f0706030..1117c61d6 100644 --- a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionTests.java +++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionTests.java @@ -26,9 +26,9 @@ import reactor.core.publisher.Mono; import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.LoadBalancerClientProperties; import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; import org.springframework.cloud.client.loadbalancer.LoadBalancerPropertiesFactory; -import org.springframework.cloud.client.loadbalancer.LoadBalancerServiceProperties; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; @@ -52,20 +52,20 @@ @SuppressWarnings("unchecked") class RetryableLoadBalancerExchangeFilterFunctionTests { - private final LoadBalancerProperties properties = new LoadBalancerProperties(); + private final LoadBalancerProperties globalProperties = new LoadBalancerProperties(); - private final LoadBalancerServiceProperties serviceProperties = new LoadBalancerServiceProperties(); + private final LoadBalancerClientProperties serviceProperties = new LoadBalancerClientProperties(); - private final LoadBalancerPropertiesFactory propertiesFactory = new LoadBalancerPropertiesFactory(properties, + private final LoadBalancerPropertiesFactory propertiesFactory = new LoadBalancerPropertiesFactory(globalProperties, serviceProperties, false); private final LoadBalancerRetryPolicy policy = new RetryableExchangeFilterFunctionLoadBalancerRetryPolicy( - properties, propertiesFactory); + globalProperties, propertiesFactory); private final ReactiveLoadBalancer.Factory factory = mock(ReactiveLoadBalancer.Factory.class); private final RetryableLoadBalancerExchangeFilterFunction filterFunction = new RetryableLoadBalancerExchangeFilterFunction( - policy, factory, properties, Collections.emptyList(), propertiesFactory); + policy, factory, globalProperties, Collections.emptyList(), propertiesFactory); private final ClientRequest clientRequest = mock(ClientRequest.class); @@ -77,8 +77,8 @@ class RetryableLoadBalancerExchangeFilterFunctionTests { @BeforeEach void setUp() { - properties.getRetry().setMaxRetriesOnSameServiceInstance(1); - properties.getRetry().getRetryableStatusCodes().add(404); + globalProperties.getRetry().setMaxRetriesOnSameServiceInstance(1); + globalProperties.getRetry().getRetryableStatusCodes().add(404); when(clientRequest.url()).thenReturn(URI.create("http://test")); when(factory.getInstance("test")).thenReturn(new TestReactiveLoadBalancer()); when(clientRequest.headers()).thenReturn(new HttpHeaders()); @@ -144,7 +144,7 @@ void shouldRetryOnMethodOtherThanGetWhenEnabled() { properties.getRetry().setMaxRetriesOnSameServiceInstance(1); properties.getRetry().getRetryableStatusCodes().add(404); final LoadBalancerPropertiesFactory propertiesFactory = new LoadBalancerPropertiesFactory(properties, - new LoadBalancerServiceProperties(), false); + new LoadBalancerClientProperties(), false); LoadBalancerRetryPolicy policy = new RetryableExchangeFilterFunctionLoadBalancerRetryPolicy(properties, propertiesFactory); RetryableLoadBalancerExchangeFilterFunction filterFunction = new RetryableLoadBalancerExchangeFilterFunction( diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/config/BlockingLoadBalancerClientAutoConfiguration.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/config/BlockingLoadBalancerClientAutoConfiguration.java index 055832c32..a68c4c1e7 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/config/BlockingLoadBalancerClientAutoConfiguration.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/config/BlockingLoadBalancerClientAutoConfiguration.java @@ -26,9 +26,9 @@ 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.LoadBalancerClientProperties; import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; import org.springframework.cloud.client.loadbalancer.LoadBalancerPropertiesFactory; -import org.springframework.cloud.client.loadbalancer.LoadBalancerServiceProperties; import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients; import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient; import org.springframework.cloud.loadbalancer.blocking.retry.BlockingLoadBalancedRetryFactory; @@ -73,7 +73,7 @@ public LoadBalancerServiceInstanceCookieTransformer loadBalancerServiceInstanceC @Configuration @ConditionalOnClass(RetryTemplate.class) - @EnableConfigurationProperties({ LoadBalancerProperties.class, LoadBalancerServiceProperties.class }) + @EnableConfigurationProperties({ LoadBalancerProperties.class, LoadBalancerClientProperties.class }) protected static class BlockingLoadBalancerRetryConfig { @Bean diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/config/LoadBalancerAutoConfiguration.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/config/LoadBalancerAutoConfiguration.java index 5a6dc7a7a..b46b03409 100644 --- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/config/LoadBalancerAutoConfiguration.java +++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/config/LoadBalancerAutoConfiguration.java @@ -24,9 +24,9 @@ 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.LoadBalancerClientProperties; import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; import org.springframework.cloud.client.loadbalancer.LoadBalancerPropertiesFactory; -import org.springframework.cloud.client.loadbalancer.LoadBalancerServiceProperties; import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerBeanPostProcessorAutoConfiguration; import org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerClientAutoConfiguration; import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClientSpecification; @@ -42,7 +42,7 @@ */ @Configuration(proxyBeanMethods = false) @LoadBalancerClients -@EnableConfigurationProperties({ LoadBalancerProperties.class, LoadBalancerServiceProperties.class }) +@EnableConfigurationProperties({ LoadBalancerProperties.class, LoadBalancerClientProperties.class }) @AutoConfigureBefore({ ReactorLoadBalancerClientAutoConfiguration.class, LoadBalancerBeanPostProcessorAutoConfiguration.class }) public class LoadBalancerAutoConfiguration { @@ -68,10 +68,10 @@ public LoadBalancerClientFactory loadBalancerClientFactory() { } @Bean - public LoadBalancerPropertiesFactory loadBalancerPropertiesFactory(LoadBalancerProperties properties, - LoadBalancerServiceProperties servicesProperties, - @Value("${spring.cloud.loadbalancer.service.configuration.enabled:false}") boolean isServiceProperties) { - return new LoadBalancerPropertiesFactory(properties, servicesProperties, isServiceProperties); + public LoadBalancerPropertiesFactory loadBalancerPropertiesFactory(LoadBalancerProperties globalProperties, + LoadBalancerClientProperties servicesProperties, + @Value("${spring.cloud.loadbalancer.client.configuration.enabled:false}") boolean isServiceProperties) { + return new LoadBalancerPropertiesFactory(globalProperties, servicesProperties, isServiceProperties); } } diff --git a/spring-cloud-loadbalancer/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-cloud-loadbalancer/src/main/resources/META-INF/additional-spring-configuration-metadata.json index efe302494..44e03db73 100644 --- a/spring-cloud-loadbalancer/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-cloud-loadbalancer/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -33,6 +33,12 @@ "name": "spring.cloud.loadbalancer.configurations", "description": "Enables a predefined LoadBalancer configuration.", "type": "java.lang.String" + }, + { + "defaultValue": false, + "name": "spring.cloud.loadbalancer.client.configuration.enabled", + "description": "Enables per client LoadBalancer configuration.", + "type": "java.lang.Boolean" } ] } diff --git a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/CachingServiceInstanceListSupplierTests.java b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/CachingServiceInstanceListSupplierTests.java index 135829349..803d0823e 100644 --- a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/CachingServiceInstanceListSupplierTests.java +++ b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/CachingServiceInstanceListSupplierTests.java @@ -27,9 +27,9 @@ import org.springframework.cloud.client.DefaultServiceInstance; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient; +import org.springframework.cloud.client.loadbalancer.LoadBalancerClientProperties; import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; import org.springframework.cloud.client.loadbalancer.LoadBalancerPropertiesFactory; -import org.springframework.cloud.client.loadbalancer.LoadBalancerServiceProperties; import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient; import org.springframework.cloud.loadbalancer.cache.LoadBalancerCacheManager; import org.springframework.cloud.loadbalancer.config.LoadBalancerCacheAutoConfiguration; @@ -121,14 +121,14 @@ public LoadBalancerProperties loadBalancerProperties() { } @Bean - public LoadBalancerServiceProperties loadBalancerServiceProperties() { - return new LoadBalancerServiceProperties(); + public LoadBalancerClientProperties loadBalancerServiceProperties() { + return new LoadBalancerClientProperties(); } @Bean - public LoadBalancerPropertiesFactory loadBalancerPropertiesFactory(LoadBalancerProperties properties, - LoadBalancerServiceProperties serviceProperties) { - return new LoadBalancerPropertiesFactory(properties, serviceProperties, false); + public LoadBalancerPropertiesFactory loadBalancerPropertiesFactory(LoadBalancerProperties globalProperties, + LoadBalancerClientProperties serviceProperties) { + return new LoadBalancerPropertiesFactory(globalProperties, serviceProperties, false); } @Bean diff --git a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/LoadBalancerServiceInstanceCookieTransformerPerServiceTests.java b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/LoadBalancerServiceInstanceCookieTransformerPerServiceTests.java index 03b13511b..0e7d4a448 100644 --- a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/LoadBalancerServiceInstanceCookieTransformerPerServiceTests.java +++ b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/LoadBalancerServiceInstanceCookieTransformerPerServiceTests.java @@ -20,9 +20,9 @@ import org.springframework.cloud.client.DefaultServiceInstance; import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.LoadBalancerClientProperties; import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; import org.springframework.cloud.client.loadbalancer.LoadBalancerPropertiesFactory; -import org.springframework.cloud.client.loadbalancer.LoadBalancerServiceProperties; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpRequest; import org.springframework.mock.http.client.MockClientHttpRequest; @@ -37,7 +37,7 @@ */ class LoadBalancerServiceInstanceCookieTransformerPerServiceTests { - LoadBalancerServiceProperties serviceProperties = new LoadBalancerServiceProperties(); + LoadBalancerClientProperties serviceProperties = new LoadBalancerClientProperties(); LoadBalancerPropertiesFactory propertiesFactory = new LoadBalancerPropertiesFactory(new LoadBalancerProperties(), serviceProperties, true); @@ -76,8 +76,8 @@ void shouldReturnPassedRequestWhenNoServiceInstance() { @Test void shouldReturnPassedRequestWhenNullServiceInstanceCookieName() { - serviceProperties.getServices().put("test", new LoadBalancerProperties()); - serviceProperties.getServices().get("test").getStickySession().setInstanceIdCookieName(null); + serviceProperties.getClient().put("test", new LoadBalancerProperties()); + serviceProperties.getClient().get("test").getStickySession().setInstanceIdCookieName(null); HttpRequest newRequest = transformer.transformRequest(request, serviceInstance); @@ -86,8 +86,8 @@ void shouldReturnPassedRequestWhenNullServiceInstanceCookieName() { @Test void shouldReturnPassedRequestWhenEmptyServiceInstanceCookieName() { - serviceProperties.getServices().put("test", new LoadBalancerProperties()); - serviceProperties.getServices().get("test").getStickySession().setInstanceIdCookieName(""); + serviceProperties.getClient().put("test", new LoadBalancerProperties()); + serviceProperties.getClient().get("test").getStickySession().setInstanceIdCookieName(""); HttpRequest newRequest = transformer.transformRequest(request, serviceInstance); diff --git a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/LoadBalancerServiceInstanceCookieTransformerTests.java b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/LoadBalancerServiceInstanceCookieTransformerTests.java index e03388e54..d3293979e 100644 --- a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/LoadBalancerServiceInstanceCookieTransformerTests.java +++ b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/LoadBalancerServiceInstanceCookieTransformerTests.java @@ -20,9 +20,9 @@ import org.springframework.cloud.client.DefaultServiceInstance; import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.LoadBalancerClientProperties; import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; import org.springframework.cloud.client.loadbalancer.LoadBalancerPropertiesFactory; -import org.springframework.cloud.client.loadbalancer.LoadBalancerServiceProperties; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpRequest; import org.springframework.mock.http.client.MockClientHttpRequest; @@ -36,12 +36,12 @@ */ class LoadBalancerServiceInstanceCookieTransformerTests { - LoadBalancerProperties loadBalancerProperties = new LoadBalancerProperties(); + LoadBalancerProperties globalProperties = new LoadBalancerProperties(); - LoadBalancerProperties.StickySession stickySessionProperties = loadBalancerProperties.getStickySession(); + LoadBalancerProperties.StickySession stickySessionProperties = globalProperties.getStickySession(); - LoadBalancerPropertiesFactory propertiesFactory = new LoadBalancerPropertiesFactory(loadBalancerProperties, - new LoadBalancerServiceProperties(), false); + LoadBalancerPropertiesFactory propertiesFactory = new LoadBalancerPropertiesFactory(globalProperties, + new LoadBalancerClientProperties(), false); LoadBalancerServiceInstanceCookieTransformer transformer = new LoadBalancerServiceInstanceCookieTransformer( stickySessionProperties, propertiesFactory); diff --git a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/ServiceInstanceListSupplierBuilderTests.java b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/ServiceInstanceListSupplierBuilderTests.java index d4a490dfc..7bc81f901 100644 --- a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/ServiceInstanceListSupplierBuilderTests.java +++ b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/ServiceInstanceListSupplierBuilderTests.java @@ -20,9 +20,9 @@ import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient; +import org.springframework.cloud.client.loadbalancer.LoadBalancerClientProperties; import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; import org.springframework.cloud.client.loadbalancer.LoadBalancerPropertiesFactory; -import org.springframework.cloud.client.loadbalancer.LoadBalancerServiceProperties; import org.springframework.cloud.loadbalancer.cache.LoadBalancerCacheManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; @@ -96,14 +96,14 @@ public LoadBalancerProperties loadBalancerProperties() { } @Bean - public LoadBalancerServiceProperties loadBalancerServiceProperties() { - return new LoadBalancerServiceProperties(); + public LoadBalancerClientProperties loadBalancerServiceProperties() { + return new LoadBalancerClientProperties(); } @Bean - public LoadBalancerPropertiesFactory loadBalancerPropertiesFactory(LoadBalancerProperties properties, - LoadBalancerServiceProperties serviceProperties) { - return new LoadBalancerPropertiesFactory(properties, serviceProperties, false); + public LoadBalancerPropertiesFactory loadBalancerPropertiesFactory(LoadBalancerProperties globalProperties, + LoadBalancerClientProperties serviceProperties) { + return new LoadBalancerPropertiesFactory(globalProperties, serviceProperties, false); } @Bean