Use LoadBalancer per-client properties. (#2426)

This commit is contained in:
Olga Maciaszek-Sharma
2021-11-03 14:19:33 +01:00
committed by spencergibb
parent 33cfdc69ee
commit 3308a493ea
5 changed files with 62 additions and 17 deletions

View File

@@ -20,9 +20,7 @@ import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties;
import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
import org.springframework.cloud.gateway.config.conditional.ConditionalOnEnabledGlobalFilter;
import org.springframework.cloud.gateway.filter.LoadBalancerServiceInstanceCookieFilter;
@@ -50,19 +48,17 @@ public class GatewayReactiveLoadBalancerClientAutoConfiguration {
@ConditionalOnMissingBean(ReactiveLoadBalancerClientFilter.class)
@ConditionalOnEnabledGlobalFilter
public ReactiveLoadBalancerClientFilter gatewayLoadBalancerClientFilter(LoadBalancerClientFactory clientFactory,
GatewayLoadBalancerProperties properties, LoadBalancerProperties loadBalancerProperties) {
return new ReactiveLoadBalancerClientFilter(clientFactory, properties, loadBalancerProperties);
GatewayLoadBalancerProperties properties) {
return new ReactiveLoadBalancerClientFilter(clientFactory, properties);
}
@Bean
@ConditionalOnBean(ReactiveLoadBalancerClientFilter.class)
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.sticky-session.add-service-instance-cookie",
havingValue = "true")
@ConditionalOnBean({ ReactiveLoadBalancerClientFilter.class, LoadBalancerClientFactory.class })
@ConditionalOnMissingBean
@ConditionalOnEnabledGlobalFilter
public LoadBalancerServiceInstanceCookieFilter loadBalancerServiceInstanceCookieFilter(
LoadBalancerProperties loadBalancerProperties) {
return new LoadBalancerServiceInstanceCookieFilter(loadBalancerProperties);
LoadBalancerClientFactory loadBalancerClientFactory) {
return new LoadBalancerServiceInstanceCookieFilter(loadBalancerClientFactory);
}
}

View File

@@ -24,6 +24,7 @@ import reactor.core.publisher.Mono;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties;
import org.springframework.cloud.client.loadbalancer.Response;
import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
import org.springframework.core.Ordered;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpHeaders;
@@ -43,19 +44,37 @@ import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.G
*/
public class LoadBalancerServiceInstanceCookieFilter implements GlobalFilter, Ordered {
private final LoadBalancerProperties loadBalancerProperties;
private LoadBalancerProperties loadBalancerProperties;
private ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerClientFactory;
/**
* @deprecated in favour of
* {@link LoadBalancerServiceInstanceCookieFilter#LoadBalancerServiceInstanceCookieFilter(ReactiveLoadBalancer.Factory)}
*/
@Deprecated
public LoadBalancerServiceInstanceCookieFilter(LoadBalancerProperties loadBalancerProperties) {
this.loadBalancerProperties = loadBalancerProperties;
}
public LoadBalancerServiceInstanceCookieFilter(
ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerClientFactory) {
this.loadBalancerClientFactory = loadBalancerClientFactory;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
Response<ServiceInstance> serviceInstanceResponse = exchange.getAttribute(GATEWAY_LOADBALANCER_RESPONSE_ATTR);
if (serviceInstanceResponse == null || !serviceInstanceResponse.hasServer()) {
return chain.filter(exchange);
}
String instanceIdCookieName = loadBalancerProperties.getStickySession().getInstanceIdCookieName();
LoadBalancerProperties properties = loadBalancerClientFactory != null
? loadBalancerClientFactory.getProperties(serviceInstanceResponse.getServer().getServiceId())
: loadBalancerProperties;
if (!properties.getStickySession().isAddServiceInstanceCookie()) {
return chain.filter(exchange);
}
String instanceIdCookieName = properties.getStickySession().getInstanceIdCookieName();
if (!StringUtils.hasText(instanceIdCookieName)) {
return chain.filter(exchange);
}

View File

@@ -72,13 +72,21 @@ public class ReactiveLoadBalancerClientFilter implements GlobalFilter, Ordered {
private final GatewayLoadBalancerProperties properties;
private final LoadBalancerProperties loadBalancerProperties;
/**
* @deprecated in favour of
* {@link ReactiveLoadBalancerClientFilter#ReactiveLoadBalancerClientFilter(LoadBalancerClientFactory, GatewayLoadBalancerProperties)}
*/
@Deprecated
public ReactiveLoadBalancerClientFilter(LoadBalancerClientFactory clientFactory,
GatewayLoadBalancerProperties properties, LoadBalancerProperties loadBalancerProperties) {
this.clientFactory = clientFactory;
this.properties = properties;
this.loadBalancerProperties = loadBalancerProperties;
}
public ReactiveLoadBalancerClientFilter(LoadBalancerClientFactory clientFactory,
GatewayLoadBalancerProperties properties) {
this.clientFactory = clientFactory;
this.properties = properties;
}
@Override
@@ -105,8 +113,8 @@ public class ReactiveLoadBalancerClientFilter implements GlobalFilter, Ordered {
Set<LoadBalancerLifecycle> supportedLifecycleProcessors = LoadBalancerLifecycleValidator
.getSupportedLifecycleProcessors(clientFactory.getInstances(serviceId, LoadBalancerLifecycle.class),
RequestDataContext.class, ResponseData.class, ServiceInstance.class);
DefaultRequest<RequestDataContext> lbRequest = new DefaultRequest<>(new RequestDataContext(
new RequestData(exchange.getRequest()), getHint(serviceId, loadBalancerProperties.getHint())));
DefaultRequest<RequestDataContext> lbRequest = new DefaultRequest<>(
new RequestDataContext(new RequestData(exchange.getRequest()), getHint(serviceId)));
return choose(lbRequest, serviceId, supportedLifecycleProcessors).doOnNext(response -> {
if (!response.hasServer()) {
@@ -164,7 +172,9 @@ public class ReactiveLoadBalancerClientFilter implements GlobalFilter, Ordered {
return loadBalancer.choose(lbRequest);
}
private String getHint(String serviceId, Map<String, String> hints) {
private String getHint(String serviceId) {
LoadBalancerProperties loadBalancerProperties = clientFactory.getProperties(serviceId);
Map<String, String> hints = loadBalancerProperties.getHint();
String defaultHint = hints.getOrDefault("default", "default");
String hintPropertyValue = hints.get(serviceId);
return hintPropertyValue != null ? hintPropertyValue : defaultHint;

View File

@@ -16,6 +16,7 @@
package org.springframework.cloud.gateway.filter;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import reactor.core.publisher.Mono;
@@ -54,6 +55,11 @@ class LoadBalancerServiceInstanceCookieFilterTests {
private final LoadBalancerServiceInstanceCookieFilter filter = new LoadBalancerServiceInstanceCookieFilter(
properties);
@BeforeEach
void setUp() {
properties.getStickySession().setAddServiceInstanceCookie(true);
}
@Test
void shouldAddServiceInstanceCookieHeader() {
exchange.getAttributes().put(GATEWAY_LOADBALANCER_RESPONSE_ATTR,

View File

@@ -124,6 +124,7 @@ class ReactiveLoadBalancerClientFilterTests {
@Test
void shouldThrowExceptionWhenNoServiceInstanceIsFound() {
when(clientFactory.getProperties(any())).thenReturn(loadBalancerProperties);
assertThatExceptionOfType(NotFoundException.class).isThrownBy(() -> {
URI uri = UriComponentsBuilder.fromUriString("lb://myservice").build().toUri();
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, uri);
@@ -135,6 +136,7 @@ class ReactiveLoadBalancerClientFilterTests {
@SuppressWarnings("unchecked")
@Test
void shouldFilter() {
when(clientFactory.getProperties(any())).thenReturn(loadBalancerProperties);
URI url = UriComponentsBuilder.fromUriString("lb://myservice").build().toUri();
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, url);
@@ -166,6 +168,7 @@ class ReactiveLoadBalancerClientFilterTests {
@Test
void happyPath() {
when(clientFactory.getProperties(any())).thenReturn(loadBalancerProperties);
MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost/get?a=b").build();
URI lbUri = URI.create("lb://service1?a=b");
@@ -176,6 +179,7 @@ class ReactiveLoadBalancerClientFilterTests {
@Test
void noQueryParams() {
when(clientFactory.getProperties(any())).thenReturn(loadBalancerProperties);
MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost/get").build();
ServerWebExchange webExchange = testFilter(request, URI.create("lb://service1"));
@@ -185,6 +189,7 @@ class ReactiveLoadBalancerClientFilterTests {
@Test
void encodedParameters() {
when(clientFactory.getProperties(any())).thenReturn(loadBalancerProperties);
URI url = UriComponentsBuilder.fromUriString("http://localhost/get?a=b&c=d[]").buildAndExpand().encode()
.toUri();
@@ -207,6 +212,7 @@ class ReactiveLoadBalancerClientFilterTests {
@Test
void unencodedParameters() {
when(clientFactory.getProperties(any())).thenReturn(loadBalancerProperties);
URI url = URI.create("http://localhost/get?a=b&c=d[]");
MockServerHttpRequest request = MockServerHttpRequest.method(HttpMethod.GET, url).build();
@@ -227,6 +233,7 @@ class ReactiveLoadBalancerClientFilterTests {
@Test
void happyPathWithAttributeRatherThanScheme() {
when(clientFactory.getProperties(any())).thenReturn(loadBalancerProperties);
MockServerHttpRequest request = MockServerHttpRequest.get("ws://localhost/get?a=b").build();
URI lbUri = URI.create("ws://service1?a=b");
@@ -254,6 +261,7 @@ class ReactiveLoadBalancerClientFilterTests {
@Test
void shouldThrow4O4ExceptionWhenNoServiceInstanceIsFound() {
when(clientFactory.getProperties(any())).thenReturn(loadBalancerProperties);
URI uri = UriComponentsBuilder.fromUriString("lb://service1").build().toUri();
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, uri);
RoundRobinLoadBalancer loadBalancer = new RoundRobinLoadBalancer(
@@ -274,6 +282,8 @@ class ReactiveLoadBalancerClientFilterTests {
@SuppressWarnings("unchecked")
@Test
void shouldOverrideSchemeUsingIsSecure() {
when(clientFactory.getProperties(any())).thenReturn(loadBalancerProperties);
when(clientFactory.getProperties(any())).thenReturn(loadBalancerProperties);
URI url = UriComponentsBuilder.fromUriString("lb://myservice").build().toUri();
ServerWebExchange exchange = MockServerWebExchange
.from(MockServerHttpRequest.get("https://localhost:9999/mypath").build());
@@ -299,6 +309,7 @@ class ReactiveLoadBalancerClientFilterTests {
void shouldPassRequestToLoadBalancer() {
String hint = "test";
when(loadBalancerProperties.getHint()).thenReturn(buildHints(hint));
when(clientFactory.getProperties(any())).thenReturn(loadBalancerProperties);
MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost/get?a=b").build();
URI lbUri = URI.create("lb://service1?a=b");
ServerWebExchange serverWebExchange = mock(ServerWebExchange.class);
@@ -323,6 +334,7 @@ class ReactiveLoadBalancerClientFilterTests {
@SuppressWarnings({ "unchecked", "rawtypes" })
@Test
void loadBalancerLifecycleCallbacksExecutedForSuccess() {
when(clientFactory.getProperties(any())).thenReturn(loadBalancerProperties);
LoadBalancerLifecycle lifecycleProcessor = mock(LoadBalancerLifecycle.class);
ServiceInstance serviceInstance = new DefaultServiceInstance("myservice1", "myservice", "localhost", 8080,
false);
@@ -342,6 +354,7 @@ class ReactiveLoadBalancerClientFilterTests {
@SuppressWarnings({ "unchecked", "rawtypes" })
@Test
void loadBalancerLifecycleCallbacksExecutedForDiscard() {
when(clientFactory.getProperties(any())).thenReturn(loadBalancerProperties);
LoadBalancerLifecycle lifecycleProcessor = mock(LoadBalancerLifecycle.class);
ServiceInstance serviceInstance = null;
ServerWebExchange serverWebExchange = mockExchange(serviceInstance, lifecycleProcessor, false);
@@ -358,6 +371,7 @@ class ReactiveLoadBalancerClientFilterTests {
@SuppressWarnings({ "unchecked", "rawtypes" })
@Test
void loadBalancerLifecycleCallbacksExecutedForFailed() {
when(clientFactory.getProperties(any())).thenReturn(loadBalancerProperties);
LoadBalancerLifecycle lifecycleProcessor = mock(LoadBalancerLifecycle.class);
ServiceInstance serviceInstance = new DefaultServiceInstance("myservice1", "myservice", "localhost", 8080,
false);