Adds API and implementation of ReactiveLoadBalancer

API lives in spring-cloud-commons module and implementation in
spring-cloud-loadbalancer.
This commit is contained in:
Spencer Gibb
2018-08-15 18:45:53 -04:00
parent 53f1f0bc3c
commit 1fa513194b
30 changed files with 1454 additions and 1 deletions

View File

@@ -0,0 +1,144 @@
/*
* Copyright 2013-2018 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.loadbalancer.core;
import java.util.Arrays;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
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.cache.annotation.EnableCaching;
import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.reactive.CompletionContext;
import org.springframework.cloud.client.loadbalancer.reactive.CompletionContext.Status;
import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
import org.springframework.cloud.client.loadbalancer.reactive.Response;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.cloud.loadbalancer.support.ServiceInstanceSuppliers;
import org.springframework.context.annotation.Bean;
import org.springframework.core.ResolvableType;
import org.springframework.core.env.Environment;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Spencer Gibb
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class LoadBalancerTest {
@Autowired
private LoadBalancerClientFactory clientFactory;
@Test
public void roundRobbinLoadbalancerWorks() {
ReactiveLoadBalancer<ServiceInstance> reactiveLoadBalancer = this.clientFactory
.getInstance("myservice", ReactiveLoadBalancer.class, ServiceInstance.class);
assertThat(reactiveLoadBalancer).isInstanceOf(RoundRobinLoadBalancer.class);
assertThat(reactiveLoadBalancer).isInstanceOf(ReactorLoadBalancer.class);
ReactorLoadBalancer<ServiceInstance> loadBalancer = (ReactorLoadBalancer<ServiceInstance>) reactiveLoadBalancer;
//order dependent on seedPosition -1 of RoundRobinLoadBalancer
List<String> hosts = Arrays.asList("a.host", "c.host", "b.host-secure", "a.host");
assertLoadBalancer(loadBalancer, hosts);
}
private void assertLoadBalancer(ReactorLoadBalancer<ServiceInstance> loadBalancer, List<String> hosts) {
for (String host : hosts) {
Mono<Response<ServiceInstance>> source = loadBalancer.choose();
StepVerifier.create(source).consumeNextWith(response -> {
assertThat(response).isNotNull();
assertThat(response.hasServer()).isTrue();
ServiceInstance instance = response.getServer();
assertThat(instance).isNotNull();
assertThat(instance.getHost())
.as("instance host is incorrent %s", host)
.isEqualTo(host);
if (host.contains("secure")) {
assertThat(instance.isSecure()).isTrue();
} else {
assertThat(instance.isSecure()).isFalse();
}
response.onComplete(new CompletionContext(Status.SUCCESSS));
}).verifyComplete();
}
}
@Test
public void emptyHosts() {
ResolvableType type = ResolvableType.forClassWithGenerics(ReactorLoadBalancer.class, ServiceInstance.class);
ReactorLoadBalancer<ServiceInstance> loadBalancer = this.clientFactory.getInstance("unknownservice", type);
assertThat(loadBalancer).isInstanceOf(RoundRobinLoadBalancer.class);
Mono<Response<ServiceInstance>> source = loadBalancer.choose();
StepVerifier.create(source).consumeNextWith(response -> {
assertThat(response).isNotNull();
assertThat(response.hasServer()).isFalse();
}).verifyComplete();
}
@Test
public void staticConfigurationWorks() {
String serviceId = "test1";
RoundRobinLoadBalancer loadBalancer = new RoundRobinLoadBalancer(serviceId,
ServiceInstanceSuppliers.toProvider(serviceId, instance(serviceId, "1.host", false),
instance(serviceId, "2.host-secure", true)),
-1);
assertLoadBalancer(loadBalancer, Arrays.asList("1.host", "2.host-secure"));
}
private DefaultServiceInstance instance(String serviceId, String host, boolean secure) {
return new DefaultServiceInstance(serviceId, host, 80, secure);
}
@EnableAutoConfiguration
@SpringBootConfiguration
@LoadBalancerClients({
@LoadBalancerClient(name = "myservice", configuration = MyServiceConfig.class),
@LoadBalancerClient(name = "unknownservice", configuration = MyServiceConfig.class),
})
@EnableCaching
protected static class Config { }
protected static class MyServiceConfig {
@Bean
public RoundRobinLoadBalancer roundRobinContextLoadBalancer(LoadBalancerClientFactory clientFactory, Environment env) {
String serviceId = clientFactory.getName(env);
return new RoundRobinLoadBalancer(serviceId,
clientFactory.getLazyProvider(serviceId, ServiceInstanceSupplier.class),
-1);
}
}
}

View File

@@ -0,0 +1,36 @@
spring:
cloud:
discovery:
client:
simple:
instances:
myservice:
-
service-id: myservice
uri: http://a.host
-
service-id: myservice
uri: http://c.host
-
service-id: myservice
uri: https://b.host-secure
anotherservice:
-
service-id: myservice
uri: http://d.host
-
service-id: myservice
uri: http://f.host
-
service-id: myservice
uri: https://e.host
thirdservice:
-
service-id: myservice
uri: http://g.host
-
service-id: myservice
uri: http://h.host
-
service-id: myservice
uri: https://i.host