Added support for reactive service discovery client
This commit is contained in:
@@ -241,10 +241,14 @@ Patterns such as service discovery, load balancing, and circuit breakers lend th
|
||||
=== @EnableDiscoveryClient
|
||||
|
||||
Spring Cloud Commons provides the `@EnableDiscoveryClient` annotation.
|
||||
This looks for implementations of the `DiscoveryClient` interface with `META-INF/spring.factories`.
|
||||
Implementations of the Discovery Client add a configuration class to `spring.factories` under the `org.springframework.cloud.client.discovery.EnableDiscoveryClient` key.
|
||||
This looks for implementations of the `DiscoveryClient` and `ReactiveDiscoveryClient` interfaces with `META-INF/spring.factories`.
|
||||
Implementations of the discovery client add a configuration class to `spring.factories` under the `org.springframework.cloud.client.discovery.EnableDiscoveryClient` key.
|
||||
Examples of `DiscoveryClient` implementations include https://cloud.spring.io/spring-cloud-netflix/[Spring Cloud Netflix Eureka], https://cloud.spring.io/spring-cloud-consul/[Spring Cloud Consul Discovery], and https://cloud.spring.io/spring-cloud-zookeeper/[Spring Cloud Zookeeper Discovery].
|
||||
|
||||
Spring Cloud will provide both the blocking and reactive service discovery clients by default.
|
||||
You can disable the blocking and/or reactive clients easily by setting `spring.cloud.discovery.blocking.enabled=false` or `spring.cloud.discovery.reactive.enabled=false`.
|
||||
To completely disable service discovery you just need to set `spring.cloud.discovery.enabled=false`.
|
||||
|
||||
By default, implementations of `DiscoveryClient` auto-register the local Spring Boot server with the remote discovery server.
|
||||
This behavior can be disabled by setting `autoRegister=false` in `@EnableDiscoveryClient`.
|
||||
|
||||
|
||||
@@ -163,5 +163,10 @@
|
||||
<artifactId>spring-cloud-test-support</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@@ -47,6 +47,7 @@ import org.springframework.context.annotation.Configuration;
|
||||
*
|
||||
* @author Spencer Gibb
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @author Tim Ysewyn
|
||||
*/
|
||||
@Configuration
|
||||
@AutoConfigureOrder(0)
|
||||
@@ -57,6 +58,7 @@ public class CommonsClientAutoConfiguration {
|
||||
@ConditionalOnClass(HealthIndicator.class)
|
||||
@ConditionalOnBean(DiscoveryClient.class)
|
||||
@ConditionalOnDiscoveryEnabled
|
||||
@ConditionalOnBlockingDiscoveryEnabled
|
||||
protected static class DiscoveryLoadBalancerConfiguration {
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2019-2019 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;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
|
||||
/**
|
||||
* Provides a more succinct conditional
|
||||
* <code>spring.cloud.discovery.blocking.enabled</code>.
|
||||
*
|
||||
* @author Tim Ysewyn
|
||||
* @since 2.2.0
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Inherited
|
||||
@ConditionalOnProperty(value = "spring.cloud.discovery.blocking.enabled",
|
||||
matchIfMissing = true)
|
||||
public @interface ConditionalOnBlockingDiscoveryEnabled {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2019-2019 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;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
/**
|
||||
* Provides a more succinct conditional
|
||||
* <code>spring.cloud.discovery.reactive.enabled</code>. Also takes into account whether
|
||||
* or not `WebClient` is on the classpath.
|
||||
*
|
||||
* @author Tim Ysewyn
|
||||
* @since 2.2.0
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Inherited
|
||||
@ConditionalOnClass(WebClient.class)
|
||||
@ConditionalOnProperty(value = "spring.cloud.discovery.reactive.enabled",
|
||||
matchIfMissing = true)
|
||||
public @interface ConditionalOnReactiveDiscoveryEnabled {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2012-2019 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;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.boot.actuate.health.HealthIndicator;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.client.actuator.HasFeatures;
|
||||
import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient;
|
||||
import org.springframework.cloud.client.discovery.health.DiscoveryClientHealthIndicatorProperties;
|
||||
import org.springframework.cloud.client.discovery.health.reactive.ReactiveDiscoveryCompositeHealthContributor;
|
||||
import org.springframework.cloud.client.discovery.health.reactive.ReactiveDiscoveryHealthIndicator;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* {@link EnableAutoConfiguration Auto-configuration} for reactive Spring Cloud Commons
|
||||
* Client.
|
||||
*
|
||||
* @author Tim Ysewyn
|
||||
* @since 2.2.0
|
||||
*/
|
||||
@Configuration
|
||||
@AutoConfigureOrder(0)
|
||||
public class ReactiveCommonsClientAutoConfiguration {
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(DiscoveryClientHealthIndicatorProperties.class)
|
||||
@ConditionalOnClass(HealthIndicator.class)
|
||||
@ConditionalOnBean(ReactiveDiscoveryClient.class)
|
||||
@ConditionalOnDiscoveryEnabled
|
||||
@ConditionalOnReactiveDiscoveryEnabled
|
||||
protected static class ReactiveDiscoveryLoadBalancerConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(
|
||||
value = "spring.cloud.discovery.client.composite-indicator.enabled",
|
||||
matchIfMissing = true)
|
||||
@ConditionalOnBean({ ReactiveDiscoveryHealthIndicator.class })
|
||||
public ReactiveDiscoveryCompositeHealthContributor reactiveDiscoveryClients(
|
||||
Collection<ReactiveDiscoveryHealthIndicator> indicators) {
|
||||
return new ReactiveDiscoveryCompositeHealthContributor(indicators);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public HasFeatures reactiveCommonsFeatures() {
|
||||
return HasFeatures.abstractFeatures(ReactiveDiscoveryClient.class,
|
||||
ReactiveLoadBalancer.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2012-2019 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;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.core.Ordered;
|
||||
|
||||
/**
|
||||
* Represents read operations commonly available to discovery services such as Netflix
|
||||
* Eureka or consul.io.
|
||||
*
|
||||
* @author Tim Ysewyn
|
||||
*/
|
||||
public interface ReactiveDiscoveryClient extends Ordered {
|
||||
|
||||
/**
|
||||
* Default order of the discovery client.
|
||||
*/
|
||||
int DEFAULT_ORDER = 0;
|
||||
|
||||
/**
|
||||
* A human-readable description of the implementation, used in HealthIndicator.
|
||||
* @return The description.
|
||||
*/
|
||||
String description();
|
||||
|
||||
/**
|
||||
* Gets all ServiceInstances associated with a particular serviceId.
|
||||
* @param serviceId The serviceId to query.
|
||||
* @return A List of ServiceInstance.
|
||||
*/
|
||||
Flux<ServiceInstance> getInstances(String serviceId);
|
||||
|
||||
/**
|
||||
* @return All known service IDs.
|
||||
*/
|
||||
Flux<String> getServices();
|
||||
|
||||
/**
|
||||
* Default implementation for getting order of discovery clients.
|
||||
* @return order
|
||||
*/
|
||||
@Override
|
||||
default int getOrder() {
|
||||
return DEFAULT_ORDER;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2012-2019 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.composite.reactive;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient;
|
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
|
||||
/**
|
||||
* A {@link ReactiveDiscoveryClient} that is composed of other discovery clients and
|
||||
* delegates calls to each of them in order.
|
||||
*
|
||||
* @author Tim Ysewyn
|
||||
*/
|
||||
public class ReactiveCompositeDiscoveryClient implements ReactiveDiscoveryClient {
|
||||
|
||||
private final List<ReactiveDiscoveryClient> discoveryClients;
|
||||
|
||||
public ReactiveCompositeDiscoveryClient(
|
||||
List<ReactiveDiscoveryClient> discoveryClients) {
|
||||
AnnotationAwareOrderComparator.sort(discoveryClients);
|
||||
this.discoveryClients = discoveryClients;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return "Composite Reactive Discovery Client";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<ServiceInstance> getInstances(String serviceId) {
|
||||
if (discoveryClients == null || discoveryClients.isEmpty()) {
|
||||
return Flux.empty();
|
||||
}
|
||||
List<Flux<ServiceInstance>> serviceInstances = new ArrayList<>();
|
||||
for (ReactiveDiscoveryClient discoveryClient : discoveryClients) {
|
||||
serviceInstances.add(discoveryClient.getInstances(serviceId));
|
||||
}
|
||||
return Flux.first(serviceInstances);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<String> getServices() {
|
||||
if (discoveryClients == null || discoveryClients.isEmpty()) {
|
||||
return Flux.empty();
|
||||
}
|
||||
return Flux.fromIterable(discoveryClients)
|
||||
.flatMap(ReactiveDiscoveryClient::getServices);
|
||||
}
|
||||
|
||||
List<ReactiveDiscoveryClient> getDiscoveryClients() {
|
||||
return discoveryClients;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2012-2019 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.composite.reactive;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.cloud.client.ConditionalOnDiscoveryEnabled;
|
||||
import org.springframework.cloud.client.ConditionalOnReactiveDiscoveryEnabled;
|
||||
import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient;
|
||||
import org.springframework.cloud.client.discovery.simple.reactive.SimpleReactiveDiscoveryClientAutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
|
||||
/**
|
||||
* Auto-configuration for reactive composite discovery client.
|
||||
*
|
||||
* @author Tim Ysewyn
|
||||
* @since 2.2.0
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnDiscoveryEnabled
|
||||
@ConditionalOnReactiveDiscoveryEnabled
|
||||
@AutoConfigureBefore(SimpleReactiveDiscoveryClientAutoConfiguration.class)
|
||||
public class ReactiveCompositeDiscoveryClientAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
public ReactiveCompositeDiscoveryClient reactiveCompositeDiscoveryClient(
|
||||
List<ReactiveDiscoveryClient> discoveryClients) {
|
||||
return new ReactiveCompositeDiscoveryClient(discoveryClients);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright 2012-2019 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.health.reactive;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.boot.actuate.health.Health;
|
||||
import org.springframework.boot.actuate.health.Status;
|
||||
import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient;
|
||||
import org.springframework.cloud.client.discovery.event.InstanceRegisteredEvent;
|
||||
import org.springframework.cloud.client.discovery.health.DiscoveryClientHealthIndicatorProperties;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.core.Ordered;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
|
||||
/**
|
||||
* A health indicator which indicates whether or not the discovery client has been
|
||||
* initialized.
|
||||
*
|
||||
* @author Tim Ysewyn
|
||||
*/
|
||||
public class ReactiveDiscoveryClientHealthIndicator
|
||||
implements ReactiveDiscoveryHealthIndicator, Ordered,
|
||||
ApplicationListener<InstanceRegisteredEvent<?>> {
|
||||
|
||||
private final ReactiveDiscoveryClient discoveryClient;
|
||||
|
||||
private final DiscoveryClientHealthIndicatorProperties properties;
|
||||
|
||||
private final Log log = LogFactory
|
||||
.getLog(ReactiveDiscoveryClientHealthIndicator.class);
|
||||
|
||||
private AtomicBoolean discoveryInitialized = new AtomicBoolean(false);
|
||||
|
||||
private int order = Ordered.HIGHEST_PRECEDENCE;
|
||||
|
||||
public ReactiveDiscoveryClientHealthIndicator(ReactiveDiscoveryClient discoveryClient,
|
||||
DiscoveryClientHealthIndicatorProperties properties) {
|
||||
this.discoveryClient = discoveryClient;
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(InstanceRegisteredEvent<?> event) {
|
||||
if (this.discoveryInitialized.compareAndSet(false, true)) {
|
||||
this.log.debug("Discovery Client has been initialized");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Health> health() {
|
||||
if (this.discoveryInitialized.get()) {
|
||||
return doHealthCheck();
|
||||
}
|
||||
else {
|
||||
return Mono.just(Health.status(new Status(Status.UNKNOWN.getCode(),
|
||||
"Discovery Client not initialized")).build());
|
||||
}
|
||||
}
|
||||
|
||||
private Mono<Health> doHealthCheck() {
|
||||
// @formatter:off
|
||||
return Mono.justOrEmpty(this.discoveryClient)
|
||||
.flatMapMany(ReactiveDiscoveryClient::getServices)
|
||||
.collectList()
|
||||
.defaultIfEmpty(emptyList())
|
||||
.map(services -> {
|
||||
ReactiveDiscoveryClient client = this.discoveryClient;
|
||||
String description = (this.properties.isIncludeDescription())
|
||||
? client.description() : "";
|
||||
return Health.status(new Status("UP", description))
|
||||
.withDetail("services", services).build();
|
||||
})
|
||||
.onErrorResume(exception -> {
|
||||
this.log.error("Error", exception);
|
||||
return Mono.just(Health.down().withException(exception).build());
|
||||
});
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return discoveryClient.description();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return this.order;
|
||||
}
|
||||
|
||||
public void setOrder(int order) {
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2012-2019 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.health.reactive;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.boot.actuate.health.CompositeReactiveHealthContributor;
|
||||
import org.springframework.boot.actuate.health.NamedContributor;
|
||||
import org.springframework.boot.actuate.health.ReactiveHealthContributor;
|
||||
import org.springframework.boot.actuate.health.ReactiveHealthIndicator;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A composite health contributor specific to a reactive discovery client implementation.
|
||||
*
|
||||
* @author Tim Ysewyn
|
||||
*/
|
||||
public class ReactiveDiscoveryCompositeHealthContributor
|
||||
implements CompositeReactiveHealthContributor {
|
||||
|
||||
private Map<String, ReactiveDiscoveryHealthIndicator> indicators;
|
||||
|
||||
public ReactiveDiscoveryCompositeHealthContributor(
|
||||
Collection<ReactiveDiscoveryHealthIndicator> indicators) {
|
||||
Assert.notNull(indicators, "'indicators' must not be null");
|
||||
this.indicators = indicators.stream().collect(Collectors
|
||||
.toMap(ReactiveDiscoveryHealthIndicator::getName, Function.identity()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReactiveHealthContributor getContributor(String name) {
|
||||
return asHealthIndicator(indicators.get(name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<NamedContributor<ReactiveHealthContributor>> iterator() {
|
||||
return indicators.values().stream().map(this::asNamedContributor).iterator();
|
||||
}
|
||||
|
||||
private NamedContributor<ReactiveHealthContributor> asNamedContributor(
|
||||
ReactiveDiscoveryHealthIndicator indicator) {
|
||||
return new NamedContributor<ReactiveHealthContributor>() {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return indicator.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReactiveHealthContributor getContributor() {
|
||||
return asHealthIndicator(indicator);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
private ReactiveHealthIndicator asHealthIndicator(
|
||||
ReactiveDiscoveryHealthIndicator indicator) {
|
||||
return (indicator != null) ? indicator::health : null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2012-2019 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.health.reactive;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.boot.actuate.health.Health;
|
||||
|
||||
/**
|
||||
* A health indicator interface specific to a reactive discovery client implementation.
|
||||
*
|
||||
* @author Tim Ysewyn
|
||||
*/
|
||||
public interface ReactiveDiscoveryHealthIndicator {
|
||||
|
||||
/**
|
||||
* Provide the name of health indicator.
|
||||
* @return a {@link String} that provides the name of health indicator, usually the
|
||||
* name of the implementation.
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* Provide the indicator of health.
|
||||
* @return a {@link Mono} that provides the {@link Health}
|
||||
*/
|
||||
Mono<Health> health();
|
||||
|
||||
}
|
||||
@@ -23,10 +23,10 @@ import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.boot.web.context.WebServerInitializedEvent;
|
||||
import org.springframework.cloud.client.CommonsClientAutoConfiguration;
|
||||
import org.springframework.cloud.client.discovery.DiscoveryClient;
|
||||
import org.springframework.cloud.client.discovery.noop.NoopDiscoveryClientAutoConfiguration;
|
||||
import org.springframework.cloud.commons.util.InetUtils;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@@ -38,18 +38,15 @@ import org.springframework.core.annotation.Order;
|
||||
*
|
||||
* @author Biju Kunjummen
|
||||
*/
|
||||
|
||||
@Configuration
|
||||
@AutoConfigureBefore(NoopDiscoveryClientAutoConfiguration.class)
|
||||
@AutoConfigureBefore({ NoopDiscoveryClientAutoConfiguration.class,
|
||||
CommonsClientAutoConfiguration.class })
|
||||
public class SimpleDiscoveryClientAutoConfiguration
|
||||
implements ApplicationListener<WebServerInitializedEvent> {
|
||||
|
||||
@Autowired(required = false)
|
||||
private ServerProperties server;
|
||||
|
||||
@Autowired
|
||||
private ApplicationContext context;
|
||||
|
||||
@Value("${spring.application.name:application}")
|
||||
private String serviceId;
|
||||
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2012-2019 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.simple.reactive;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient;
|
||||
|
||||
/**
|
||||
* A {@link ReactiveDiscoveryClient} that will use the properties file as a source of
|
||||
* service instances.
|
||||
*
|
||||
* @author Tim Ysewyn
|
||||
*/
|
||||
public class SimpleReactiveDiscoveryClient implements ReactiveDiscoveryClient {
|
||||
|
||||
private SimpleReactiveDiscoveryProperties simpleDiscoveryProperties;
|
||||
|
||||
public SimpleReactiveDiscoveryClient(
|
||||
SimpleReactiveDiscoveryProperties simpleDiscoveryProperties) {
|
||||
this.simpleDiscoveryProperties = simpleDiscoveryProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return "Simple Reactive Discovery Client";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<ServiceInstance> getInstances(String serviceId) {
|
||||
return this.simpleDiscoveryProperties.getInstances(serviceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<String> getServices() {
|
||||
return Flux.fromIterable(this.simpleDiscoveryProperties.getInstances().keySet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return this.simpleDiscoveryProperties.getOrder();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright 2012-2019 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.simple.reactive;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.web.context.WebServerInitializedEvent;
|
||||
import org.springframework.cloud.client.ConditionalOnDiscoveryEnabled;
|
||||
import org.springframework.cloud.client.ConditionalOnReactiveDiscoveryEnabled;
|
||||
import org.springframework.cloud.client.ReactiveCommonsClientAutoConfiguration;
|
||||
import org.springframework.cloud.client.discovery.health.DiscoveryClientHealthIndicatorProperties;
|
||||
import org.springframework.cloud.client.discovery.health.reactive.ReactiveDiscoveryClientHealthIndicator;
|
||||
import org.springframework.cloud.commons.util.InetUtils;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.annotation.Order;
|
||||
|
||||
/**
|
||||
* Spring Boot auto-configuration for simple properties-based reactive discovery client.
|
||||
*
|
||||
* @author Tim Ysewyn
|
||||
* @since 2.2.0
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnDiscoveryEnabled
|
||||
@ConditionalOnReactiveDiscoveryEnabled
|
||||
@EnableConfigurationProperties(DiscoveryClientHealthIndicatorProperties.class)
|
||||
@AutoConfigureBefore(ReactiveCommonsClientAutoConfiguration.class)
|
||||
public class SimpleReactiveDiscoveryClientAutoConfiguration
|
||||
implements ApplicationListener<WebServerInitializedEvent> {
|
||||
|
||||
@Autowired(required = false)
|
||||
private ServerProperties server;
|
||||
|
||||
@Value("${spring.application.name:application}")
|
||||
private String serviceId;
|
||||
|
||||
@Autowired
|
||||
private InetUtils inet;
|
||||
|
||||
private int port = 0;
|
||||
|
||||
private SimpleReactiveDiscoveryProperties simple = new SimpleReactiveDiscoveryProperties();
|
||||
|
||||
@Bean
|
||||
public SimpleReactiveDiscoveryProperties simpleReactiveDiscoveryProperties() {
|
||||
simple.getLocal().setServiceId(serviceId);
|
||||
simple.getLocal().setUri(URI.create("http://"
|
||||
+ inet.findFirstNonLoopbackHostInfo().getHostname() + ":" + findPort()));
|
||||
return simple;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order
|
||||
public SimpleReactiveDiscoveryClient simpleReactiveDiscoveryClient() {
|
||||
return new SimpleReactiveDiscoveryClient(simpleReactiveDiscoveryProperties());
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(
|
||||
value = "spring.cloud.discovery.client.health-indicator.enabled",
|
||||
matchIfMissing = true)
|
||||
public ReactiveDiscoveryClientHealthIndicator simpleReactiveDiscoveryClientHealthIndicator(
|
||||
DiscoveryClientHealthIndicatorProperties properties) {
|
||||
return new ReactiveDiscoveryClientHealthIndicator(simpleReactiveDiscoveryClient(),
|
||||
properties);
|
||||
}
|
||||
|
||||
private int findPort() {
|
||||
if (port > 0) {
|
||||
return port;
|
||||
}
|
||||
if (server != null && server.getPort() != null && server.getPort() > 0) {
|
||||
return server.getPort();
|
||||
}
|
||||
return 8080;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(WebServerInitializedEvent webServerInitializedEvent) {
|
||||
port = webServerInitializedEvent.getWebServer().getPort();
|
||||
if (port > 0) {
|
||||
simple.getLocal().setUri(URI.create("http://"
|
||||
+ inet.findFirstNonLoopbackHostInfo().getHostname() + ":" + port));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
* Copyright 2012-2019 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.simple.reactive;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
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
|
||||
* establish the precedence of this client in the list of clients used by
|
||||
* {@link org.springframework.cloud.client.discovery.composite.CompositeDiscoveryClient}.
|
||||
*
|
||||
* @author Tim Ysewyn
|
||||
* @since 2.2.0
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "spring.cloud.discovery.client.simple")
|
||||
public class SimpleReactiveDiscoveryProperties {
|
||||
|
||||
private Map<String, List<SimpleServiceInstance>> instances = new HashMap<>();
|
||||
|
||||
/**
|
||||
* The properties of the local instance (if it exists). Users should set these
|
||||
* properties explicitly if they are exporting data (e.g. metrics) that need to be
|
||||
* identified by the service instance.
|
||||
*/
|
||||
private SimpleServiceInstance local = new SimpleServiceInstance();
|
||||
|
||||
private int order = DiscoveryClient.DEFAULT_ORDER;
|
||||
|
||||
public Flux<ServiceInstance> getInstances(String service) {
|
||||
return Flux.fromIterable(instances.getOrDefault(service, emptyList()));
|
||||
}
|
||||
|
||||
Map<String, List<SimpleServiceInstance>> getInstances() {
|
||||
return instances;
|
||||
}
|
||||
|
||||
public void setInstances(Map<String, List<SimpleServiceInstance>> instances) {
|
||||
this.instances = instances;
|
||||
}
|
||||
|
||||
public SimpleServiceInstance getLocal() {
|
||||
return this.local;
|
||||
}
|
||||
|
||||
public int getOrder() {
|
||||
return this.order;
|
||||
}
|
||||
|
||||
public void setOrder(int order) {
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
for (String key : this.instances.keySet()) {
|
||||
for (SimpleServiceInstance instance : this.instances.get(key)) {
|
||||
instance.setServiceId(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic implementation of {@link ServiceInstance}.
|
||||
*/
|
||||
public static class SimpleServiceInstance implements ServiceInstance {
|
||||
|
||||
/**
|
||||
* The URI of the service instance. Will be parsed to extract the scheme, host,
|
||||
* and port.
|
||||
*/
|
||||
private URI uri;
|
||||
|
||||
private String host;
|
||||
|
||||
private int port;
|
||||
|
||||
private boolean secure;
|
||||
|
||||
/**
|
||||
* Metadata for the service instance. Can be used by discovery clients to modify
|
||||
* their behaviour per instance, e.g. when load balancing.
|
||||
*/
|
||||
private Map<String, String> metadata = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* The unique identifier or name for the service instance.
|
||||
*/
|
||||
private String instanceId;
|
||||
|
||||
/**
|
||||
* The identifier or name for the service. Multiple instances might share the same
|
||||
* service ID.
|
||||
*/
|
||||
private String serviceId;
|
||||
|
||||
public SimpleServiceInstance() {
|
||||
}
|
||||
|
||||
public SimpleServiceInstance(URI uri) {
|
||||
setUri(uri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getInstanceId() {
|
||||
return this.instanceId;
|
||||
}
|
||||
|
||||
public void setInstanceId(String id) {
|
||||
this.instanceId = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServiceId() {
|
||||
return this.serviceId;
|
||||
}
|
||||
|
||||
public void setServiceId(String id) {
|
||||
this.serviceId = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHost() {
|
||||
return this.host;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPort() {
|
||||
return this.port;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSecure() {
|
||||
return this.secure;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI getUri() {
|
||||
return this.uri;
|
||||
}
|
||||
|
||||
public void setUri(URI uri) {
|
||||
this.uri = uri;
|
||||
this.host = this.uri.getHost();
|
||||
this.port = this.uri.getPort();
|
||||
String scheme = this.uri.getScheme();
|
||||
if ("https".equals(scheme)) {
|
||||
this.secure = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getMetadata() {
|
||||
return this.metadata;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
# AutoConfiguration
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
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.noop.NoopDiscoveryClientAutoConfiguration,\
|
||||
org.springframework.cloud.client.discovery.simple.SimpleDiscoveryClientAutoConfiguration,\
|
||||
org.springframework.cloud.client.discovery.simple.reactive.SimpleReactiveDiscoveryClientAutoConfiguration,\
|
||||
org.springframework.cloud.client.hypermedia.CloudHypermediaAutoConfiguration,\
|
||||
org.springframework.cloud.client.loadbalancer.AsyncLoadBalancerAutoConfiguration,\
|
||||
org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration,\
|
||||
|
||||
@@ -19,18 +19,19 @@ package org.springframework.cloud.client;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.boot.WebApplicationType;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.TestConfiguration;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.cloud.client.actuator.FeaturesEndpoint;
|
||||
import org.springframework.cloud.client.actuator.HasFeatures;
|
||||
import org.springframework.cloud.client.discovery.health.DiscoveryClientHealthIndicator;
|
||||
import org.springframework.cloud.client.discovery.health.DiscoveryCompositeHealthIndicator;
|
||||
import org.springframework.cloud.client.discovery.noop.NoopDiscoveryClientAutoConfiguration;
|
||||
import org.springframework.cloud.client.discovery.simple.SimpleDiscoveryClientAutoConfiguration;
|
||||
import org.springframework.cloud.commons.util.UtilAutoConfiguration;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
@@ -39,66 +40,95 @@ import static org.assertj.core.api.BDDAssertions.then;
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @author Tim Ysewyn
|
||||
*/
|
||||
public class CommonsClientAutoConfigurationTests {
|
||||
|
||||
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(
|
||||
AutoConfigurations.of(HealthEndpointAutoConfiguration.class,
|
||||
CommonsClientAutoConfiguration.class,
|
||||
SimpleDiscoveryClientAutoConfiguration.class,
|
||||
UtilAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
public void beansCreatedNormally() {
|
||||
try (ConfigurableApplicationContext ctxt = init()) {
|
||||
applicationContextRunner.run(ctxt -> {
|
||||
then(ctxt.getBean(DiscoveryClientHealthIndicator.class)).isNotNull();
|
||||
then(ctxt.getBean(DiscoveryCompositeHealthIndicator.class)).isNotNull();
|
||||
then(ctxt.getBean(FeaturesEndpoint.class)).isNotNull();
|
||||
then(ctxt.getBeansOfType(HasFeatures.class).values()).isNotEmpty();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void disableAll() {
|
||||
try (ConfigurableApplicationContext ctxt = init(
|
||||
"spring.cloud.discovery.enabled=false")) {
|
||||
assertBeanNonExistant(ctxt, DiscoveryClientHealthIndicator.class);
|
||||
assertBeanNonExistant(ctxt, DiscoveryCompositeHealthIndicator.class);
|
||||
then(ctxt.getBean(FeaturesEndpoint.class)).isNotNull(); // features
|
||||
// actuator
|
||||
// is
|
||||
// independent
|
||||
// of
|
||||
// discovery
|
||||
assertBeanNonExistant(ctxt, HasFeatures.class);
|
||||
}
|
||||
applicationContextRunner
|
||||
.withPropertyValues("spring.cloud.discovery.enabled=false").run(ctxt -> {
|
||||
assertBeanNonExistant(ctxt, DiscoveryClientHealthIndicator.class);
|
||||
assertBeanNonExistant(ctxt, DiscoveryCompositeHealthIndicator.class);
|
||||
then(ctxt.getBean(FeaturesEndpoint.class)).isNotNull();
|
||||
// features actuator is independent of discovery
|
||||
assertBeanNonExistant(ctxt, HasFeatures.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void disableBlocking() {
|
||||
applicationContextRunner
|
||||
.withPropertyValues("spring.cloud.discovery.blocking.enabled=false")
|
||||
.run(ctxt -> {
|
||||
assertBeanNonExistant(ctxt, DiscoveryClientHealthIndicator.class);
|
||||
assertBeanNonExistant(ctxt, DiscoveryCompositeHealthIndicator.class);
|
||||
then(ctxt.getBean(FeaturesEndpoint.class)).isNotNull();
|
||||
// features actuator is independent of discovery
|
||||
assertBeanNonExistant(ctxt, HasFeatures.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void disableAllIndividually() {
|
||||
try (ConfigurableApplicationContext ctxt = init(
|
||||
applicationContextRunner.withPropertyValues(
|
||||
"spring.cloud.discovery.client.health-indicator.enabled=false",
|
||||
"spring.cloud.discovery.client.composite-indicator.enabled=false",
|
||||
"spring.cloud.features.enabled=false")) {
|
||||
assertBeanNonExistant(ctxt, DiscoveryClientHealthIndicator.class);
|
||||
assertBeanNonExistant(ctxt, DiscoveryCompositeHealthIndicator.class);
|
||||
assertBeanNonExistant(ctxt, FeaturesEndpoint.class);
|
||||
}
|
||||
"spring.cloud.features.enabled=false").run(ctxt -> {
|
||||
assertBeanNonExistant(ctxt, DiscoveryClientHealthIndicator.class);
|
||||
assertBeanNonExistant(ctxt, DiscoveryCompositeHealthIndicator.class);
|
||||
assertBeanNonExistant(ctxt, FeaturesEndpoint.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void disableHealthIndicator() {
|
||||
try (ConfigurableApplicationContext ctxt = init(
|
||||
"spring.cloud.discovery.client.health-indicator.enabled=false")) {
|
||||
assertBeanNonExistant(ctxt, DiscoveryClientHealthIndicator.class);
|
||||
assertBeanNonExistant(ctxt, DiscoveryCompositeHealthIndicator.class);
|
||||
}
|
||||
applicationContextRunner
|
||||
.withPropertyValues(
|
||||
"spring.cloud.discovery.client.health-indicator.enabled=false")
|
||||
.run(ctxt -> {
|
||||
assertBeanNonExistant(ctxt, DiscoveryClientHealthIndicator.class);
|
||||
assertBeanNonExistant(ctxt, DiscoveryCompositeHealthIndicator.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void conditionalOnDiscoveryEnabledWorks() {
|
||||
try (ConfigurableApplicationContext context = init(
|
||||
"spring.cloud.discovery.enabled=false")) {
|
||||
assertBeanNonExistant(context, TestBean.class);
|
||||
}
|
||||
try (ConfigurableApplicationContext context = init(
|
||||
"spring.cloud.discovery.enabled=true")) {
|
||||
assertThat(context.getBean(TestBean.class)).isNotNull();
|
||||
}
|
||||
applicationContextRunner.withUserConfiguration(DiscoveryEnabledConfig.class)
|
||||
.withPropertyValues("spring.cloud.discovery.enabled=false")
|
||||
.run(context -> assertBeanNonExistant(context, TestBean.class));
|
||||
applicationContextRunner.withUserConfiguration(DiscoveryEnabledConfig.class)
|
||||
.withPropertyValues("spring.cloud.discovery.enabled=true")
|
||||
.run(context -> assertThat(context.getBean(TestBean.class)).isNotNull());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void conditionalOnBlockingDiscoveryEnabledWorks() {
|
||||
applicationContextRunner
|
||||
.withUserConfiguration(BlockingDiscoveryEnabledConfig.class)
|
||||
.withPropertyValues("spring.cloud.discovery.blocking.enabled=false")
|
||||
.run(context -> assertBeanNonExistant(context, TestBean.class));
|
||||
applicationContextRunner
|
||||
.withUserConfiguration(BlockingDiscoveryEnabledConfig.class)
|
||||
.withPropertyValues("spring.cloud.discovery.blocking.enabled=true")
|
||||
.run(context -> assertThat(context.getBean(TestBean.class)).isNotNull());
|
||||
}
|
||||
|
||||
private void assertBeanNonExistant(ConfigurableApplicationContext ctxt,
|
||||
@@ -112,18 +142,6 @@ public class CommonsClientAutoConfigurationTests {
|
||||
}
|
||||
}
|
||||
|
||||
protected ConfigurableApplicationContext init(String... pairs) {
|
||||
return new SpringApplicationBuilder().web(WebApplicationType.NONE)
|
||||
.sources(Config.class).properties(pairs).run();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableAutoConfiguration
|
||||
@Import({ NoopDiscoveryClientAutoConfiguration.class, DiscoveryEnabledConfig.class })
|
||||
protected static class Config {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnDiscoveryEnabled
|
||||
protected static class DiscoveryEnabledConfig {
|
||||
@@ -135,6 +153,17 @@ public class CommonsClientAutoConfigurationTests {
|
||||
|
||||
}
|
||||
|
||||
@TestConfiguration
|
||||
@ConditionalOnBlockingDiscoveryEnabled
|
||||
protected static class BlockingDiscoveryEnabledConfig {
|
||||
|
||||
@Bean
|
||||
TestBean testBean() {
|
||||
return new TestBean();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class TestBean {
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* Copyright 2019-2019 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;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.TestConfiguration;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.cloud.client.actuator.FeaturesEndpoint;
|
||||
import org.springframework.cloud.client.actuator.HasFeatures;
|
||||
import org.springframework.cloud.client.discovery.health.reactive.ReactiveDiscoveryClientHealthIndicator;
|
||||
import org.springframework.cloud.client.discovery.health.reactive.ReactiveDiscoveryCompositeHealthContributor;
|
||||
import org.springframework.cloud.client.discovery.simple.reactive.SimpleReactiveDiscoveryClientAutoConfiguration;
|
||||
import org.springframework.cloud.commons.util.UtilAutoConfiguration;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
import static org.assertj.core.api.BDDAssertions.then;
|
||||
|
||||
/**
|
||||
* @author Tim Ysewyn
|
||||
*/
|
||||
public class ReactiveCommonsClientAutoConfigurationTests {
|
||||
|
||||
ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(CommonsClientAutoConfiguration.class,
|
||||
SimpleReactiveDiscoveryClientAutoConfiguration.class,
|
||||
UtilAutoConfiguration.class,
|
||||
ReactiveCommonsClientAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
public void beansCreatedNormally() {
|
||||
applicationContextRunner.run(context -> {
|
||||
then(context.getBean(ReactiveDiscoveryClientHealthIndicator.class))
|
||||
.isNotNull();
|
||||
then(context.getBean(ReactiveDiscoveryCompositeHealthContributor.class))
|
||||
.isNotNull();
|
||||
then(context.getBean(FeaturesEndpoint.class)).isNotNull();
|
||||
then(context.getBeansOfType(HasFeatures.class).values()).isNotEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void disableAll() {
|
||||
applicationContextRunner
|
||||
.withPropertyValues("spring.cloud.discovery.enabled=false")
|
||||
.run(context -> {
|
||||
assertBeanNonExistant(context,
|
||||
ReactiveDiscoveryClientHealthIndicator.class);
|
||||
assertBeanNonExistant(context,
|
||||
ReactiveDiscoveryCompositeHealthContributor.class);
|
||||
// features actuator is independent of discovery
|
||||
then(context.getBean(FeaturesEndpoint.class)).isNotNull();
|
||||
assertBeanNonExistant(context, HasFeatures.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void disableReactive() {
|
||||
applicationContextRunner
|
||||
.withPropertyValues("spring.cloud.discovery.reactive.enabled=false")
|
||||
.run(context -> {
|
||||
assertBeanNonExistant(context,
|
||||
ReactiveDiscoveryClientHealthIndicator.class);
|
||||
assertBeanNonExistant(context,
|
||||
ReactiveDiscoveryCompositeHealthContributor.class);
|
||||
// features actuator is independent of discovery
|
||||
then(context.getBean(FeaturesEndpoint.class)).isNotNull();
|
||||
assertBeanNonExistant(context, HasFeatures.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void disableAllIndividually() {
|
||||
applicationContextRunner.withPropertyValues(
|
||||
"spring.cloud.discovery.client.health-indicator.enabled=false",
|
||||
"spring.cloud.discovery.client.composite-indicator.enabled=false",
|
||||
"spring.cloud.features.enabled=false").run(context -> {
|
||||
assertBeanNonExistant(context,
|
||||
ReactiveDiscoveryClientHealthIndicator.class);
|
||||
assertBeanNonExistant(context,
|
||||
ReactiveDiscoveryCompositeHealthContributor.class);
|
||||
assertBeanNonExistant(context, FeaturesEndpoint.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void disableHealthIndicator() {
|
||||
applicationContextRunner
|
||||
.withPropertyValues(
|
||||
"spring.cloud.discovery.client.health-indicator.enabled=false")
|
||||
.run(context -> {
|
||||
assertBeanNonExistant(context,
|
||||
ReactiveDiscoveryClientHealthIndicator.class);
|
||||
assertBeanNonExistant(context,
|
||||
ReactiveDiscoveryCompositeHealthContributor.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void conditionalOnReactiveDiscoveryEnabledWorks() {
|
||||
ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withUserConfiguration(ReactiveDiscoveryEnabledConfig.class);
|
||||
contextRunner.withPropertyValues("spring.cloud.discovery.reactive.enabled=false")
|
||||
.run(context -> assertBeanNonExistant(context, TestBean.class));
|
||||
contextRunner.withPropertyValues("spring.cloud.discovery.reactive.enabled=true")
|
||||
.run(context -> assertThat(context.getBean(TestBean.class)).isNotNull());
|
||||
}
|
||||
|
||||
private void assertBeanNonExistant(ConfigurableApplicationContext ctxt,
|
||||
Class<?> beanClass) {
|
||||
try {
|
||||
ctxt.getBean(beanClass);
|
||||
fail("Bean of type " + beanClass + " should not have been created");
|
||||
}
|
||||
catch (BeansException e) {
|
||||
// should fail with exception
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnDiscoveryEnabled
|
||||
protected static class DiscoveryEnabledConfig {
|
||||
|
||||
@Bean
|
||||
TestBean testBean() {
|
||||
return new TestBean();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@TestConfiguration
|
||||
@ConditionalOnReactiveDiscoveryEnabled
|
||||
protected static class ReactiveDiscoveryEnabledConfig {
|
||||
|
||||
@Bean
|
||||
TestBean testBean() {
|
||||
return new TestBean();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class TestBean {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2012-2019 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.composite.reactive;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.TestConfiguration;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Tim Ysewyn
|
||||
*/
|
||||
class ReactiveCompositeDiscoveryClientAutoConfigurationTests {
|
||||
|
||||
private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations
|
||||
.of(ReactiveCompositeDiscoveryClientAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
public void shouldCreateCompositeReactiveDiscoveryClientWithoutDelegates() {
|
||||
this.contextRunner.run((context) -> {
|
||||
ReactiveDiscoveryClient client = context
|
||||
.getBean(ReactiveDiscoveryClient.class);
|
||||
assertThat(client).isNotNull();
|
||||
assertThat(client).isInstanceOf(ReactiveCompositeDiscoveryClient.class);
|
||||
assertThat(((ReactiveCompositeDiscoveryClient) client).getDiscoveryClients())
|
||||
.isEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCreateCompositeReactiveDiscoveryClientWithDelegate() {
|
||||
this.contextRunner.withUserConfiguration(Configuration.class).run((context) -> {
|
||||
ReactiveDiscoveryClient client = context
|
||||
.getBean(ReactiveDiscoveryClient.class);
|
||||
assertThat(client).isNotNull();
|
||||
assertThat(client).isInstanceOf(ReactiveCompositeDiscoveryClient.class);
|
||||
assertThat(((ReactiveCompositeDiscoveryClient) client).getDiscoveryClients())
|
||||
.hasSize(1);
|
||||
});
|
||||
}
|
||||
|
||||
@TestConfiguration
|
||||
static class Configuration {
|
||||
|
||||
@Bean
|
||||
ReactiveDiscoveryClient discoveryClient() {
|
||||
return new ReactiveDiscoveryClient() {
|
||||
@Override
|
||||
public String description() {
|
||||
return "Reactive Test Discovery Client";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<ServiceInstance> getInstances(String serviceId) {
|
||||
return Flux.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<String> getServices() {
|
||||
return Flux.empty();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright 2012-2019 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.composite.reactive;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.test.StepVerifier;
|
||||
import reactor.test.publisher.TestPublisher;
|
||||
|
||||
import org.springframework.cloud.client.DefaultServiceInstance;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.Collections.emptyList;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* @author Tim Ysewyn
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class ReactiveCompositeDiscoveryClientTests {
|
||||
|
||||
@Mock
|
||||
private ReactiveDiscoveryClient discoveryClient1;
|
||||
|
||||
@Mock
|
||||
private ReactiveDiscoveryClient discoveryClient2;
|
||||
|
||||
@Test
|
||||
public void shouldReturnEmptyFluxOfServices() {
|
||||
ReactiveCompositeDiscoveryClient client = new ReactiveCompositeDiscoveryClient(
|
||||
emptyList());
|
||||
|
||||
Flux<String> services = client.getServices();
|
||||
|
||||
StepVerifier.create(services).expectComplete().verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnFluxOfServices() {
|
||||
TestPublisher<String> discoveryClient1Publisher = TestPublisher.createCold();
|
||||
discoveryClient1Publisher.emit("serviceAFromClient1");
|
||||
discoveryClient1Publisher.emit("serviceBFromClient1");
|
||||
discoveryClient1Publisher.complete();
|
||||
|
||||
TestPublisher<String> discoveryClient2Publisher = TestPublisher.createCold();
|
||||
discoveryClient2Publisher.emit("serviceCFromClient2");
|
||||
discoveryClient2Publisher.complete();
|
||||
|
||||
when(discoveryClient1.getServices()).thenReturn(discoveryClient1Publisher.flux());
|
||||
when(discoveryClient2.getServices()).thenReturn(discoveryClient2Publisher.flux());
|
||||
|
||||
ReactiveCompositeDiscoveryClient client = new ReactiveCompositeDiscoveryClient(
|
||||
asList(discoveryClient1, discoveryClient2));
|
||||
|
||||
assertThat(client.description()).isEqualTo("Composite Reactive Discovery Client");
|
||||
|
||||
Flux<String> services = client.getServices();
|
||||
|
||||
StepVerifier.create(services).expectNext("serviceAFromClient1")
|
||||
.expectNext("serviceBFromClient1").expectNext("serviceCFromClient2")
|
||||
.expectComplete().verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnEmptyFluxOfServiceInstances() {
|
||||
ReactiveCompositeDiscoveryClient client = new ReactiveCompositeDiscoveryClient(
|
||||
emptyList());
|
||||
|
||||
Flux<ServiceInstance> instances = client.getInstances("service");
|
||||
|
||||
StepVerifier.create(instances).expectComplete().verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnFluxOfServiceInstances() {
|
||||
DefaultServiceInstance serviceInstance1 = new DefaultServiceInstance("instance",
|
||||
"service", "localhost", 8080, false);
|
||||
DefaultServiceInstance serviceInstance2 = new DefaultServiceInstance("instance2",
|
||||
"service", "localhost", 8080, false);
|
||||
TestPublisher<ServiceInstance> discoveryClient1Publisher = TestPublisher
|
||||
.createCold();
|
||||
discoveryClient1Publisher.emit(serviceInstance1);
|
||||
discoveryClient1Publisher.emit(serviceInstance2);
|
||||
discoveryClient1Publisher.complete();
|
||||
|
||||
TestPublisher<ServiceInstance> discoveryClient2Publisher = TestPublisher
|
||||
.createCold();
|
||||
discoveryClient2Publisher.complete();
|
||||
|
||||
when(discoveryClient1.getInstances("service"))
|
||||
.thenReturn(discoveryClient1Publisher.flux());
|
||||
when(discoveryClient2.getInstances("service"))
|
||||
.thenReturn(discoveryClient2Publisher.flux());
|
||||
|
||||
ReactiveCompositeDiscoveryClient client = new ReactiveCompositeDiscoveryClient(
|
||||
asList(discoveryClient1, discoveryClient2));
|
||||
|
||||
Flux<ServiceInstance> instances = client.getInstances("service");
|
||||
|
||||
StepVerifier.create(instances).expectNext(serviceInstance1)
|
||||
.expectNext(serviceInstance2).expectComplete().verify();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright 2012-2019 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.health.reactive;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import org.springframework.boot.actuate.health.Health;
|
||||
import org.springframework.boot.actuate.health.Status;
|
||||
import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient;
|
||||
import org.springframework.cloud.client.discovery.event.InstanceRegisteredEvent;
|
||||
import org.springframework.cloud.client.discovery.health.DiscoveryClientHealthIndicatorProperties;
|
||||
import org.springframework.core.Ordered;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* @author Tim Ysewyn
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class ReactiveDiscoveryClientHealthIndicatorTests {
|
||||
|
||||
@Mock
|
||||
private ReactiveDiscoveryClient discoveryClient;
|
||||
|
||||
@Mock
|
||||
private DiscoveryClientHealthIndicatorProperties properties;
|
||||
|
||||
@InjectMocks
|
||||
private ReactiveDiscoveryClientHealthIndicator indicator;
|
||||
|
||||
@Test
|
||||
public void shouldReturnCorrectOrder() {
|
||||
assertThat(indicator.getOrder()).isEqualTo(Ordered.HIGHEST_PRECEDENCE);
|
||||
indicator.setOrder(0);
|
||||
assertThat(indicator.getOrder()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnUnknownStatusWhenNotInitialized() {
|
||||
Health expectedHealth = Health.status(
|
||||
new Status(Status.UNKNOWN.getCode(), "Discovery Client not initialized"))
|
||||
.build();
|
||||
Mono<Health> health = indicator.health();
|
||||
StepVerifier.create(health).expectNext(expectedHealth).expectComplete().verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnUpStatusWithoutServices() {
|
||||
when(discoveryClient.description()).thenReturn("Mocked Service Discovery Client");
|
||||
when(discoveryClient.getServices()).thenReturn(Flux.empty());
|
||||
Health expectedHealth = Health.status(new Status(Status.UP.getCode(), ""))
|
||||
.withDetail("services", emptyList()).build();
|
||||
|
||||
indicator.onApplicationEvent(new InstanceRegisteredEvent<>(this, null));
|
||||
Mono<Health> health = indicator.health();
|
||||
|
||||
assertThat(indicator.getName()).isEqualTo("Mocked Service Discovery Client");
|
||||
StepVerifier.create(health).expectNext(expectedHealth).expectComplete().verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnUpStatusWithServices() {
|
||||
when(discoveryClient.getServices()).thenReturn(Flux.just("service"));
|
||||
when(properties.isIncludeDescription()).thenReturn(true);
|
||||
when(discoveryClient.description()).thenReturn("Mocked Service Discovery Client");
|
||||
Health expectedHealth = Health
|
||||
.status(new Status(Status.UP.getCode(),
|
||||
"Mocked Service Discovery Client"))
|
||||
.withDetail("services", singletonList("service")).build();
|
||||
|
||||
indicator.onApplicationEvent(new InstanceRegisteredEvent<>(this, null));
|
||||
Mono<Health> health = indicator.health();
|
||||
|
||||
assertThat(indicator.getName()).isEqualTo("Mocked Service Discovery Client");
|
||||
StepVerifier.create(health).expectNext(expectedHealth).expectComplete().verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnDownStatusWhenServicesCouldNotBeRetrieved() {
|
||||
RuntimeException ex = new RuntimeException("something went wrong");
|
||||
Health expectedHealth = Health.down(ex).build();
|
||||
when(discoveryClient.getServices()).thenReturn(Flux.error(ex));
|
||||
|
||||
indicator.onApplicationEvent(new InstanceRegisteredEvent<>(this, null));
|
||||
Mono<Health> health = indicator.health();
|
||||
|
||||
StepVerifier.create(health).expectNext(expectedHealth).expectComplete()
|
||||
.verifyThenAssertThat();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2012-2019 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.health.reactive;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import org.springframework.boot.actuate.health.Health;
|
||||
import org.springframework.boot.actuate.health.NamedContributor;
|
||||
import org.springframework.boot.actuate.health.ReactiveHealthContributor;
|
||||
import org.springframework.boot.actuate.health.ReactiveHealthIndicator;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* @author Tim Ysewyn
|
||||
*/
|
||||
class ReactiveDiscoveryCompositeHealthContributorTests {
|
||||
|
||||
@Test
|
||||
void shouldReturnEmptyIterator() {
|
||||
ReactiveDiscoveryCompositeHealthContributor healthContributor = new ReactiveDiscoveryCompositeHealthContributor(
|
||||
emptyList());
|
||||
assertThat(healthContributor.iterator().hasNext()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnNullForUnknownContributor() {
|
||||
ReactiveDiscoveryCompositeHealthContributor healthContributor = new ReactiveDiscoveryCompositeHealthContributor(
|
||||
emptyList());
|
||||
assertThat(healthContributor.getContributor("unknown")).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnKnownContributor() {
|
||||
ReactiveDiscoveryHealthIndicator indicator = mock(
|
||||
ReactiveDiscoveryHealthIndicator.class);
|
||||
Health health = Health.up().build();
|
||||
when(indicator.getName()).thenReturn("known");
|
||||
when(indicator.health()).thenReturn(Mono.just(health));
|
||||
|
||||
ReactiveDiscoveryCompositeHealthContributor healthContributor = new ReactiveDiscoveryCompositeHealthContributor(
|
||||
singletonList(indicator));
|
||||
|
||||
assertThat(healthContributor.getContributor("known")).isNotNull();
|
||||
Iterator<NamedContributor<ReactiveHealthContributor>> iterator = healthContributor
|
||||
.iterator();
|
||||
assertThat(iterator.hasNext()).isTrue();
|
||||
NamedContributor<ReactiveHealthContributor> contributor = iterator.next();
|
||||
assertThat(contributor).isNotNull();
|
||||
assertThat(contributor.getName()).isEqualTo("known");
|
||||
assertThat(contributor.getContributor()).isNotNull();
|
||||
assertThat(contributor.getContributor())
|
||||
.isInstanceOf(ReactiveHealthIndicator.class);
|
||||
ReactiveHealthIndicator healthIndicator = (ReactiveHealthIndicator) contributor
|
||||
.getContributor();
|
||||
StepVerifier.create(healthIndicator.getHealth(true)).expectNext(health)
|
||||
.expectComplete().verify();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2012-2019 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.simple.reactive;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.test.context.TestConfiguration;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient;
|
||||
import org.springframework.cloud.commons.util.InetUtils;
|
||||
import org.springframework.cloud.commons.util.UtilAutoConfiguration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Tim Ysewyn
|
||||
*/
|
||||
class SimpleReactiveDiscoveryClientAutoConfigurationTests {
|
||||
|
||||
private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(
|
||||
SimpleReactiveDiscoveryClientAutoConfiguration.class,
|
||||
UtilAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
public void shouldUseDefaults() {
|
||||
this.contextRunner.run((context) -> {
|
||||
ReactiveDiscoveryClient client = context
|
||||
.getBean(ReactiveDiscoveryClient.class);
|
||||
assertThat(client).isNotNull();
|
||||
assertThat(client.getOrder())
|
||||
.isEqualTo(ReactiveDiscoveryClient.DEFAULT_ORDER);
|
||||
InetUtils inet = context.getBean(InetUtils.class);
|
||||
assertThat(inet).isNotNull();
|
||||
SimpleReactiveDiscoveryProperties properties = context
|
||||
.getBean(SimpleReactiveDiscoveryProperties.class);
|
||||
assertThat(properties).isNotNull();
|
||||
assertThat(properties.getLocal().getServiceId()).isEqualTo("application");
|
||||
assertThat(properties.getLocal().getHost())
|
||||
.isEqualTo(inet.findFirstNonLoopbackHostInfo().getHostname());
|
||||
assertThat(properties.getLocal().getPort()).isEqualTo(8080);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldUseCustomConfiguration() {
|
||||
this.contextRunner.withUserConfiguration(Configuration.class)
|
||||
.withPropertyValues("spring.application.name=my-service",
|
||||
"spring.cloud.discovery.client.simple.order=1",
|
||||
"server.port=8443")
|
||||
.run((context) -> {
|
||||
ReactiveDiscoveryClient client = context
|
||||
.getBean(ReactiveDiscoveryClient.class);
|
||||
assertThat(client).isNotNull();
|
||||
assertThat(client.getOrder()).isEqualTo(1);
|
||||
InetUtils inet = context.getBean(InetUtils.class);
|
||||
assertThat(inet).isNotNull();
|
||||
SimpleReactiveDiscoveryProperties properties = context
|
||||
.getBean(SimpleReactiveDiscoveryProperties.class);
|
||||
assertThat(properties).isNotNull();
|
||||
assertThat(properties.getLocal().getServiceId())
|
||||
.isEqualTo("my-service");
|
||||
assertThat(properties.getLocal().getHost())
|
||||
.isEqualTo(inet.findFirstNonLoopbackHostInfo().getHostname());
|
||||
assertThat(properties.getLocal().getPort()).isEqualTo(8443);
|
||||
});
|
||||
}
|
||||
|
||||
@TestConfiguration
|
||||
@EnableConfigurationProperties(ServerProperties.class)
|
||||
static class Configuration {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2012-2019 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.simple.reactive;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient;
|
||||
import org.springframework.cloud.client.discovery.simple.reactive.SimpleReactiveDiscoveryProperties.SimpleServiceInstance;
|
||||
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Tim Ysewyn
|
||||
*/
|
||||
public class SimpleReactiveDiscoveryClientTests {
|
||||
|
||||
private final SimpleServiceInstance service1Inst1 = new SimpleServiceInstance(
|
||||
URI.create("http://host1:8080"));
|
||||
|
||||
private final SimpleServiceInstance service1Inst2 = new SimpleServiceInstance(
|
||||
URI.create("https://host2:8443"));
|
||||
|
||||
private SimpleReactiveDiscoveryClient client;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
SimpleReactiveDiscoveryProperties simpleReactiveDiscoveryProperties = new SimpleReactiveDiscoveryProperties();
|
||||
simpleReactiveDiscoveryProperties.setInstances(
|
||||
singletonMap("service", Arrays.asList(service1Inst1, service1Inst2)));
|
||||
simpleReactiveDiscoveryProperties.init();
|
||||
this.client = new SimpleReactiveDiscoveryClient(
|
||||
simpleReactiveDiscoveryProperties);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyDefaults() {
|
||||
assertThat(client.description()).isEqualTo("Simple Reactive Discovery Client");
|
||||
assertThat(client.getOrder()).isEqualTo(ReactiveDiscoveryClient.DEFAULT_ORDER);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnFluxOfServices() {
|
||||
Flux<String> services = this.client.getServices();
|
||||
StepVerifier.create(services).expectNext("service").expectComplete().verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnEmptyFluxForNonExistingService() {
|
||||
Flux<ServiceInstance> instances = this.client.getInstances("undefined");
|
||||
StepVerifier.create(instances).expectComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnFluxOfServiceInstances() {
|
||||
Flux<ServiceInstance> services = this.client.getInstances("service");
|
||||
StepVerifier.create(services).expectNext(service1Inst1).expectNext(service1Inst2)
|
||||
.expectComplete().verify();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user