Lb complete lifecycle (#783)
* Add LoadBalancerLifecycle. Trigger lifecycle callbacks. Set hints from properties. * Trigger LB lifecycle callbacks from BlockingLoadBalancerClient and RetryLoadBalancerInterceptor. * Register LifecycleProcessors with @LoadBalancerClients * Register LifecycleProcessors with @LoadBalancerClients configuration. * Handle null lifecycle beans map returned from factory. Adjust tests to code changes. * Handle null lifecycle beans map returned from factory in RetryLoadBalancerInterceptor. Ensure ReactiveLoadBalancer.Factory bean is present while instantiating RetryLoadBalancerInterceptor. Add more tests. * Remove generics from supports(...) method signature. * Allow setting hint per service via properties. * Add some toString() methods. Add more info on deprecated callbacks in javadocs and comments. * Add javadocs. * Format javadocs. Add docs. * Update hint docs. * Fix docs. * Fix docs. * Extract filtering supported lifecycle processors to a separate class; Execute onComplete() calls for DISCARD status in RetryLoadBalancerInterceptor. Remove duplicated `onComplete` calls for FAILED and SUCCESS status in RetryLoadBalancerInterceptor. Add test for no duplicated lifecycle calls in RetryLoadBalancerInterceptorTest. * Small refactoring: remove deprecated methods use, add final keywords, remove unnecessary keywords. * Add javadoc.
This commit is contained in:
committed by
GitHub
parent
c8a57be0f1
commit
46bf5a01ac
@@ -34,6 +34,7 @@
|
||||
|spring.cloud.loadbalancer.health-check.initial-delay | | Initial delay value for the HealthCheck scheduler.
|
||||
|spring.cloud.loadbalancer.health-check.interval | 25s | Interval for rerunning the HealthCheck scheduler.
|
||||
|spring.cloud.loadbalancer.health-check.path | |
|
||||
|spring.cloud.loadbalancer.hint | | Allows setting the value of <code>hint</code> that is passed on to the LoadBalancer request and can subsequently be used in {@link ReactiveLoadBalancer} implementations.
|
||||
|spring.cloud.loadbalancer.retry.enabled | true |
|
||||
|spring.cloud.loadbalancer.ribbon.enabled | true | Causes `RibbonLoadBalancerClient` to be used by default.
|
||||
|spring.cloud.loadbalancer.service-discovery.timeout | | String representation of Duration of the timeout for calls to service discovery.
|
||||
|
||||
@@ -917,8 +917,7 @@ in order to avoid retrying calls on a failing instance.
|
||||
`HealthCheckServiceInstanceListSupplier` uses properties prefixed with
|
||||
`spring.cloud.loadbalancer.health-check`. You can set the `initialDelay` and `interval`
|
||||
for the scheduler. You can set the default path for the healthcheck URL by setting
|
||||
the value of the `spring.cloud.loadbalancer.health-check.path.default`. You can also set a specific value
|
||||
for any given service by setting the value of the `spring.cloud.loadbalancer.health-check.path.[SERVICE_ID]`, substituting the `[SERVICE_ID]` with the correct ID of your service. If the path is not set, `/actuator/health` is used by default.
|
||||
the value of the `spring.cloud.loadbalancer.health-check.path.default` property. You can also set a specific value for any given service by setting the value of the `spring.cloud.loadbalancer.health-check.path.[SERVICE_ID]` property, substituting `[SERVICE_ID]` with the correct ID of your service. If the path is not set, `/actuator/health` is used by default.
|
||||
|
||||
TIP:: If you rely on the default path (`/actuator/health`), make sure you add `spring-boot-starter-actuator` to your collaborator's dependencies, unless you are planning to add such an endpoint on your own.
|
||||
|
||||
@@ -947,6 +946,13 @@ public class CustomLoadBalancerConfiguration {
|
||||
|
||||
NOTE:: `HealthCheckServiceInstanceListSupplier` has its own caching mechanism based on Reactor Flux `replay()`, therefore, if it's being used, you may want to skip wrapping that supplier with `CachingServiceInstanceListSupplier`.
|
||||
|
||||
[[spring-cloud-loadbalancer-hints]]
|
||||
=== Spring Cloud LoadBalancer Hints
|
||||
|
||||
Spring Cloud LoadBalancer lets you set `String` hints that are passed to the LoadBalancer within the `Request` object and that can later be used in `ReactiveLoadBalancer` implementations that can handle them.
|
||||
|
||||
You can set a default hint for all services by setting the value of the `spring.cloud.loadbalancer.hint.default` property. You can also set a specific value
|
||||
for any given service by setting the value of the `spring.cloud.loadbalancer.hint.[SERVICE_ID]` property, substituting `[SERVICE_ID]` with the correct ID of your service. If the hint is not set by the user, `default` is used.
|
||||
|
||||
[[spring-cloud-loadbalancer-starter]]
|
||||
=== Spring Cloud LoadBalancer Starter
|
||||
@@ -1007,6 +1013,20 @@ public class MyConfiguration {
|
||||
----
|
||||
====
|
||||
|
||||
[[loadbalancer-lifecycle]]
|
||||
=== Spring Cloud LoadBalancer Lifecycle
|
||||
|
||||
One type of bean that it may be useful to register using <<custom-loadbalancer-configuration,Custom LoadBalancer configuration>> is `LoadBalancerLifecycle`.
|
||||
|
||||
The LoadBalancerLifecycle beans provide callback methods, named `onStart(Request<RC> request)` and `onComplete(CompletionContext<RES, T> completionContext)`, that you should implement to specify what actions should take place before and after load-balancing.
|
||||
|
||||
`onStart(Request<RC> request)` takes a `Request` object as a parameter. It contains data that is used to select an appropriate instance, including the downstream client request and <<spring-cloud-loadbalancer-hints,hint>>. On the other hand, a `CompletionContext` object is provided to the `onComplete(CompletionContext<RES, T> completionContext)` method. It contains the LoadBalancer `Response`, including the selected service instance, the `Status` of the request executed against that service instance and (if available) the response returned to the downstream client, and (if an exception has occurred) the corresponding `Throwable`.
|
||||
|
||||
The `supports(Class requestContextClass, Class responseClass,
|
||||
Class serverTypeClass)` method can be used to determine whether the processor in question handles objects of provided types. If not overridden by the user, it returns `true`.
|
||||
|
||||
NOTE: In the preceding method calls, `RC` means `RequestContext` type, `RES` means client response type, and `T` means returned server type.
|
||||
|
||||
== Spring Cloud Circuit Breaker
|
||||
|
||||
include::spring-cloud-circuitbreaker.adoc[leveloffset=+1]
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import org.springframework.web.reactive.function.client.ClientRequest;
|
||||
|
||||
/**
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public class ClientRequestContext extends DefaultRequestContext {
|
||||
|
||||
public ClientRequestContext(ClientRequest clientRequest) {
|
||||
this(clientRequest, "default");
|
||||
}
|
||||
|
||||
public ClientRequestContext(ClientRequest clientRequest, String hint) {
|
||||
super(clientRequest, hint);
|
||||
}
|
||||
|
||||
public ClientRequest getClientRequest() {
|
||||
return (ClientRequest) super.getClientRequest();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,22 +19,46 @@ package org.springframework.cloud.client.loadbalancer;
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
|
||||
/**
|
||||
* Allows propagation of data related to load-balanced call completion status.
|
||||
*
|
||||
* @author Spencer Gibb
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @since 3.0.0
|
||||
*/
|
||||
// TODO: add metrics
|
||||
public class CompletionContext {
|
||||
public class CompletionContext<RES, T> {
|
||||
|
||||
private final Status status;
|
||||
|
||||
private final Throwable throwable;
|
||||
|
||||
private final Response<T> loadBalancerResponse;
|
||||
|
||||
private final RES clientResponse;
|
||||
|
||||
public CompletionContext(Status status) {
|
||||
this(status, null);
|
||||
this(status, null, null, null);
|
||||
}
|
||||
|
||||
public CompletionContext(Status status, Throwable throwable) {
|
||||
public CompletionContext(Status status, Response<T> response) {
|
||||
this(status, null, response, null);
|
||||
}
|
||||
|
||||
public CompletionContext(Status status, Throwable throwable,
|
||||
Response<T> loadBalancerResponse) {
|
||||
this(status, throwable, loadBalancerResponse, null);
|
||||
}
|
||||
|
||||
public CompletionContext(Status status, Response<T> loadBalancerResponse,
|
||||
RES clientResponse) {
|
||||
this(status, null, loadBalancerResponse, clientResponse);
|
||||
}
|
||||
|
||||
public CompletionContext(Status status, Throwable throwable,
|
||||
Response<T> loadBalancerResponse, RES clientResponse) {
|
||||
this.status = status;
|
||||
this.throwable = throwable;
|
||||
this.loadBalancerResponse = loadBalancerResponse;
|
||||
this.clientResponse = clientResponse;
|
||||
}
|
||||
|
||||
public Status status() {
|
||||
@@ -45,11 +69,21 @@ public class CompletionContext {
|
||||
return this.throwable;
|
||||
}
|
||||
|
||||
public Response<T> getLoadBalancerResponse() {
|
||||
return loadBalancerResponse;
|
||||
}
|
||||
|
||||
public RES getClientResponse() {
|
||||
return clientResponse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
ToStringCreator to = new ToStringCreator(this);
|
||||
to.append("status", this.status);
|
||||
to.append("throwable", this.throwable);
|
||||
to.append("loadBalancerResponse", loadBalancerResponse);
|
||||
to.append("clientResponse", clientResponse);
|
||||
return to.toString();
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.cloud.client.loadbalancer;
|
||||
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
|
||||
/**
|
||||
* A default implementation of {@link Request}.
|
||||
*
|
||||
@@ -43,4 +45,11 @@ public class DefaultRequest<T> implements Request<T> {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
ToStringCreator to = new ToStringCreator(this);
|
||||
to.append("context", context);
|
||||
return to.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,32 +16,43 @@
|
||||
|
||||
package org.springframework.cloud.client.loadbalancer;
|
||||
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
|
||||
/**
|
||||
* Contains information relevant to the request.
|
||||
*
|
||||
* @author Olga Maciaszek-Sharma
|
||||
*/
|
||||
public class DefaultRequestContext {
|
||||
public class DefaultRequestContext extends HintRequestContext {
|
||||
|
||||
/**
|
||||
* A {@link String} value of hint that can be used to choose the correct service
|
||||
* instance.
|
||||
* The request to be executed against the service instance selected by the
|
||||
* LoadBalancer.
|
||||
*/
|
||||
private String hint = "default";
|
||||
private final Object clientRequest;
|
||||
|
||||
public DefaultRequestContext() {
|
||||
clientRequest = null;
|
||||
}
|
||||
|
||||
public DefaultRequestContext(String hint) {
|
||||
this.hint = hint;
|
||||
public DefaultRequestContext(Object clientRequest) {
|
||||
this.clientRequest = clientRequest;
|
||||
}
|
||||
|
||||
public String getHint() {
|
||||
return hint;
|
||||
public DefaultRequestContext(Object clientRequest, String hint) {
|
||||
super(hint);
|
||||
this.clientRequest = clientRequest;
|
||||
}
|
||||
|
||||
public void setHint(String hint) {
|
||||
this.hint = hint;
|
||||
public Object getClientRequest() {
|
||||
return clientRequest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
ToStringCreator to = new ToStringCreator(this);
|
||||
to.append("clientRequest", clientRequest);
|
||||
return to.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,9 +17,11 @@
|
||||
package org.springframework.cloud.client.loadbalancer;
|
||||
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
* @author Olga Maciaszek-Sharma
|
||||
*/
|
||||
public class DefaultResponse implements Response<ServiceInstance> {
|
||||
|
||||
@@ -41,7 +43,14 @@ public class DefaultResponse implements Response<ServiceInstance> {
|
||||
|
||||
@Override
|
||||
public void onComplete(CompletionContext completionContext) {
|
||||
// TODO: implement
|
||||
// do nothing: deprecated interface method
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
ToStringCreator to = new ToStringCreator(this);
|
||||
to.append("serviceInstance", serviceInstance);
|
||||
return to.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ public class EmptyResponse implements Response<ServiceInstance> {
|
||||
|
||||
@Override
|
||||
public void onComplete(CompletionContext completionContext) {
|
||||
// TODO: implement
|
||||
// do nothing: deprecated interface method
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
|
||||
/**
|
||||
* Allows propagating hints to the LoadBalancer.
|
||||
*
|
||||
* @author Olga Maciaszek-Sharma
|
||||
*/
|
||||
public class HintRequestContext {
|
||||
|
||||
/**
|
||||
* A {@link String} value of hint that can be used to choose the correct service
|
||||
* instance.
|
||||
*/
|
||||
private String hint = "default";
|
||||
|
||||
public HintRequestContext() {
|
||||
}
|
||||
|
||||
public HintRequestContext(String hint) {
|
||||
this.hint = hint;
|
||||
}
|
||||
|
||||
public String getHint() {
|
||||
return hint;
|
||||
}
|
||||
|
||||
public void setHint(String hint) {
|
||||
this.hint = hint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
ToStringCreator to = new ToStringCreator(this);
|
||||
to.append("hint", hint);
|
||||
return to.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import org.springframework.http.HttpRequest;
|
||||
|
||||
/**
|
||||
* @author Olga Maciaszek-Sharma
|
||||
*/
|
||||
public class HttpRequestContext extends DefaultRequestContext {
|
||||
|
||||
public HttpRequestContext(HttpRequest httpRequest) {
|
||||
this(httpRequest, "default");
|
||||
}
|
||||
|
||||
public HttpRequestContext(HttpRequest httpRequest, String hint) {
|
||||
super(httpRequest, hint);
|
||||
}
|
||||
|
||||
public HttpRequest getClientRequest() {
|
||||
return (HttpRequest) super.getClientRequest();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -28,6 +28,9 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerProperties;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.client.ClientHttpRequestInterceptor;
|
||||
@@ -120,17 +123,21 @@ public class LoadBalancerAutoConfiguration {
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(RetryTemplate.class)
|
||||
@ConditionalOnBean(ReactiveLoadBalancer.Factory.class)
|
||||
public static class RetryInterceptorAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public RetryLoadBalancerInterceptor loadBalancerInterceptor(
|
||||
LoadBalancerClient loadBalancerClient,
|
||||
LoadBalancerRetryProperties properties,
|
||||
LoadBalancerRetryProperties retryProperties,
|
||||
LoadBalancerRequestFactory requestFactory,
|
||||
LoadBalancedRetryFactory loadBalancedRetryFactory) {
|
||||
return new RetryLoadBalancerInterceptor(loadBalancerClient, properties,
|
||||
requestFactory, loadBalancedRetryFactory);
|
||||
LoadBalancedRetryFactory loadBalancedRetryFactory,
|
||||
LoadBalancerProperties properties,
|
||||
ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory) {
|
||||
return new RetryLoadBalancerInterceptor(loadBalancerClient, retryProperties,
|
||||
requestFactory, loadBalancedRetryFactory, properties,
|
||||
loadBalancerFactory);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Allows to define actions that should be carried out before and after load-balancing.
|
||||
*
|
||||
* @author Olga Maciaszek-Sharma
|
||||
*/
|
||||
public interface LoadBalancerLifecycle<RC, RES, T> {
|
||||
|
||||
/**
|
||||
* Allows to assess whether the lifecycle bean's callbacks should be executed. Some
|
||||
* examples of possible implementations could comprise of verifying whether the
|
||||
* classes passed in parameters are exactly or extend the classes that this lifecycle
|
||||
* bean should process.
|
||||
* @param requestContextClass The class of the {@link Request} <code>context</code>
|
||||
* @param responseClass The class of the {@link CompletionContext}
|
||||
* <code>clientResponse</code>
|
||||
* @param serverTypeClass The type of Server that the LoadBalancer retrieves
|
||||
* @return <code>true</code> if the lifecycle should be used to process given classes
|
||||
*/
|
||||
default boolean supports(Class requestContextClass, Class responseClass,
|
||||
Class serverTypeClass) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* A callback method executed before load-balancing.
|
||||
* @param request the {@link Request} that will be used by the LoadBalancer to select
|
||||
* a service instance
|
||||
*/
|
||||
void onStart(Request<RC> request);
|
||||
|
||||
/**
|
||||
* A callback method executed after load-balancing.
|
||||
* @param completionContext the {@link CompletionContext} containing data relevant to
|
||||
* the load-balancing and the response returned from the selected service instance
|
||||
*/
|
||||
void onComplete(CompletionContext<RES, T> completionContext);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Utility class containing methods that allow to filter out supported {@link LoadBalancerLifecycle} beans.
|
||||
*
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @since 3.0
|
||||
*/
|
||||
public final class LoadBalancerLifecycleValidator {
|
||||
|
||||
private LoadBalancerLifecycleValidator() {
|
||||
throw new IllegalStateException("Can't instantiate a utility class");
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static Set<LoadBalancerLifecycle> getSupportedLifecycleProcessors(
|
||||
Map<String, LoadBalancerLifecycle> lifecycleProcessors,
|
||||
Class requestContextClass, Class clientResponseClass, Class serverTypeClass) {
|
||||
if (lifecycleProcessors == null) {
|
||||
return new HashSet<>();
|
||||
}
|
||||
return lifecycleProcessors.values().stream()
|
||||
.filter(lifecycle -> lifecycle.supports(requestContextClass,
|
||||
clientResponseClass, serverTypeClass))
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -30,8 +30,8 @@ public interface Response<T> {
|
||||
|
||||
/**
|
||||
* Notification that the request completed.
|
||||
* @deprecated <code>onComplete</code> callbacks for load-balanced calls are being
|
||||
* moved to a separate interface
|
||||
* @deprecated in favour of
|
||||
* {@link LoadBalancerLifecycle#onComplete(CompletionContext)}
|
||||
* @param completionContext - completion context
|
||||
*/
|
||||
@Deprecated
|
||||
|
||||
@@ -18,8 +18,11 @@ package org.springframework.cloud.client.loadbalancer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerProperties;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
|
||||
import org.springframework.http.HttpRequest;
|
||||
import org.springframework.http.client.ClientHttpRequestExecution;
|
||||
import org.springframework.http.client.ClientHttpRequestInterceptor;
|
||||
@@ -36,26 +39,34 @@ import org.springframework.util.StreamUtils;
|
||||
* @author Ryan Baxter
|
||||
* @author Will Tran
|
||||
* @author Gang Li
|
||||
* @author Olga Maciaszek-Sharma
|
||||
*/
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public class RetryLoadBalancerInterceptor implements ClientHttpRequestInterceptor {
|
||||
|
||||
private LoadBalancerClient loadBalancer;
|
||||
private final LoadBalancerClient loadBalancer;
|
||||
|
||||
private LoadBalancerRetryProperties lbProperties;
|
||||
private final LoadBalancerProperties properties;
|
||||
|
||||
private LoadBalancerRequestFactory requestFactory;
|
||||
private final LoadBalancerRequestFactory requestFactory;
|
||||
|
||||
private LoadBalancedRetryFactory lbRetryFactory;
|
||||
private final LoadBalancedRetryFactory lbRetryFactory;
|
||||
|
||||
private final LoadBalancerRetryProperties retryProperties;
|
||||
|
||||
private final ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory;
|
||||
|
||||
public RetryLoadBalancerInterceptor(LoadBalancerClient loadBalancer,
|
||||
LoadBalancerRetryProperties lbProperties,
|
||||
LoadBalancerRetryProperties retryProperties,
|
||||
LoadBalancerRequestFactory requestFactory,
|
||||
LoadBalancedRetryFactory lbRetryFactory) {
|
||||
LoadBalancedRetryFactory lbRetryFactory, LoadBalancerProperties properties,
|
||||
ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory) {
|
||||
this.loadBalancer = loadBalancer;
|
||||
this.lbProperties = lbProperties;
|
||||
this.retryProperties = retryProperties;
|
||||
this.requestFactory = requestFactory;
|
||||
this.lbRetryFactory = lbRetryFactory;
|
||||
|
||||
this.properties = properties;
|
||||
this.loadBalancerFactory = loadBalancerFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -65,8 +76,8 @@ public class RetryLoadBalancerInterceptor implements ClientHttpRequestIntercepto
|
||||
final String serviceName = originalUri.getHost();
|
||||
Assert.state(serviceName != null,
|
||||
"Request URI does not contain a valid hostname: " + originalUri);
|
||||
final LoadBalancedRetryPolicy retryPolicy = this.lbRetryFactory
|
||||
.createRetryPolicy(serviceName, this.loadBalancer);
|
||||
final LoadBalancedRetryPolicy retryPolicy = lbRetryFactory
|
||||
.createRetryPolicy(serviceName, loadBalancer);
|
||||
RetryTemplate template = createRetryTemplate(serviceName, request, retryPolicy);
|
||||
return template.execute(context -> {
|
||||
ServiceInstance serviceInstance = null;
|
||||
@@ -74,12 +85,29 @@ public class RetryLoadBalancerInterceptor implements ClientHttpRequestIntercepto
|
||||
LoadBalancedRetryContext lbContext = (LoadBalancedRetryContext) context;
|
||||
serviceInstance = lbContext.getServiceInstance();
|
||||
}
|
||||
Set<LoadBalancerLifecycle> supportedLifecycleProcessors = LoadBalancerLifecycleValidator
|
||||
.getSupportedLifecycleProcessors(
|
||||
loadBalancerFactory.getInstances(serviceName,
|
||||
LoadBalancerLifecycle.class),
|
||||
HttpRequestContext.class, ClientHttpResponse.class,
|
||||
ServiceInstance.class);
|
||||
String hint = getHint(serviceName);
|
||||
DefaultRequest<HttpRequestContext> lbRequest = new DefaultRequest<>(
|
||||
new HttpRequestContext(request, hint));
|
||||
supportedLifecycleProcessors
|
||||
.forEach(lifecycle -> lifecycle.onStart(lbRequest));
|
||||
if (serviceInstance == null) {
|
||||
serviceInstance = this.loadBalancer.choose(serviceName);
|
||||
serviceInstance = loadBalancer.choose(serviceName, lbRequest);
|
||||
}
|
||||
Response<ServiceInstance> lbResponse = new DefaultResponse(serviceInstance);
|
||||
if (serviceInstance == null) {
|
||||
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onComplete(
|
||||
new CompletionContext<ClientHttpResponse, ServiceInstance>(
|
||||
CompletionContext.Status.DISCARD, lbResponse)));
|
||||
}
|
||||
ClientHttpResponse response = RetryLoadBalancerInterceptor.this.loadBalancer
|
||||
.execute(serviceName, serviceInstance,
|
||||
this.requestFactory.createRequest(request, body, execution));
|
||||
requestFactory.createRequest(request, body, execution));
|
||||
int statusCode = response.getRawStatusCode();
|
||||
if (retryPolicy != null && retryPolicy.retryableStatusCode(statusCode)) {
|
||||
byte[] bodyCopy = StreamUtils.copyToByteArray(response.getBody());
|
||||
@@ -103,20 +131,24 @@ public class RetryLoadBalancerInterceptor implements ClientHttpRequestIntercepto
|
||||
private RetryTemplate createRetryTemplate(String serviceName, HttpRequest request,
|
||||
LoadBalancedRetryPolicy retryPolicy) {
|
||||
RetryTemplate template = new RetryTemplate();
|
||||
BackOffPolicy backOffPolicy = this.lbRetryFactory
|
||||
.createBackOffPolicy(serviceName);
|
||||
BackOffPolicy backOffPolicy = lbRetryFactory.createBackOffPolicy(serviceName);
|
||||
template.setBackOffPolicy(
|
||||
backOffPolicy == null ? new NoBackOffPolicy() : backOffPolicy);
|
||||
template.setThrowLastExceptionOnExhausted(true);
|
||||
RetryListener[] retryListeners = this.lbRetryFactory
|
||||
.createRetryListeners(serviceName);
|
||||
RetryListener[] retryListeners = lbRetryFactory.createRetryListeners(serviceName);
|
||||
if (retryListeners != null && retryListeners.length != 0) {
|
||||
template.setListeners(retryListeners);
|
||||
}
|
||||
template.setRetryPolicy(!this.lbProperties.isEnabled() || retryPolicy == null
|
||||
template.setRetryPolicy(!retryProperties.isEnabled() || retryPolicy == null
|
||||
? new NeverRetryPolicy() : new InterceptorRetryPolicy(request,
|
||||
retryPolicy, this.loadBalancer, serviceName));
|
||||
retryPolicy, loadBalancer, serviceName));
|
||||
return template;
|
||||
}
|
||||
|
||||
private String getHint(String serviceId) {
|
||||
String defaultHint = properties.getHint().getOrDefault("default", "default");
|
||||
String hintPropertyValue = properties.getHint().get(serviceId);
|
||||
return hintPropertyValue != null ? hintPropertyValue : defaultHint;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import org.springframework.cloud.client.ServiceInstance;
|
||||
* to.
|
||||
*
|
||||
* @author Ryan Baxter
|
||||
* @author Olga Maciaszek-Sharma
|
||||
*/
|
||||
public interface ServiceInstanceChooser {
|
||||
|
||||
@@ -33,4 +34,13 @@ public interface ServiceInstanceChooser {
|
||||
*/
|
||||
ServiceInstance choose(String serviceId);
|
||||
|
||||
/**
|
||||
* Chooses a ServiceInstance from the LoadBalancer for the specified service and
|
||||
* LoadBalancer request.
|
||||
* @param serviceId The service ID to look up the LoadBalancer.
|
||||
* @param request The request to pass on to the LoadBalancer
|
||||
* @return A ServiceInstance that matches the serviceId.
|
||||
*/
|
||||
<T> ServiceInstance choose(String serviceId, Request<T> request);
|
||||
|
||||
}
|
||||
|
||||
@@ -36,6 +36,13 @@ public class LoadBalancerProperties {
|
||||
*/
|
||||
private HealthCheck healthCheck = new HealthCheck();
|
||||
|
||||
/**
|
||||
* Allows setting the value of <code>hint</code> that is passed on to the LoadBalancer
|
||||
* request and can subsequently be used in {@link ReactiveLoadBalancer}
|
||||
* implementations.
|
||||
*/
|
||||
private Map<String, String> hint = new LinkedCaseInsensitiveMap<>();
|
||||
|
||||
public HealthCheck getHealthCheck() {
|
||||
return healthCheck;
|
||||
}
|
||||
@@ -44,6 +51,14 @@ public class LoadBalancerProperties {
|
||||
this.healthCheck = healthCheck;
|
||||
}
|
||||
|
||||
public Map<String, String> getHint() {
|
||||
return hint;
|
||||
}
|
||||
|
||||
public void setHint(Map<String, String> hint) {
|
||||
this.hint = hint;
|
||||
}
|
||||
|
||||
public static class HealthCheck {
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.cloud.client.loadbalancer.reactive;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
|
||||
import org.springframework.cloud.client.loadbalancer.DefaultRequest;
|
||||
@@ -49,11 +51,32 @@ public interface ReactiveLoadBalancer<T> {
|
||||
return choose(REQUEST);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface Factory<T> {
|
||||
|
||||
ReactiveLoadBalancer<T> getInstance(String serviceId);
|
||||
|
||||
/**
|
||||
* Allows accessing beans registered within client-specific LoadBalancer contexts.
|
||||
* @param name Name of the beans to be returned
|
||||
* @param type The class of the beans to be returned
|
||||
* @param <X> The type of the beans to be returned
|
||||
* @return a {@link Map} of beans
|
||||
* @see <code>@LoadBalancerClient</code>
|
||||
*/
|
||||
<X> Map<String, X> getInstances(String name, Class<X> type);
|
||||
|
||||
/**
|
||||
* Allows accessing a bean registered within client-specific LoadBalancer
|
||||
* contexts.
|
||||
* @param name Name of the bean to be returned
|
||||
* @param clazz The class of the bean to be returned
|
||||
* @param generics The classes of generic types of the bean to be returned
|
||||
* @param <X> The type of the bean to be returned
|
||||
* @return a {@link Map} of beans
|
||||
* @see <code>@LoadBalancerClient</code>
|
||||
*/
|
||||
<X> X getInstance(String name, Class<?> clazz, Class<?>... generics);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -40,8 +40,10 @@ public class ReactorLoadBalancerClientAutoConfiguration {
|
||||
@ConditionalOnMissingBean
|
||||
@Bean
|
||||
public ReactorLoadBalancerExchangeFilterFunction loadBalancerExchangeFilterFunction(
|
||||
ReactiveLoadBalancer.Factory loadBalancerFactory) {
|
||||
return new ReactorLoadBalancerExchangeFilterFunction(loadBalancerFactory);
|
||||
ReactiveLoadBalancer.Factory loadBalancerFactory,
|
||||
LoadBalancerProperties properties) {
|
||||
return new ReactorLoadBalancerExchangeFilterFunction(loadBalancerFactory,
|
||||
properties);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,14 +17,21 @@
|
||||
package org.springframework.cloud.client.loadbalancer.reactive;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.loadbalancer.ClientRequestContext;
|
||||
import org.springframework.cloud.client.loadbalancer.CompletionContext;
|
||||
import org.springframework.cloud.client.loadbalancer.DefaultRequest;
|
||||
import org.springframework.cloud.client.loadbalancer.EmptyResponse;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycle;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycleValidator;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerUriTools;
|
||||
import org.springframework.cloud.client.loadbalancer.Request;
|
||||
import org.springframework.cloud.client.loadbalancer.Response;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.reactive.function.client.ClientRequest;
|
||||
@@ -39,6 +46,7 @@ import org.springframework.web.reactive.function.client.ExchangeFunction;
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @since 2.2.0
|
||||
*/
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
public class ReactorLoadBalancerExchangeFilterFunction implements ExchangeFilterFunction {
|
||||
|
||||
private static final Log LOG = LogFactory
|
||||
@@ -46,14 +54,19 @@ public class ReactorLoadBalancerExchangeFilterFunction implements ExchangeFilter
|
||||
|
||||
private final ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory;
|
||||
|
||||
private final LoadBalancerProperties properties;
|
||||
|
||||
public ReactorLoadBalancerExchangeFilterFunction(
|
||||
ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory) {
|
||||
ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory,
|
||||
LoadBalancerProperties properties) {
|
||||
this.loadBalancerFactory = loadBalancerFactory;
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
|
||||
URI originalUrl = request.url();
|
||||
public Mono<ClientResponse> filter(ClientRequest clientRequest,
|
||||
ExchangeFunction next) {
|
||||
URI originalUrl = clientRequest.url();
|
||||
String serviceId = originalUrl.getHost();
|
||||
if (serviceId == null) {
|
||||
String message = String.format(
|
||||
@@ -65,13 +78,26 @@ public class ReactorLoadBalancerExchangeFilterFunction implements ExchangeFilter
|
||||
return Mono.just(
|
||||
ClientResponse.create(HttpStatus.BAD_REQUEST).body(message).build());
|
||||
}
|
||||
return choose(serviceId).flatMap(response -> {
|
||||
ServiceInstance instance = response.getServer();
|
||||
Set<LoadBalancerLifecycle> supportedLifecycleProcessors = LoadBalancerLifecycleValidator
|
||||
.getSupportedLifecycleProcessors(
|
||||
loadBalancerFactory.getInstances(serviceId,
|
||||
LoadBalancerLifecycle.class),
|
||||
ClientRequestContext.class, ClientResponse.class,
|
||||
ServiceInstance.class);
|
||||
String hint = getHint(serviceId);
|
||||
DefaultRequest<ClientRequestContext> lbRequest = new DefaultRequest<>(
|
||||
new ClientRequestContext(clientRequest, hint));
|
||||
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest));
|
||||
return choose(serviceId, lbRequest).flatMap(lbResponse -> {
|
||||
ServiceInstance instance = lbResponse.getServer();
|
||||
if (instance == null) {
|
||||
String message = serviceInstanceUnavailableMessage(serviceId);
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn(message);
|
||||
}
|
||||
supportedLifecycleProcessors.forEach(
|
||||
lifecycle -> lifecycle.onComplete(new CompletionContext<>(
|
||||
CompletionContext.Status.DISCARD, lbResponse)));
|
||||
return Mono.just(ClientResponse.create(HttpStatus.SERVICE_UNAVAILABLE)
|
||||
.body(serviceInstanceUnavailableMessage(serviceId)).build());
|
||||
}
|
||||
@@ -81,9 +107,19 @@ public class ReactorLoadBalancerExchangeFilterFunction implements ExchangeFilter
|
||||
"Load balancer has retrieved the instance for service %s: %s",
|
||||
serviceId, instance.getUri()));
|
||||
}
|
||||
ClientRequest newRequest = buildClientRequest(request,
|
||||
ClientRequest newRequest = buildClientRequest(clientRequest,
|
||||
reconstructURI(instance, originalUrl));
|
||||
return next.exchange(newRequest);
|
||||
return next.exchange(newRequest)
|
||||
.doOnError(throwable -> supportedLifecycleProcessors
|
||||
.forEach(lifecycle -> lifecycle.onComplete(
|
||||
new CompletionContext<ClientResponse, ServiceInstance>(
|
||||
CompletionContext.Status.FAILED, throwable,
|
||||
lbResponse))))
|
||||
.doOnSuccess(clientResponse -> supportedLifecycleProcessors
|
||||
.forEach(lifecycle -> lifecycle.onComplete(
|
||||
new CompletionContext<ClientResponse, ServiceInstance>(
|
||||
CompletionContext.Status.SUCCESS, lbResponse,
|
||||
clientResponse))));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -91,13 +127,14 @@ public class ReactorLoadBalancerExchangeFilterFunction implements ExchangeFilter
|
||||
return LoadBalancerUriTools.reconstructURI(instance, original);
|
||||
}
|
||||
|
||||
protected Mono<Response<ServiceInstance>> choose(String serviceId) {
|
||||
protected Mono<Response<ServiceInstance>> choose(String serviceId,
|
||||
Request<ClientRequestContext> request) {
|
||||
ReactiveLoadBalancer<ServiceInstance> loadBalancer = loadBalancerFactory
|
||||
.getInstance(serviceId);
|
||||
if (loadBalancer == null) {
|
||||
return Mono.just(new EmptyResponse());
|
||||
}
|
||||
return Mono.from(loadBalancer.choose());
|
||||
return Mono.from(loadBalancer.choose(request));
|
||||
}
|
||||
|
||||
private String serviceInstanceUnavailableMessage(String serviceId) {
|
||||
@@ -112,4 +149,10 @@ public class ReactorLoadBalancerExchangeFilterFunction implements ExchangeFilter
|
||||
.body(request.body()).build();
|
||||
}
|
||||
|
||||
private String getHint(String serviceId) {
|
||||
String defaultHint = properties.getHint().getOrDefault("default", "default");
|
||||
String hintPropertyValue = properties.getHint().get(serviceId);
|
||||
return hintPropertyValue != null ? hintPropertyValue : defaultHint;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package org.springframework.cloud.client.loadbalancer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
@@ -29,17 +28,22 @@ import org.springframework.boot.WebApplicationType;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.cloud.client.DefaultServiceInstance;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerProperties;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import static org.assertj.core.api.BDDAssertions.then;
|
||||
import static org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer.REQUEST;
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
* @author Tim Ysewyn
|
||||
* @author Olga Maciaszek-Sharma
|
||||
*/
|
||||
public abstract class AbstractLoadBalancerAutoConfigurationTests {
|
||||
|
||||
@@ -98,9 +102,20 @@ public abstract class AbstractLoadBalancerAutoConfigurationTests {
|
||||
return new NoopLoadBalancerClient();
|
||||
}
|
||||
|
||||
@Bean
|
||||
LoadBalancerProperties loadBalancerProperties() {
|
||||
return new LoadBalancerProperties();
|
||||
}
|
||||
|
||||
@Bean
|
||||
ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory() {
|
||||
return new TestLoadBalancerFactory();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Import(OneRestTemplate.class)
|
||||
protected static class TwoRestTemplates {
|
||||
|
||||
@Primary
|
||||
@@ -109,17 +124,6 @@ public abstract class AbstractLoadBalancerAutoConfigurationTests {
|
||||
return new RestTemplate();
|
||||
}
|
||||
|
||||
@LoadBalanced
|
||||
@Bean
|
||||
RestTemplate loadBalancedRestTemplate() {
|
||||
return new RestTemplate();
|
||||
}
|
||||
|
||||
@Bean
|
||||
LoadBalancerClient loadBalancerClient() {
|
||||
return new NoopLoadBalancerClient();
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
protected static class Two {
|
||||
|
||||
@@ -140,6 +144,11 @@ public abstract class AbstractLoadBalancerAutoConfigurationTests {
|
||||
|
||||
@Override
|
||||
public ServiceInstance choose(String serviceId) {
|
||||
return choose(serviceId, REQUEST);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ServiceInstance choose(String serviceId, Request<T> request) {
|
||||
return new DefaultServiceInstance(serviceId, serviceId, serviceId,
|
||||
this.random.nextInt(40000), false);
|
||||
}
|
||||
@@ -147,7 +156,7 @@ public abstract class AbstractLoadBalancerAutoConfigurationTests {
|
||||
@Override
|
||||
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) {
|
||||
try {
|
||||
return request.apply(choose(serviceId));
|
||||
return request.apply(choose(serviceId, REQUEST));
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
@@ -156,9 +165,9 @@ public abstract class AbstractLoadBalancerAutoConfigurationTests {
|
||||
|
||||
@Override
|
||||
public <T> T execute(String serviceId, ServiceInstance serviceInstance,
|
||||
LoadBalancerRequest<T> request) throws IOException {
|
||||
LoadBalancerRequest<T> request) {
|
||||
try {
|
||||
return request.apply(choose(serviceId));
|
||||
return request.apply(choose(serviceId, REQUEST));
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
@@ -172,4 +181,24 @@ public abstract class AbstractLoadBalancerAutoConfigurationTests {
|
||||
|
||||
}
|
||||
|
||||
private static class TestLoadBalancerFactory
|
||||
implements ReactiveLoadBalancer.Factory<ServiceInstance> {
|
||||
|
||||
@Override
|
||||
public ReactiveLoadBalancer<ServiceInstance> getInstance(String serviceId) {
|
||||
throw new UnsupportedOperationException("Not implemented.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getInstance(String name, Class clazz, Class[] generics) {
|
||||
throw new UnsupportedOperationException("Not implemented.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map getInstances(String name, Class type) {
|
||||
throw new UnsupportedOperationException("Not implemented.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package org.springframework.cloud.client.loadbalancer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@@ -38,10 +37,12 @@ import org.springframework.http.client.AsyncClientHttpRequestInterceptor;
|
||||
import org.springframework.web.client.AsyncRestTemplate;
|
||||
|
||||
import static org.assertj.core.api.BDDAssertions.then;
|
||||
import static org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer.REQUEST;
|
||||
|
||||
/**
|
||||
* @author Rob Worsnop
|
||||
* @author Tim Ysewyn
|
||||
* @author Olga Maciaszek-Sharma
|
||||
*/
|
||||
public class AsyncLoadBalancerAutoConfigurationTests {
|
||||
|
||||
@@ -154,6 +155,11 @@ public class AsyncLoadBalancerAutoConfigurationTests {
|
||||
|
||||
@Override
|
||||
public ServiceInstance choose(String serviceId) {
|
||||
return choose(serviceId, REQUEST);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ServiceInstance choose(String serviceId, Request<T> request) {
|
||||
return new DefaultServiceInstance(serviceId, serviceId, serviceId,
|
||||
this.random.nextInt(40000), false);
|
||||
}
|
||||
@@ -161,7 +167,7 @@ public class AsyncLoadBalancerAutoConfigurationTests {
|
||||
@Override
|
||||
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) {
|
||||
try {
|
||||
return request.apply(choose(serviceId));
|
||||
return request.apply(choose(serviceId, REQUEST));
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
@@ -170,9 +176,9 @@ public class AsyncLoadBalancerAutoConfigurationTests {
|
||||
|
||||
@Override
|
||||
public <T> T execute(String serviceId, ServiceInstance serviceInstance,
|
||||
LoadBalancerRequest<T> request) throws IOException {
|
||||
LoadBalancerRequest<T> request) {
|
||||
try {
|
||||
return request.apply(choose(serviceId));
|
||||
return request.apply(choose(serviceId, REQUEST));
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
|
||||
@@ -21,21 +21,24 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import org.springframework.boot.WebApplicationType;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerProperties;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.http.HttpRequest;
|
||||
import org.springframework.http.client.ClientHttpRequestExecution;
|
||||
|
||||
import static org.assertj.core.api.BDDAssertions.then;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
@@ -134,13 +137,9 @@ public class LoadBalancerRequestFactoryConfigurationTests {
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Import(NoTransformer.class)
|
||||
static class Transformer {
|
||||
|
||||
@Bean
|
||||
public LoadBalancerClient loadBalancerClient() {
|
||||
return mock(LoadBalancerClient.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public LoadBalancerRequestTransformer transformer() {
|
||||
return mock(LoadBalancerRequestTransformer.class);
|
||||
@@ -149,18 +148,9 @@ public class LoadBalancerRequestFactoryConfigurationTests {
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Import(Transformer.class)
|
||||
static class TransformersAreOrdered {
|
||||
|
||||
@Bean
|
||||
public LoadBalancerClient loadBalancerClient() {
|
||||
return mock(LoadBalancerClient.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public LoadBalancerRequestTransformer transformer() {
|
||||
return mock(LoadBalancerRequestTransformer.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(LoadBalancerRequestTransformer.DEFAULT_ORDER + 1)
|
||||
public LoadBalancerRequestTransformer transformer2() {
|
||||
@@ -177,6 +167,17 @@ public class LoadBalancerRequestFactoryConfigurationTests {
|
||||
return mock(LoadBalancerClient.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
LoadBalancerProperties loadBalancerProperties() {
|
||||
return new LoadBalancerProperties();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Bean
|
||||
ReactiveLoadBalancer.Factory<ServiceInstance> factory() {
|
||||
return mock(ReactiveLoadBalancer.Factory.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ public class RetryLoadBalancerAutoConfigurationTests
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaultBackOffPolicy() throws Exception {
|
||||
public void testDefaultBackOffPolicy() {
|
||||
ConfigurableApplicationContext context = init(OneRestTemplate.class);
|
||||
LoadBalancedRetryFactory loadBalancedRetryFactory = context
|
||||
.getBean(LoadBalancedRetryFactory.class);
|
||||
|
||||
@@ -20,6 +20,12 @@ import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
@@ -28,7 +34,10 @@ import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentMatchers;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import org.springframework.cloud.client.DefaultServiceInstance;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerProperties;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
|
||||
import org.springframework.http.HttpRequest;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.client.ClientHttpRequestExecution;
|
||||
@@ -44,10 +53,11 @@ import org.springframework.retry.backoff.BackOffPolicy;
|
||||
import org.springframework.retry.backoff.NoBackOffPolicy;
|
||||
import org.springframework.retry.listener.RetryListenerSupport;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.BDDAssertions.then;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.nullable;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
@@ -56,31 +66,38 @@ import static org.mockito.Mockito.when;
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
* @author Gang Li
|
||||
* @author Olga Maciaszek-Sharma
|
||||
*/
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class RetryLoadBalancerInterceptorTest {
|
||||
public class RetryLoadBalancerInterceptorTests {
|
||||
|
||||
private LoadBalancerClient client;
|
||||
|
||||
private LoadBalancerRetryProperties lbProperties;
|
||||
private LoadBalancerRetryProperties retryProperties;
|
||||
|
||||
private LoadBalancerRequestFactory lbRequestFactory;
|
||||
|
||||
private LoadBalancedRetryFactory loadBalancedRetryFactory = new LoadBalancedRetryFactory() {
|
||||
private final LoadBalancedRetryFactory loadBalancedRetryFactory = new LoadBalancedRetryFactory() {
|
||||
};
|
||||
|
||||
private LoadBalancerProperties properties;
|
||||
|
||||
private ReactiveLoadBalancer.Factory<ServiceInstance> lbFactory;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
this.client = mock(LoadBalancerClient.class);
|
||||
this.lbProperties = new LoadBalancerRetryProperties();
|
||||
this.lbRequestFactory = mock(LoadBalancerRequestFactory.class);
|
||||
|
||||
client = mock(LoadBalancerClient.class);
|
||||
retryProperties = new LoadBalancerRetryProperties();
|
||||
lbRequestFactory = mock(LoadBalancerRequestFactory.class);
|
||||
properties = new LoadBalancerProperties();
|
||||
lbFactory = mock(ReactiveLoadBalancer.Factory.class);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
this.client = null;
|
||||
this.lbProperties = null;
|
||||
public void tearDown() {
|
||||
client = null;
|
||||
retryProperties = null;
|
||||
}
|
||||
|
||||
@Test(expected = IOException.class)
|
||||
@@ -88,31 +105,31 @@ public class RetryLoadBalancerInterceptorTest {
|
||||
HttpRequest request = mock(HttpRequest.class);
|
||||
when(request.getURI()).thenReturn(new URI("http://foo"));
|
||||
ServiceInstance serviceInstance = mock(ServiceInstance.class);
|
||||
when(this.client.choose(eq("foo"))).thenReturn(serviceInstance);
|
||||
when(this.client.execute(eq("foo"), eq(serviceInstance),
|
||||
when(client.choose(eq("foo"), any())).thenReturn(serviceInstance);
|
||||
when(client.execute(eq("foo"), eq(serviceInstance),
|
||||
any(LoadBalancerRequest.class))).thenThrow(new IOException());
|
||||
this.lbProperties.setEnabled(false);
|
||||
retryProperties.setEnabled(false);
|
||||
RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(
|
||||
this.client, this.lbProperties, this.lbRequestFactory,
|
||||
this.loadBalancedRetryFactory);
|
||||
client, retryProperties, lbRequestFactory, loadBalancedRetryFactory,
|
||||
properties, lbFactory);
|
||||
byte[] body = new byte[] {};
|
||||
ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class);
|
||||
|
||||
when(this.lbRequestFactory.createRequest(any(), any(), any()))
|
||||
when(lbRequestFactory.createRequest(any(), any(), any()))
|
||||
.thenReturn(mock(LoadBalancerRequest.class));
|
||||
|
||||
interceptor.intercept(request, body, execution);
|
||||
verify(this.lbRequestFactory).createRequest(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"));
|
||||
this.lbProperties.setEnabled(true);
|
||||
retryProperties.setEnabled(true);
|
||||
RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(
|
||||
this.client, this.lbProperties, this.lbRequestFactory,
|
||||
this.loadBalancedRetryFactory);
|
||||
client, retryProperties, lbRequestFactory, loadBalancedRetryFactory,
|
||||
properties, lbFactory);
|
||||
byte[] body = new byte[] {};
|
||||
ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class);
|
||||
interceptor.intercept(request, body, execution);
|
||||
@@ -125,19 +142,19 @@ public class RetryLoadBalancerInterceptorTest {
|
||||
ClientHttpResponse clientHttpResponse = new MockClientHttpResponse(new byte[] {},
|
||||
HttpStatus.OK);
|
||||
ServiceInstance serviceInstance = mock(ServiceInstance.class);
|
||||
when(this.client.choose(eq("foo"))).thenReturn(serviceInstance);
|
||||
when(this.client.execute(eq("foo"), eq(serviceInstance),
|
||||
when(client.choose(eq("foo"), any())).thenReturn(serviceInstance);
|
||||
when(client.execute(eq("foo"), eq(serviceInstance),
|
||||
any(LoadBalancerRequest.class))).thenReturn(clientHttpResponse);
|
||||
when(this.lbRequestFactory.createRequest(any(), any(), any()))
|
||||
when(lbRequestFactory.createRequest(any(), any(), any()))
|
||||
.thenReturn(mock(LoadBalancerRequest.class));
|
||||
this.lbProperties.setEnabled(true);
|
||||
retryProperties.setEnabled(true);
|
||||
RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(
|
||||
this.client, this.lbProperties, this.lbRequestFactory,
|
||||
this.loadBalancedRetryFactory);
|
||||
client, retryProperties, lbRequestFactory, loadBalancedRetryFactory,
|
||||
properties, lbFactory);
|
||||
byte[] body = new byte[] {};
|
||||
ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class);
|
||||
interceptor.intercept(request, body, execution);
|
||||
verify(this.lbRequestFactory).createRequest(request, body, execution);
|
||||
verify(lbRequestFactory).createRequest(request, body, execution);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -148,20 +165,20 @@ public class RetryLoadBalancerInterceptorTest {
|
||||
HttpStatus.OK);
|
||||
LoadBalancedRetryPolicy policy = mock(LoadBalancedRetryPolicy.class);
|
||||
ServiceInstance serviceInstance = mock(ServiceInstance.class);
|
||||
when(this.client.choose(eq("foo"))).thenReturn(serviceInstance);
|
||||
when(this.client.execute(eq("foo"), eq(serviceInstance),
|
||||
when(client.choose(eq("foo"), any())).thenReturn(serviceInstance);
|
||||
when(client.execute(eq("foo"), eq(serviceInstance),
|
||||
any(LoadBalancerRequest.class))).thenReturn(clientHttpResponse);
|
||||
when(this.lbRequestFactory.createRequest(any(), any(), any()))
|
||||
when(lbRequestFactory.createRequest(any(), any(), any()))
|
||||
.thenReturn(mock(LoadBalancerRequest.class));
|
||||
this.lbProperties.setEnabled(true);
|
||||
retryProperties.setEnabled(true);
|
||||
RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(
|
||||
this.client, this.lbProperties, this.lbRequestFactory,
|
||||
new MyLoadBalancedRetryFactory(policy));
|
||||
client, retryProperties, lbRequestFactory,
|
||||
new MyLoadBalancedRetryFactory(policy), properties, lbFactory);
|
||||
byte[] body = new byte[] {};
|
||||
ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class);
|
||||
ClientHttpResponse rsp = interceptor.intercept(request, body, execution);
|
||||
then(rsp).isEqualTo(clientHttpResponse);
|
||||
verify(this.lbRequestFactory).createRequest(request, body, execution);
|
||||
verify(lbRequestFactory).createRequest(request, body, execution);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -180,23 +197,23 @@ public class RetryLoadBalancerInterceptorTest {
|
||||
when(policy.canRetryNextServer(any(LoadBalancedRetryContext.class)))
|
||||
.thenReturn(true);
|
||||
ServiceInstance serviceInstance = mock(ServiceInstance.class);
|
||||
when(this.client.choose(eq("foo"))).thenReturn(serviceInstance);
|
||||
when(this.client.execute(eq("foo"), eq(serviceInstance),
|
||||
when(client.choose(eq("foo"), any())).thenReturn(serviceInstance);
|
||||
when(client.execute(eq("foo"), eq(serviceInstance),
|
||||
nullable(LoadBalancerRequest.class)))
|
||||
.thenReturn(clientHttpResponseNotFound)
|
||||
.thenReturn(clientHttpResponseOk);
|
||||
this.lbProperties.setEnabled(true);
|
||||
retryProperties.setEnabled(true);
|
||||
RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(
|
||||
this.client, this.lbProperties, this.lbRequestFactory,
|
||||
new MyLoadBalancedRetryFactory(policy));
|
||||
client, retryProperties, lbRequestFactory,
|
||||
new MyLoadBalancedRetryFactory(policy), properties, lbFactory);
|
||||
byte[] body = new byte[] {};
|
||||
ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class);
|
||||
ClientHttpResponse rsp = interceptor.intercept(request, body, execution);
|
||||
verify(this.client, times(2)).execute(eq("foo"), eq(serviceInstance),
|
||||
verify(client, times(2)).execute(eq("foo"), eq(serviceInstance),
|
||||
nullable(LoadBalancerRequest.class));
|
||||
verify(notFoundStream, times(1)).close();
|
||||
then(rsp).isEqualTo(clientHttpResponseOk);
|
||||
verify(this.lbRequestFactory, times(2)).createRequest(request, body, execution);
|
||||
verify(lbRequestFactory, times(2)).createRequest(request, body, execution);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -215,22 +232,22 @@ public class RetryLoadBalancerInterceptorTest {
|
||||
.thenReturn(false);
|
||||
|
||||
ServiceInstance serviceInstance = mock(ServiceInstance.class);
|
||||
when(this.client.choose(eq("foo"))).thenReturn(serviceInstance);
|
||||
when(this.client.execute(eq("foo"), eq(serviceInstance),
|
||||
when(client.choose(eq("foo"), any())).thenReturn(serviceInstance);
|
||||
when(client.execute(eq("foo"), eq(serviceInstance),
|
||||
ArgumentMatchers.<LoadBalancerRequest<ClientHttpResponse>>any()))
|
||||
.thenReturn(clientHttpResponseNotFound);
|
||||
|
||||
this.lbProperties.setEnabled(true);
|
||||
retryProperties.setEnabled(true);
|
||||
byte[] body = new byte[] {};
|
||||
ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class);
|
||||
RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(
|
||||
this.client, this.lbProperties, this.lbRequestFactory,
|
||||
new MyLoadBalancedRetryFactory(policy));
|
||||
client, retryProperties, lbRequestFactory,
|
||||
new MyLoadBalancedRetryFactory(policy), properties, lbFactory);
|
||||
ClientHttpResponse rsp = interceptor.intercept(request, body, execution);
|
||||
|
||||
verify(this.client, times(1)).execute(eq("foo"), eq(serviceInstance),
|
||||
verify(client, times(1)).execute(eq("foo"), eq(serviceInstance),
|
||||
ArgumentMatchers.<LoadBalancerRequest<ClientHttpResponse>>any());
|
||||
verify(this.lbRequestFactory, times(1)).createRequest(request, body, execution);
|
||||
verify(lbRequestFactory, times(1)).createRequest(request, body, execution);
|
||||
verify(policy, times(2)).canRetryNextServer(any(LoadBalancedRetryContext.class));
|
||||
|
||||
// call twice in a retry attempt
|
||||
@@ -251,23 +268,24 @@ public class RetryLoadBalancerInterceptorTest {
|
||||
.thenReturn(true);
|
||||
MyBackOffPolicy backOffPolicy = new MyBackOffPolicy();
|
||||
ServiceInstance serviceInstance = mock(ServiceInstance.class);
|
||||
when(this.client.choose(eq("foo"))).thenReturn(serviceInstance);
|
||||
when(this.client.execute(eq("foo"), eq(serviceInstance),
|
||||
when(client.choose(eq("foo"), any())).thenReturn(serviceInstance);
|
||||
when(client.execute(eq("foo"), eq(serviceInstance),
|
||||
any(LoadBalancerRequest.class))).thenThrow(new IOException())
|
||||
.thenReturn(clientHttpResponse);
|
||||
when(this.lbRequestFactory.createRequest(any(), any(), any()))
|
||||
when(lbRequestFactory.createRequest(any(), any(), any()))
|
||||
.thenReturn(mock(LoadBalancerRequest.class));
|
||||
this.lbProperties.setEnabled(true);
|
||||
retryProperties.setEnabled(true);
|
||||
RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(
|
||||
this.client, this.lbProperties, this.lbRequestFactory,
|
||||
new MyLoadBalancedRetryFactory(policy, backOffPolicy));
|
||||
client, retryProperties, lbRequestFactory,
|
||||
new MyLoadBalancedRetryFactory(policy, backOffPolicy), properties,
|
||||
lbFactory);
|
||||
byte[] body = new byte[] {};
|
||||
ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class);
|
||||
ClientHttpResponse rsp = interceptor.intercept(request, body, execution);
|
||||
verify(this.client, times(2)).execute(eq("foo"), eq(serviceInstance),
|
||||
verify(client, times(2)).execute(eq("foo"), eq(serviceInstance),
|
||||
any(LoadBalancerRequest.class));
|
||||
then(rsp).isEqualTo(clientHttpResponse);
|
||||
verify(this.lbRequestFactory, times(2)).createRequest(request, body, execution);
|
||||
verify(lbRequestFactory, times(2)).createRequest(request, body, execution);
|
||||
then(backOffPolicy.getBackoffAttempts()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@@ -281,20 +299,24 @@ public class RetryLoadBalancerInterceptorTest {
|
||||
when(policy.canRetryNextServer(any(LoadBalancedRetryContext.class)))
|
||||
.thenReturn(false);
|
||||
ServiceInstance serviceInstance = mock(ServiceInstance.class);
|
||||
when(this.client.choose(eq("foo"))).thenReturn(serviceInstance);
|
||||
when(this.client.execute(eq("foo"), eq(serviceInstance),
|
||||
when(client.choose(eq("foo"), any())).thenReturn(serviceInstance);
|
||||
when(client.execute(eq("foo"), eq(serviceInstance),
|
||||
any(LoadBalancerRequest.class))).thenThrow(new IOException())
|
||||
.thenReturn(clientHttpResponse);
|
||||
when(this.lbRequestFactory.createRequest(any(), any(), any()))
|
||||
when(lbRequestFactory.createRequest(any(), any(), any()))
|
||||
.thenReturn(mock(LoadBalancerRequest.class));
|
||||
this.lbProperties.setEnabled(true);
|
||||
retryProperties.setEnabled(true);
|
||||
RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(
|
||||
this.client, this.lbProperties, this.lbRequestFactory,
|
||||
new MyLoadBalancedRetryFactory(policy));
|
||||
client, retryProperties, lbRequestFactory,
|
||||
new MyLoadBalancedRetryFactory(policy), properties, lbFactory);
|
||||
byte[] body = new byte[] {};
|
||||
ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class);
|
||||
interceptor.intercept(request, body, execution);
|
||||
verify(this.lbRequestFactory).createRequest(request, body, execution);
|
||||
verify(lbRequestFactory).createRequest(request, body, execution);
|
||||
}
|
||||
|
||||
private static ServiceInstance defaultServiceInstance() {
|
||||
return new DefaultServiceInstance("testInstance", "test", "testHost", 80, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -308,51 +330,30 @@ public class RetryLoadBalancerInterceptorTest {
|
||||
.thenReturn(true);
|
||||
MyBackOffPolicy backOffPolicy = new MyBackOffPolicy();
|
||||
ServiceInstance serviceInstance = mock(ServiceInstance.class);
|
||||
when(this.client.choose(eq("listener"))).thenReturn(serviceInstance);
|
||||
when(this.client.execute(eq("listener"), eq(serviceInstance),
|
||||
when(client.choose(eq("listener"), any())).thenReturn(serviceInstance);
|
||||
when(client.execute(eq("listener"), eq(serviceInstance),
|
||||
any(LoadBalancerRequest.class))).thenThrow(new IOException())
|
||||
.thenReturn(clientHttpResponse);
|
||||
this.lbProperties.setEnabled(true);
|
||||
retryProperties.setEnabled(true);
|
||||
MyRetryListener retryListener = new MyRetryListener();
|
||||
when(this.lbRequestFactory.createRequest(any(), any(), any()))
|
||||
when(lbRequestFactory.createRequest(any(), any(), any()))
|
||||
.thenReturn(mock(LoadBalancerRequest.class));
|
||||
RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(
|
||||
this.client, this.lbProperties, this.lbRequestFactory,
|
||||
client, retryProperties, lbRequestFactory,
|
||||
new MyLoadBalancedRetryFactory(policy, backOffPolicy,
|
||||
new RetryListener[] { retryListener }));
|
||||
new RetryListener[] { retryListener }),
|
||||
properties, lbFactory);
|
||||
byte[] body = new byte[] {};
|
||||
ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class);
|
||||
ClientHttpResponse rsp = interceptor.intercept(request, body, execution);
|
||||
verify(this.client, times(2)).execute(eq("listener"), eq(serviceInstance),
|
||||
verify(client, times(2)).execute(eq("listener"), eq(serviceInstance),
|
||||
any(LoadBalancerRequest.class));
|
||||
then(rsp).isEqualTo(clientHttpResponse);
|
||||
verify(this.lbRequestFactory, times(2)).createRequest(request, body, execution);
|
||||
verify(lbRequestFactory, times(2)).createRequest(request, body, execution);
|
||||
then(backOffPolicy.getBackoffAttempts()).isEqualTo(1);
|
||||
then(retryListener.getOnError()).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();
|
||||
this.lbProperties.setEnabled(true);
|
||||
RetryListener myRetryListener = new RetryListenerSupport() {
|
||||
@Override
|
||||
public <T, E extends Throwable> boolean open(RetryContext context,
|
||||
RetryCallback<T, E> callback) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(
|
||||
this.client, this.lbProperties, this.lbRequestFactory,
|
||||
new MyLoadBalancedRetryFactory(policy, backOffPolicy,
|
||||
new RetryListener[] { myRetryListener }));
|
||||
ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class);
|
||||
interceptor.intercept(request, new byte[] {}, execution);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void retryWithDefaultConstructorTest() throws Throwable {
|
||||
HttpRequest request = mock(HttpRequest.class);
|
||||
@@ -364,29 +365,91 @@ public class RetryLoadBalancerInterceptorTest {
|
||||
.thenReturn(true);
|
||||
MyBackOffPolicy backOffPolicy = new MyBackOffPolicy();
|
||||
ServiceInstance serviceInstance = mock(ServiceInstance.class);
|
||||
when(this.client.choose(eq("default"))).thenReturn(serviceInstance);
|
||||
when(this.client.execute(eq("default"), eq(serviceInstance),
|
||||
when(client.choose(eq("default"), any())).thenReturn(serviceInstance);
|
||||
when(client.execute(eq("default"), eq(serviceInstance),
|
||||
any(LoadBalancerRequest.class))).thenThrow(new IOException())
|
||||
.thenReturn(clientHttpResponse);
|
||||
this.lbProperties.setEnabled(true);
|
||||
when(this.lbRequestFactory.createRequest(any(), any(), any()))
|
||||
retryProperties.setEnabled(true);
|
||||
when(lbRequestFactory.createRequest(any(), any(), any()))
|
||||
.thenReturn(mock(LoadBalancerRequest.class));
|
||||
RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(
|
||||
this.client, this.lbProperties, this.lbRequestFactory,
|
||||
new MyLoadBalancedRetryFactory(policy, backOffPolicy));
|
||||
client, retryProperties, lbRequestFactory,
|
||||
new MyLoadBalancedRetryFactory(policy, backOffPolicy), properties,
|
||||
lbFactory);
|
||||
byte[] body = new byte[] {};
|
||||
ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class);
|
||||
ClientHttpResponse rsp = interceptor.intercept(request, body, execution);
|
||||
verify(this.client, times(2)).execute(eq("default"), eq(serviceInstance),
|
||||
verify(client, times(2)).execute(eq("default"), eq(serviceInstance),
|
||||
any(LoadBalancerRequest.class));
|
||||
then(rsp).isEqualTo(clientHttpResponse);
|
||||
verify(this.lbRequestFactory, times(2)).createRequest(request, body, execution);
|
||||
verify(lbRequestFactory, times(2)).createRequest(request, body, execution);
|
||||
then(backOffPolicy.getBackoffAttempts()).isEqualTo(1);
|
||||
}
|
||||
|
||||
class MyLoadBalancedRetryFactory implements LoadBalancedRetryFactory {
|
||||
@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();
|
||||
retryProperties.setEnabled(true);
|
||||
RetryListener myRetryListener = new RetryListenerSupport() {
|
||||
@Override
|
||||
public <T, E extends Throwable> boolean open(RetryContext context,
|
||||
RetryCallback<T, E> callback) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
RetryLoadBalancerInterceptor interceptor = new RetryLoadBalancerInterceptor(
|
||||
client, retryProperties, lbRequestFactory,
|
||||
new MyLoadBalancedRetryFactory(policy, backOffPolicy,
|
||||
new RetryListener[] { myRetryListener }),
|
||||
properties, lbFactory);
|
||||
ClientHttpRequestExecution execution = mock(ClientHttpRequestExecution.class);
|
||||
interceptor.intercept(request, new byte[] {}, execution);
|
||||
}
|
||||
|
||||
private LoadBalancedRetryPolicy loadBalancedRetryPolicy;
|
||||
@Test
|
||||
public void shouldNotDuplicateLifecycleCalls()
|
||||
throws IOException, URISyntaxException {
|
||||
Map<String, LoadBalancerLifecycle> 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, retryProperties, lbRequestFactory, loadBalancedRetryFactory,
|
||||
properties, lbFactory);
|
||||
|
||||
interceptor.intercept(request, new byte[] {},
|
||||
mock(ClientHttpRequestExecution.class));
|
||||
|
||||
assertThat(((TestLoadBalancerLifecycle) lifecycleProcessors.get("testLifecycle"))
|
||||
.getStartLog()).hasSize(1);
|
||||
assertThat(((TestLoadBalancerLifecycle) lifecycleProcessors.get("testLifecycle"))
|
||||
.getCompleteLog()).hasSize(0);
|
||||
assertThat(
|
||||
((TestLoadBalancerLifecycle) lifecycleProcessors.get("anotherLifecycle"))
|
||||
.getStartLog()).hasSize(1);
|
||||
assertThat(
|
||||
((TestLoadBalancerLifecycle) lifecycleProcessors.get("anotherLifecycle"))
|
||||
.getCompleteLog()).hasSize(0);
|
||||
assertThat(((TestLoadBalancerLifecycle) client.getLifecycleProcessors()
|
||||
.get("testLifecycle")).getStartLog()).hasSize(0);
|
||||
assertThat(((TestLoadBalancerLifecycle) client.getLifecycleProcessors()
|
||||
.get("testLifecycle")).getCompleteLog()).hasSize(1);
|
||||
assertThat(((TestLoadBalancerLifecycle) client.getLifecycleProcessors()
|
||||
.get("anotherLifecycle")).getStartLog()).hasSize(0);
|
||||
assertThat(((TestLoadBalancerLifecycle) client.getLifecycleProcessors()
|
||||
.get("anotherLifecycle")).getCompleteLog()).hasSize(1);
|
||||
}
|
||||
|
||||
static class MyLoadBalancedRetryFactory implements LoadBalancedRetryFactory {
|
||||
|
||||
private final LoadBalancedRetryPolicy loadBalancedRetryPolicy;
|
||||
|
||||
private BackOffPolicy backOffPolicy;
|
||||
|
||||
@@ -411,32 +474,32 @@ public class RetryLoadBalancerInterceptorTest {
|
||||
@Override
|
||||
public LoadBalancedRetryPolicy createRetryPolicy(String service,
|
||||
ServiceInstanceChooser serviceInstanceChooser) {
|
||||
return this.loadBalancedRetryPolicy;
|
||||
return loadBalancedRetryPolicy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BackOffPolicy createBackOffPolicy(String service) {
|
||||
if (this.backOffPolicy == null) {
|
||||
if (backOffPolicy == null) {
|
||||
return new NoBackOffPolicy();
|
||||
}
|
||||
else {
|
||||
return this.backOffPolicy;
|
||||
return backOffPolicy;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public RetryListener[] createRetryListeners(String service) {
|
||||
if (this.retryListeners == null) {
|
||||
if (retryListeners == null) {
|
||||
return new RetryListener[0];
|
||||
}
|
||||
else {
|
||||
return this.retryListeners;
|
||||
return retryListeners;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class MyBackOffPolicy implements BackOffPolicy {
|
||||
static class MyBackOffPolicy implements BackOffPolicy {
|
||||
|
||||
private int backoffAttempts = 0;
|
||||
|
||||
@@ -453,27 +516,128 @@ public class RetryLoadBalancerInterceptorTest {
|
||||
@Override
|
||||
public void backOff(BackOffContext backOffContext)
|
||||
throws BackOffInterruptedException {
|
||||
this.backoffAttempts++;
|
||||
backoffAttempts++;
|
||||
}
|
||||
|
||||
int getBackoffAttempts() {
|
||||
return this.backoffAttempts;
|
||||
return backoffAttempts;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class MyRetryListener extends RetryListenerSupport {
|
||||
static class MyRetryListener extends RetryListenerSupport {
|
||||
|
||||
private int onError = 0;
|
||||
|
||||
@Override
|
||||
public <T, E extends Throwable> void onError(RetryContext retryContext,
|
||||
RetryCallback<T, E> retryCallback, Throwable throwable) {
|
||||
this.onError++;
|
||||
onError++;
|
||||
}
|
||||
|
||||
int getOnError() {
|
||||
return this.onError;
|
||||
return onError;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected static class TestLoadBalancerClient implements LoadBalancerClient {
|
||||
|
||||
private final Map<String, LoadBalancerLifecycle> lifecycleProcessors = new HashMap<>();
|
||||
|
||||
TestLoadBalancerClient() {
|
||||
lifecycleProcessors.put("testLifecycle", new TestLoadBalancerLifecycle());
|
||||
lifecycleProcessors.put("anotherLifecycle",
|
||||
new AnotherLoadBalancerLifecycle());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) {
|
||||
throw new UnsupportedOperationException("Not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T execute(String serviceId, ServiceInstance serviceInstance,
|
||||
LoadBalancerRequest<T> request) {
|
||||
Set<LoadBalancerLifecycle> supportedLoadBalancerProcessors = LoadBalancerLifecycleValidator
|
||||
.getSupportedLifecycleProcessors(lifecycleProcessors,
|
||||
DefaultRequestContext.class, Object.class,
|
||||
ServiceInstance.class);
|
||||
|
||||
T response = (T) new MockClientHttpResponse(new byte[] {}, HttpStatus.OK);
|
||||
supportedLoadBalancerProcessors.forEach(lifecycle -> lifecycle
|
||||
.onComplete(new CompletionContext(CompletionContext.Status.SUCCESS,
|
||||
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 <T> ServiceInstance choose(String serviceId, Request<T> request) {
|
||||
return defaultServiceInstance();
|
||||
}
|
||||
|
||||
Map<String, LoadBalancerLifecycle> getLifecycleProcessors() {
|
||||
return lifecycleProcessors;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected static class TestLoadBalancerLifecycle
|
||||
implements LoadBalancerLifecycle<Object, Object, ServiceInstance> {
|
||||
|
||||
final ConcurrentHashMap<String, Request<Object>> startLog = new ConcurrentHashMap<>();
|
||||
|
||||
final ConcurrentHashMap<String, CompletionContext<Object, ServiceInstance>> 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<Object> request) {
|
||||
startLog.put(getName() + UUID.randomUUID(), request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(
|
||||
CompletionContext<Object, ServiceInstance> completionContext) {
|
||||
completeLog.put(getName() + UUID.randomUUID(), completionContext);
|
||||
}
|
||||
|
||||
ConcurrentHashMap<String, Request<Object>> getStartLog() {
|
||||
return startLog;
|
||||
}
|
||||
|
||||
ConcurrentHashMap<String, CompletionContext<Object, ServiceInstance>> getCompleteLog() {
|
||||
return completeLog;
|
||||
}
|
||||
|
||||
protected String getName() {
|
||||
return getClass().getSimpleName();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected static class AnotherLoadBalancerLifecycle
|
||||
extends TestLoadBalancerLifecycle {
|
||||
|
||||
@Override
|
||||
protected String getName() {
|
||||
return getClass().getSimpleName();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -106,7 +106,24 @@ public class ReactorLoadBalancerClientAutoConfigurationTests {
|
||||
|
||||
@Bean
|
||||
ReactiveLoadBalancer.Factory<ServiceInstance> reactiveLoadBalancerFactory() {
|
||||
return serviceId -> new TestReactiveLoadBalancer();
|
||||
return new ReactiveLoadBalancer.Factory<ServiceInstance>() {
|
||||
@Override
|
||||
public ReactiveLoadBalancer<ServiceInstance> getInstance(
|
||||
String serviceId) {
|
||||
return new TestReactiveLoadBalancer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <X> Map<String, X> getInstances(String name, Class<X> type) {
|
||||
throw new UnsupportedOperationException("Not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public <X> X getInstance(String name, Class<?> clazz,
|
||||
Class<?>... generics) {
|
||||
throw new UnsupportedOperationException("Not implemented.");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -115,6 +132,11 @@ public class ReactorLoadBalancerClientAutoConfigurationTests {
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
LoadBalancerProperties loadBalancerProperties() {
|
||||
return new LoadBalancerProperties();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
|
||||
@@ -17,13 +17,17 @@
|
||||
package org.springframework.cloud.client.loadbalancer.reactive;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@@ -36,18 +40,22 @@ 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.DefaultResponse;
|
||||
import org.springframework.cloud.client.loadbalancer.EmptyResponse;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycle;
|
||||
import org.springframework.cloud.client.loadbalancer.Request;
|
||||
import org.springframework.cloud.client.loadbalancer.Response;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
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;
|
||||
|
||||
@@ -56,7 +64,7 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
|
||||
*
|
||||
* @author Olga Maciaszek-Sharma
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@SpringBootTest(webEnvironment = RANDOM_PORT)
|
||||
class ReactorLoadBalancerExchangeFilterFunctionTests {
|
||||
|
||||
@@ -66,6 +74,12 @@ class ReactorLoadBalancerExchangeFilterFunctionTests {
|
||||
@Autowired
|
||||
private SimpleDiscoveryProperties properties;
|
||||
|
||||
@Autowired
|
||||
private LoadBalancerProperties loadBalancerProperties;
|
||||
|
||||
@Autowired
|
||||
private ReactiveLoadBalancer.Factory<ServiceInstance> factory;
|
||||
|
||||
@LocalServerPort
|
||||
private int port;
|
||||
|
||||
@@ -74,8 +88,15 @@ class ReactorLoadBalancerExchangeFilterFunctionTests {
|
||||
SimpleDiscoveryProperties.SimpleServiceInstance instance = new SimpleDiscoveryProperties.SimpleServiceInstance();
|
||||
instance.setServiceId("testservice");
|
||||
instance.setUri(URI.create("http://localhost:" + this.port));
|
||||
SimpleDiscoveryProperties.SimpleServiceInstance instanceWithNoLifecycleProcessors = new SimpleDiscoveryProperties.SimpleServiceInstance();
|
||||
instanceWithNoLifecycleProcessors
|
||||
.setServiceId("serviceWithNoLifecycleProcessors");
|
||||
instanceWithNoLifecycleProcessors
|
||||
.setUri(URI.create("http://localhost:" + this.port));
|
||||
this.properties.getInstances().put("testservice",
|
||||
Collections.singletonList(instance));
|
||||
properties.getInstances().put("serviceWithNoLifecycleProcessors",
|
||||
Collections.singletonList(instanceWithNoLifecycleProcessors));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -101,9 +122,43 @@ class ReactorLoadBalancerExchangeFilterFunctionTests {
|
||||
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();
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadBalancerLifecycleCallbacksExecuted() {
|
||||
final String callbackTestHint = "callbackTestHint";
|
||||
loadBalancerProperties.getHint().put("testservice", "callbackTestHint");
|
||||
final String result = "callbackTestResult";
|
||||
ClientResponse clientResponse = WebClient.builder().baseUrl("http://testservice")
|
||||
.filter(this.loadBalancerFunction).build().get().uri("/callback")
|
||||
.exchange().block();
|
||||
|
||||
Collection<Request<Object>> lifecycleLogRequests = ((TestLoadBalancerLifecycle) factory
|
||||
.getInstances("testservice", LoadBalancerLifecycle.class)
|
||||
.get("loadBalancerLifecycle")).getStartLog().values();
|
||||
Collection<CompletionContext<Object, ServiceInstance>> 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(anotherLifecycleLogRequests)
|
||||
.extracting(completionContext -> ((ClientResponse) completionContext
|
||||
.getClientResponse()).bodyToMono(String.class).block())
|
||||
.contains(result);
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
@EnableDiscoveryClient
|
||||
@EnableAutoConfiguration
|
||||
@SpringBootConfiguration
|
||||
@SpringBootConfiguration(proxyBeanMethods = false)
|
||||
@RestController
|
||||
static class Config {
|
||||
|
||||
@@ -112,11 +167,93 @@ class ReactorLoadBalancerExchangeFilterFunctionTests {
|
||||
return "Hello World";
|
||||
}
|
||||
|
||||
@RequestMapping("/callback")
|
||||
String callbackTestResult() {
|
||||
return "callbackTestResult";
|
||||
}
|
||||
|
||||
@Bean
|
||||
ReactiveLoadBalancer.Factory<ServiceInstance> reactiveLoadBalancerFactory(
|
||||
DiscoveryClient discoveryClient) {
|
||||
return serviceId -> new DiscoveryClientBasedReactiveLoadBalancer(serviceId,
|
||||
discoveryClient);
|
||||
return new ReactiveLoadBalancer.Factory<ServiceInstance>() {
|
||||
|
||||
private final TestLoadBalancerLifecycle testLoadBalancerLifecycle = new TestLoadBalancerLifecycle();
|
||||
|
||||
private final TestLoadBalancerLifecycle anotherLoadBalancerLifecycle = new AnotherLoadBalancerLifecycle();
|
||||
|
||||
@Override
|
||||
public ReactiveLoadBalancer<ServiceInstance> getInstance(
|
||||
String serviceId) {
|
||||
return new DiscoveryClientBasedReactiveLoadBalancer(serviceId,
|
||||
discoveryClient);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <X> Map<String, X> getInstances(String name, Class<X> type) {
|
||||
if (name.equals("serviceWithNoLifecycleProcessors")) {
|
||||
return null;
|
||||
}
|
||||
Map lifecycleProcessors = new HashMap<>();
|
||||
lifecycleProcessors.put("loadBalancerLifecycle",
|
||||
testLoadBalancerLifecycle);
|
||||
lifecycleProcessors.put("anotherLoadBalancerLifecycle",
|
||||
anotherLoadBalancerLifecycle);
|
||||
return lifecycleProcessors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <X> X getInstance(String name, Class<?> clazz,
|
||||
Class<?>... generics) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
LoadBalancerProperties loadBalancerProperties() {
|
||||
return new LoadBalancerProperties();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected static class TestLoadBalancerLifecycle
|
||||
implements LoadBalancerLifecycle<Object, Object, ServiceInstance> {
|
||||
|
||||
ConcurrentHashMap<String, Request<Object>> startLog = new ConcurrentHashMap<>();
|
||||
|
||||
ConcurrentHashMap<String, CompletionContext<Object, ServiceInstance>> completeLog = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public void onStart(Request<Object> request) {
|
||||
startLog.put(getName() + UUID.randomUUID(), request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(
|
||||
CompletionContext<Object, ServiceInstance> completionContext) {
|
||||
completeLog.put(getName() + UUID.randomUUID(), completionContext);
|
||||
}
|
||||
|
||||
ConcurrentHashMap<String, Request<Object>> getStartLog() {
|
||||
return startLog;
|
||||
}
|
||||
|
||||
ConcurrentHashMap<String, CompletionContext<Object, ServiceInstance>> getCompleteLog() {
|
||||
return completeLog;
|
||||
}
|
||||
|
||||
protected String getName() {
|
||||
return this.getClass().getSimpleName();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected static class AnotherLoadBalancerLifecycle
|
||||
extends TestLoadBalancerLifecycle {
|
||||
|
||||
@Override
|
||||
protected String getName() {
|
||||
return this.getClass().getSimpleName();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,38 +18,67 @@ package org.springframework.cloud.loadbalancer.blocking.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.Set;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.loadbalancer.CompletionContext;
|
||||
import org.springframework.cloud.client.loadbalancer.DefaultRequest;
|
||||
import org.springframework.cloud.client.loadbalancer.DefaultRequestContext;
|
||||
import org.springframework.cloud.client.loadbalancer.DefaultResponse;
|
||||
import org.springframework.cloud.client.loadbalancer.EmptyResponse;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycle;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycleValidator;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerRequest;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerUriTools;
|
||||
import org.springframework.cloud.client.loadbalancer.Request;
|
||||
import org.springframework.cloud.client.loadbalancer.Response;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerProperties;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
|
||||
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import static org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer.REQUEST;
|
||||
|
||||
/**
|
||||
* The default {@link LoadBalancerClient} implementation.
|
||||
*
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @since 2.2.0
|
||||
*/
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public class BlockingLoadBalancerClient implements LoadBalancerClient {
|
||||
|
||||
private final LoadBalancerClientFactory loadBalancerClientFactory;
|
||||
|
||||
public BlockingLoadBalancerClient(
|
||||
LoadBalancerClientFactory loadBalancerClientFactory) {
|
||||
private final LoadBalancerProperties properties;
|
||||
|
||||
public BlockingLoadBalancerClient(LoadBalancerClientFactory loadBalancerClientFactory,
|
||||
LoadBalancerProperties properties) {
|
||||
this.loadBalancerClientFactory = loadBalancerClientFactory;
|
||||
this.properties = properties;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T execute(String serviceId, LoadBalancerRequest<T> request)
|
||||
throws IOException {
|
||||
ServiceInstance serviceInstance = choose(serviceId);
|
||||
String hint = getHint(serviceId);
|
||||
DefaultRequest<DefaultRequestContext> lbRequest = new DefaultRequest<>(
|
||||
new DefaultRequestContext(request, hint));
|
||||
Set<LoadBalancerLifecycle> supportedLifecycleProcessors = LoadBalancerLifecycleValidator
|
||||
.getSupportedLifecycleProcessors(
|
||||
loadBalancerClientFactory.getInstances(serviceId,
|
||||
LoadBalancerLifecycle.class),
|
||||
DefaultRequestContext.class, Object.class, ServiceInstance.class);
|
||||
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest));
|
||||
ServiceInstance serviceInstance = choose(serviceId, lbRequest);
|
||||
if (serviceInstance == null) {
|
||||
supportedLifecycleProcessors
|
||||
.forEach(lifecycle -> lifecycle.onComplete(new CompletionContext<>(
|
||||
CompletionContext.Status.DISCARD, new EmptyResponse())));
|
||||
throw new IllegalStateException("No instances available for " + serviceId);
|
||||
}
|
||||
return execute(serviceId, serviceInstance, request);
|
||||
@@ -58,13 +87,29 @@ public class BlockingLoadBalancerClient implements LoadBalancerClient {
|
||||
@Override
|
||||
public <T> T execute(String serviceId, ServiceInstance serviceInstance,
|
||||
LoadBalancerRequest<T> request) throws IOException {
|
||||
DefaultResponse defaultResponse = new DefaultResponse(serviceInstance);
|
||||
Set<LoadBalancerLifecycle> supportedLifecycleProcessors = LoadBalancerLifecycleValidator
|
||||
.getSupportedLifecycleProcessors(
|
||||
loadBalancerClientFactory.getInstances(serviceId,
|
||||
LoadBalancerLifecycle.class),
|
||||
DefaultRequestContext.class, Object.class, ServiceInstance.class);
|
||||
try {
|
||||
return request.apply(serviceInstance);
|
||||
T response = request.apply(serviceInstance);
|
||||
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle
|
||||
.onComplete(new CompletionContext<>(CompletionContext.Status.SUCCESS,
|
||||
defaultResponse, response)));
|
||||
return response;
|
||||
}
|
||||
catch (IOException iOException) {
|
||||
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle
|
||||
.onComplete(new CompletionContext<>(CompletionContext.Status.FAILED,
|
||||
iOException, defaultResponse)));
|
||||
throw iOException;
|
||||
}
|
||||
catch (Exception exception) {
|
||||
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle
|
||||
.onComplete(new CompletionContext<>(CompletionContext.Status.FAILED,
|
||||
exception, defaultResponse)));
|
||||
ReflectionUtils.rethrowRuntimeException(exception);
|
||||
}
|
||||
return null;
|
||||
@@ -77,17 +122,28 @@ public class BlockingLoadBalancerClient implements LoadBalancerClient {
|
||||
|
||||
@Override
|
||||
public ServiceInstance choose(String serviceId) {
|
||||
return choose(serviceId, REQUEST);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ServiceInstance choose(String serviceId, Request<T> request) {
|
||||
ReactiveLoadBalancer<ServiceInstance> loadBalancer = loadBalancerClientFactory
|
||||
.getInstance(serviceId);
|
||||
if (loadBalancer == null) {
|
||||
return null;
|
||||
}
|
||||
Response<ServiceInstance> loadBalancerResponse = Mono.from(loadBalancer.choose())
|
||||
.block();
|
||||
Response<ServiceInstance> loadBalancerResponse = Mono
|
||||
.from(loadBalancer.choose(request)).block();
|
||||
if (loadBalancerResponse == null) {
|
||||
return null;
|
||||
}
|
||||
return loadBalancerResponse.getServer();
|
||||
}
|
||||
|
||||
private String getHint(String serviceId) {
|
||||
String defaultHint = properties.getHint().getOrDefault("default", "default");
|
||||
String hintPropertyValue = properties.getHint().get(serviceId);
|
||||
return hintPropertyValue != null ? hintPropertyValue : defaultHint;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.cloud.client.loadbalancer.AsyncLoadBalancerAutoConfiguration;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerProperties;
|
||||
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
|
||||
import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient;
|
||||
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
|
||||
@@ -49,8 +50,9 @@ public class BlockingLoadBalancerClientAutoConfiguration {
|
||||
@ConditionalOnBean(LoadBalancerClientFactory.class)
|
||||
@ConditionalOnMissingBean
|
||||
public LoadBalancerClient blockingLoadBalancerClient(
|
||||
LoadBalancerClientFactory loadBalancerClientFactory) {
|
||||
return new BlockingLoadBalancerClient(loadBalancerClientFactory);
|
||||
LoadBalancerClientFactory loadBalancerClientFactory,
|
||||
LoadBalancerProperties properties) {
|
||||
return new BlockingLoadBalancerClient(loadBalancerClientFactory, properties);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,35 +18,42 @@ package org.springframework.cloud.loadbalancer.blocking.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
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.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.discovery.DiscoveryClient;
|
||||
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.DefaultResponse;
|
||||
import org.springframework.cloud.client.loadbalancer.EmptyResponse;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycle;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerRequest;
|
||||
import org.springframework.cloud.client.loadbalancer.Request;
|
||||
import org.springframework.cloud.client.loadbalancer.Response;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerProperties;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
|
||||
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
|
||||
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
|
||||
import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
|
||||
import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
|
||||
/**
|
||||
@@ -55,7 +62,6 @@ import static org.assertj.core.api.Assertions.fail;
|
||||
* @author Olga Maciaszek-Sharma
|
||||
*/
|
||||
@SpringBootTest
|
||||
@ExtendWith(SpringExtension.class)
|
||||
class BlockingLoadBalancerClientTests {
|
||||
|
||||
@Autowired
|
||||
@@ -64,6 +70,12 @@ class BlockingLoadBalancerClientTests {
|
||||
@Autowired
|
||||
private SimpleDiscoveryProperties properties;
|
||||
|
||||
@Autowired
|
||||
ReactiveLoadBalancer.Factory<ServiceInstance> factory;
|
||||
|
||||
@Autowired
|
||||
LoadBalancerProperties loadBalancerProperties;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
properties.getInstances().put("myservice",
|
||||
@@ -141,19 +153,65 @@ class BlockingLoadBalancerClientTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void exceptionNotThrownWhenFactoryReturnsNullLifecycleProcessorsMap() {
|
||||
assertThatCode(
|
||||
() -> loadBalancerClient.execute("serviceWithNoLifecycleProcessors",
|
||||
(LoadBalancerRequest<Object>) instance -> {
|
||||
assertThat(instance.getHost()).isEqualTo("test.example");
|
||||
return "result";
|
||||
})).doesNotThrowAnyException();
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadBalancerLifecycleCallbacksExecuted() throws IOException {
|
||||
String callbackTestHint = "callbackTestHint";
|
||||
loadBalancerProperties.getHint().put("myservice", "callbackTestHint");
|
||||
final String result = "callbackTestResult";
|
||||
Object actualResult = loadBalancerClient.execute("myservice",
|
||||
(LoadBalancerRequest<Object>) instance -> {
|
||||
assertThat(instance.getHost()).isEqualTo("test.example");
|
||||
return result;
|
||||
});
|
||||
|
||||
Collection<Request<Object>> lifecycleLogRequests = ((TestLoadBalancerLifecycle) factory
|
||||
.getInstances("myservice", LoadBalancerLifecycle.class)
|
||||
.get("loadBalancerLifecycle")).getStartLog().values();
|
||||
Collection<CompletionContext<Object, ServiceInstance>> anotherLifecycleLogRequests = ((AnotherLoadBalancerLifecycle) factory
|
||||
.getInstances("myservice", LoadBalancerLifecycle.class)
|
||||
.get("anotherLoadBalancerLifecycle")).getCompleteLog().values();
|
||||
assertThat(actualResult).isEqualTo(result);
|
||||
assertThat(lifecycleLogRequests).extracting(
|
||||
request -> ((DefaultRequestContext) request.getContext()).getHint())
|
||||
.contains(callbackTestHint);
|
||||
assertThat(anotherLifecycleLogRequests)
|
||||
.extracting(CompletionContext::getClientResponse).contains(result);
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableAutoConfiguration
|
||||
@SpringBootConfiguration
|
||||
@LoadBalancerClients({
|
||||
@org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient(
|
||||
name = "myservice", configuration = MyServiceConfig.class),
|
||||
@org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient(
|
||||
name = "unknownservice",
|
||||
configuration = UnknownServiceConfig.class) })
|
||||
@LoadBalancerClient(name = "myservice",
|
||||
configuration = MyServiceConfig.class),
|
||||
@LoadBalancerClient(name = "unknownservice",
|
||||
configuration = UnknownServiceConfig.class),
|
||||
@LoadBalancerClient(name = "serviceWithNoLifecycleProcessors",
|
||||
configuration = NoLifecycleProcessorsConfig.class) })
|
||||
protected static class Config {
|
||||
|
||||
}
|
||||
|
||||
protected static class NoLifecycleProcessorsConfig {
|
||||
|
||||
@Bean
|
||||
ReactorLoadBalancer<ServiceInstance> reactiveLoadBalancer(
|
||||
DiscoveryClient discoveryClient) {
|
||||
return new DiscoveryClientBasedReactiveLoadBalancer("myservice",
|
||||
discoveryClient);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected static class MyServiceConfig {
|
||||
|
||||
@Bean
|
||||
@@ -163,6 +221,16 @@ class BlockingLoadBalancerClientTests {
|
||||
discoveryClient);
|
||||
}
|
||||
|
||||
@Bean
|
||||
LoadBalancerLifecycle<Object, Object, ServiceInstance> loadBalancerLifecycle() {
|
||||
return new TestLoadBalancerLifecycle();
|
||||
}
|
||||
|
||||
@Bean
|
||||
LoadBalancerLifecycle<Object, Object, ServiceInstance> anotherLoadBalancerLifecycle() {
|
||||
return new AnotherLoadBalancerLifecycle();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected static class UnknownServiceConfig {
|
||||
@@ -176,8 +244,51 @@ class BlockingLoadBalancerClientTests {
|
||||
|
||||
}
|
||||
|
||||
protected static class TestLoadBalancerLifecycle
|
||||
implements LoadBalancerLifecycle<Object, Object, ServiceInstance> {
|
||||
|
||||
final ConcurrentHashMap<String, Request<Object>> startLog = new ConcurrentHashMap<>();
|
||||
|
||||
final ConcurrentHashMap<String, CompletionContext<Object, ServiceInstance>> completeLog = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public void onStart(Request<Object> request) {
|
||||
startLog.put(getName() + UUID.randomUUID(), request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(
|
||||
CompletionContext<Object, ServiceInstance> completionContext) {
|
||||
completeLog.put(getName() + UUID.randomUUID(), completionContext);
|
||||
}
|
||||
|
||||
ConcurrentHashMap<String, Request<Object>> getStartLog() {
|
||||
return startLog;
|
||||
}
|
||||
|
||||
ConcurrentHashMap<String, CompletionContext<Object, ServiceInstance>> getCompleteLog() {
|
||||
return completeLog;
|
||||
}
|
||||
|
||||
protected String getName() {
|
||||
return this.getClass().getSimpleName();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected static class AnotherLoadBalancerLifecycle
|
||||
extends TestLoadBalancerLifecycle {
|
||||
|
||||
@Override
|
||||
protected String getName() {
|
||||
return this.getClass().getSimpleName();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
class DiscoveryClientBasedReactiveLoadBalancer
|
||||
implements ReactorServiceInstanceLoadBalancer {
|
||||
|
||||
|
||||
@@ -110,8 +110,9 @@ class CachingServiceInstanceListSupplierTests {
|
||||
|
||||
@Bean
|
||||
BlockingLoadBalancerClient blockingLoadBalancerClient(
|
||||
LoadBalancerClientFactory loadBalancerClientFactory) {
|
||||
return new BlockingLoadBalancerClient(loadBalancerClientFactory);
|
||||
LoadBalancerClientFactory loadBalancerClientFactory,
|
||||
LoadBalancerProperties properties) {
|
||||
return new BlockingLoadBalancerClient(loadBalancerClientFactory, properties);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -176,7 +176,7 @@ public class LoadBalancerTests {
|
||||
}
|
||||
|
||||
@EnableAutoConfiguration
|
||||
@SpringBootConfiguration
|
||||
@SpringBootConfiguration(proxyBeanMethods = false)
|
||||
@LoadBalancerClients({
|
||||
@LoadBalancerClient(name = "myservice",
|
||||
configuration = MyServiceConfig.class),
|
||||
|
||||
Reference in New Issue
Block a user