Initial support for DiscoveryClientEndpoint and LoadBalancerEndpoint
Supplies actuator endpoints for commons methods on these interfaces.
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2013-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.discovery.endpoint;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Selector;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient;
|
||||
|
||||
@Endpoint(id = "discovery")
|
||||
public class DiscoveryClientEndpoint {
|
||||
|
||||
private final ReactiveDiscoveryClient discoveryClient;
|
||||
|
||||
public DiscoveryClientEndpoint(ReactiveDiscoveryClient discoveryClient) {
|
||||
this.discoveryClient = discoveryClient;
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
public Mono<List<String>> services() {
|
||||
return this.discoveryClient.getServices().collectList();
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
public Mono<List<ServiceInstance>> instances(@Selector String serviceId) {
|
||||
Flux<ServiceInstance> instances = this.discoveryClient.getInstances(serviceId);
|
||||
return instances.collectList();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2013-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.discovery.endpoint;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient;
|
||||
import org.springframework.cloud.client.discovery.composite.CompositeDiscoveryClientAutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(Endpoint.class)
|
||||
@AutoConfigureAfter(CompositeDiscoveryClientAutoConfiguration.class)
|
||||
public class DiscoveryClientEndpointAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnAvailableEndpoint
|
||||
@ConditionalOnBean(ReactiveDiscoveryClient.class)
|
||||
public DiscoveryClientEndpoint discoveryClientEndpoint(
|
||||
ReactiveDiscoveryClient discoveryClient) {
|
||||
return new DiscoveryClientEndpoint(discoveryClient);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -31,8 +31,6 @@ import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.discovery.DiscoveryClient;
|
||||
import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
|
||||
/**
|
||||
* Properties to hold the details of a {@link ReactiveDiscoveryClient} service instance
|
||||
* for a given service. It also holds the user-configurable order that will be used to
|
||||
@@ -57,7 +55,10 @@ public class SimpleReactiveDiscoveryProperties {
|
||||
private int order = DiscoveryClient.DEFAULT_ORDER;
|
||||
|
||||
public Flux<ServiceInstance> getInstances(String service) {
|
||||
return Flux.fromIterable(instances.getOrDefault(service, emptyList()));
|
||||
if (instances.containsKey(service)) {
|
||||
return Flux.fromIterable(instances.get(service));
|
||||
}
|
||||
return Flux.empty();
|
||||
}
|
||||
|
||||
Map<String, List<SimpleServiceInstance>> getInstances() {
|
||||
|
||||
@@ -16,32 +16,45 @@
|
||||
|
||||
package org.springframework.cloud.client.loadbalancer.reactive;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint;
|
||||
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.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.endpoint.LoadBalancerEndpoint;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
/**
|
||||
* @deprecated in favour of {@link ReactorLoadBalancerClientAutoConfiguration}
|
||||
* @author Spencer Gibb
|
||||
* @author Olga Maciaszek-Sharma
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(WebClient.class)
|
||||
@ConditionalOnBean(LoadBalancerClient.class)
|
||||
@AutoConfigureAfter(ReactorLoadBalancerClientAutoConfiguration.class)
|
||||
@ConditionalOnMissingBean(ReactorLoadBalancerExchangeFilterFunction.class)
|
||||
@Deprecated
|
||||
public class ReactiveLoadBalancerAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnClass(
|
||||
name = "org.springframework.web.reactive.function.client.WebClient")
|
||||
@ConditionalOnMissingBean(ReactorLoadBalancerExchangeFilterFunction.class)
|
||||
@ConditionalOnBean(LoadBalancerClient.class)
|
||||
@Deprecated
|
||||
public LoadBalancerExchangeFilterFunction loadBalancerExchangeFilterFunction(
|
||||
LoadBalancerClient client) {
|
||||
return new LoadBalancerExchangeFilterFunction(client);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnAvailableEndpoint
|
||||
@ConditionalOnClass(
|
||||
name = "org.springframework.boot.actuate.endpoint.annotation.Endpoint")
|
||||
@ConditionalOnBean(ReactiveLoadBalancer.Factory.class)
|
||||
public LoadBalancerEndpoint loadBalancerEndpoint(
|
||||
ObjectProvider<ReactiveLoadBalancer.Factory<ServiceInstance>> clientFactory) {
|
||||
return new LoadBalancerEndpoint(clientFactory);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.client.loadbalancer.reactive.endpoint;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Selector;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer.Factory;
|
||||
|
||||
@Endpoint(id = "loadbalancer")
|
||||
public class LoadBalancerEndpoint {
|
||||
|
||||
private final ObjectProvider<Factory<ServiceInstance>> clientFactory;
|
||||
|
||||
public LoadBalancerEndpoint(ObjectProvider<Factory<ServiceInstance>> clientFactory) {
|
||||
this.clientFactory = clientFactory;
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
public Mono<ServiceInstance> choose(@Selector String serviceId,
|
||||
@Selector String operation) {
|
||||
if (!"choose".equalsIgnoreCase(operation)) {
|
||||
return Mono.error(
|
||||
new IllegalArgumentException("Unknown operation: " + operation));
|
||||
}
|
||||
Factory<ServiceInstance> factory = this.clientFactory.getIfAvailable();
|
||||
if (factory == null) {
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
ReactiveLoadBalancer<ServiceInstance> loadBalancer = factory
|
||||
.getInstance(serviceId);
|
||||
return Mono.from(loadBalancer.choose()).flatMap(response -> {
|
||||
if (response.hasServer()) {
|
||||
return Mono.just(response.getServer());
|
||||
}
|
||||
return Mono.empty();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,6 +4,7 @@ org.springframework.cloud.client.CommonsClientAutoConfiguration,\
|
||||
org.springframework.cloud.client.ReactiveCommonsClientAutoConfiguration,\
|
||||
org.springframework.cloud.client.discovery.composite.CompositeDiscoveryClientAutoConfiguration,\
|
||||
org.springframework.cloud.client.discovery.composite.reactive.ReactiveCompositeDiscoveryClientAutoConfiguration,\
|
||||
org.springframework.cloud.client.discovery.endpoint.DiscoveryClientEndpointAutoConfiguration,\
|
||||
org.springframework.cloud.client.discovery.noop.NoopDiscoveryClientAutoConfiguration,\
|
||||
org.springframework.cloud.client.discovery.simple.SimpleDiscoveryClientAutoConfiguration,\
|
||||
org.springframework.cloud.client.discovery.simple.reactive.SimpleReactiveDiscoveryClientAutoConfiguration,\
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2013-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.discovery.endpoint;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
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.discovery.simple.reactive.SimpleReactiveDiscoveryProperties.SimpleServiceInstance;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(properties = { "management.endpoints.web.exposure.include=discovery" },
|
||||
webEnvironment = RANDOM_PORT)
|
||||
@ActiveProfiles("lbendpoint")
|
||||
public class DiscoveryClientEndpointTests {
|
||||
|
||||
@Autowired
|
||||
private WebTestClient client;
|
||||
|
||||
@Test
|
||||
public void servicesWorks() {
|
||||
client.get().uri("/actuator/discovery").exchange().expectBody(List.class)
|
||||
.consumeWith(result -> {
|
||||
List<String> body = result.getResponseBody();
|
||||
assertThat(body).contains("myservice", "anotherservice",
|
||||
"thirdservice");
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void instancesWorks() {
|
||||
client.get().uri("/actuator/discovery/myservice").exchange()
|
||||
.expectBodyList(SimpleServiceInstance.class).consumeWith(result -> {
|
||||
List body = result.getResponseBody();
|
||||
assertThat(body).isNotEmpty();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noInstancesWorks() {
|
||||
client.get().uri("/actuator/discovery/unknownservice").exchange()
|
||||
.expectBodyList(SimpleServiceInstance.class).consumeWith(result -> {
|
||||
List body = result.getResponseBody();
|
||||
assertThat(body).isEmpty();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@SpringBootConfiguration
|
||||
@EnableAutoConfiguration
|
||||
protected static class TestConfiguration {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.client.loadbalancer.reactive.endpoint;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
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.ReactiveDiscoveryClient;
|
||||
import org.springframework.cloud.client.discovery.simple.reactive.SimpleReactiveDiscoveryProperties.SimpleServiceInstance;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.CompletionContext;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.Response;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(properties = { "management.endpoints.web.exposure.include=loadbalancer" },
|
||||
webEnvironment = RANDOM_PORT)
|
||||
@ActiveProfiles("lbendpoint")
|
||||
public class LoadBalancerEndpointTests {
|
||||
|
||||
private static final Random random = new Random();
|
||||
|
||||
@Autowired
|
||||
private WebTestClient client;
|
||||
|
||||
@Test
|
||||
public void chooseWorks() {
|
||||
client.get().uri("/actuator/loadbalancer/myservice/choose").exchange()
|
||||
.expectBody(SimpleServiceInstance.class).consumeWith(result -> {
|
||||
ServiceInstance body = result.getResponseBody();
|
||||
assertThat(body).isNotNull();
|
||||
assertThat(body.getHost()).isNotEmpty();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void chooseWorksWithoutInstances() {
|
||||
client.get().uri("/actuator/loadbalancer/unknownservice/choose").exchange()
|
||||
.expectBody(SimpleServiceInstance.class).consumeWith(result -> {
|
||||
ServiceInstance body = result.getResponseBody();
|
||||
assertThat(body).isNull();
|
||||
// TODO: web response 404?
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
static Mono<Response<ServiceInstance>> choose(ReactiveDiscoveryClient discoveryClient,
|
||||
String serviceId) {
|
||||
return discoveryClient.getInstances(serviceId).collectList().map(instances -> {
|
||||
if (instances.isEmpty()) {
|
||||
return new InstanceResponse(null);
|
||||
}
|
||||
int idx = random.nextInt(instances.size());
|
||||
ServiceInstance instance = instances.get(idx);
|
||||
return new InstanceResponse(instance);
|
||||
});
|
||||
}
|
||||
|
||||
private static final class InstanceResponse implements Response<ServiceInstance> {
|
||||
|
||||
private final ServiceInstance instance;
|
||||
|
||||
private InstanceResponse(ServiceInstance instance) {
|
||||
this.instance = instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasServer() {
|
||||
return this.instance != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServiceInstance getServer() {
|
||||
return this.instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(CompletionContext completionContext) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@SpringBootConfiguration
|
||||
@EnableAutoConfiguration
|
||||
protected static class TestConfiguration {
|
||||
|
||||
@Bean
|
||||
public ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory(
|
||||
ReactiveDiscoveryClient discoveryClient) {
|
||||
return serviceId -> (ReactiveLoadBalancer<ServiceInstance>) request -> choose(
|
||||
discoveryClient, serviceId);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
spring:
|
||||
cloud:
|
||||
discovery:
|
||||
client:
|
||||
simple:
|
||||
instances:
|
||||
myservice:
|
||||
- service-id: myservice
|
||||
uri: http://ahost:8080
|
||||
- service-id: myservice
|
||||
uri: http://chost:8081
|
||||
- service-id: myservice
|
||||
uri: https://bhostsecure:8082
|
||||
anotherservice:
|
||||
- service-id: myservice
|
||||
uri: http://dhost:8180
|
||||
- service-id: myservice
|
||||
uri: https://fhost:8181
|
||||
- service-id: myservice
|
||||
uri: http://ehost:8182
|
||||
thirdservice:
|
||||
- service-id: myservice
|
||||
uri: http://ghost:8280
|
||||
- service-id: myservice
|
||||
uri: http://hhost:8281
|
||||
- service-id: myservice
|
||||
uri: http://ihost:8282
|
||||
Reference in New Issue
Block a user