Skip to content

Commit 6677d16

Browse files
Add Reactive probe (#1201)
1 parent 77f9d2b commit 6677d16

File tree

4 files changed

+106
-29
lines changed

4 files changed

+106
-29
lines changed

spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/ReactiveDiscoveryClient.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@
1616

1717
package org.springframework.cloud.client.discovery;
1818

19+
import org.apache.commons.logging.Log;
20+
import org.apache.commons.logging.LogFactory;
1921
import reactor.core.publisher.Flux;
22+
import reactor.core.publisher.Mono;
2023

2124
import org.springframework.cloud.client.ServiceInstance;
2225
import org.springframework.core.Ordered;
@@ -26,9 +29,12 @@
2629
* Eureka or consul.io.
2730
*
2831
* @author Tim Ysewyn
32+
* @author Olga Maciaszek-Sharma
2933
*/
3034
public interface ReactiveDiscoveryClient extends Ordered {
3135

36+
Log LOG = LogFactory.getLog(ReactiveDiscoveryClient.class);
37+
3238
/**
3339
* Default order of the discovery client.
3440
*/
@@ -60,11 +66,35 @@ public interface ReactiveDiscoveryClient extends Ordered {
6066
* <p>
6167
* The default implementation simply calls {@link #getServices()} - client
6268
* implementations can override with a lighter weight operation if they choose to.
69+
* @deprecated in favour of {@link ReactiveDiscoveryClient#reactiveProbe()}. This
70+
* method should not be used as is, as it contains a bug - the method called within
71+
* returns a {@link Flux}, which is not accessible for subscription or blocking from
72+
* within. We are leaving it with a deprecation in order not to bring downstream
73+
* implementations.
6374
*/
75+
@Deprecated
6476
default void probe() {
77+
if (LOG.isWarnEnabled()) {
78+
LOG.warn("ReactiveDiscoveryClient#probe has been called. If you're calling this method directly, "
79+
+ "use ReactiveDiscoveryClient#reactiveProbe instead.");
80+
}
6581
getServices();
6682
}
6783

84+
/**
85+
* Can be used to verify the client is still valid and able to make calls.
86+
* <p>
87+
* A successful invocation with no exception thrown implies the client is able to make
88+
* calls.
89+
* <p>
90+
* The default implementation simply calls {@link #getServices()} and wraps it with a
91+
* {@link Mono} - client implementations can override with a lighter weight operation
92+
* if they choose to.
93+
*/
94+
default Mono<Void> reactiveProbe() {
95+
return getServices().then();
96+
}
97+
6898
/**
6999
* Default implementation for getting order of discovery clients.
70100
* @return order

spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/health/reactive/ReactiveDiscoveryClientHealthIndicator.java

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -33,21 +33,21 @@
3333
import static java.util.Collections.emptyList;
3434

3535
/**
36-
* A health indicator which indicates whether or not the discovery client has been
37-
* initialized.
36+
* A health indicator which indicates whether the discovery client has been initialized.
3837
*
3938
* @author Tim Ysewyn
4039
* @author Chris Bono
40+
* @author Olga Maciaszek-Sharma
4141
*/
4242
public class ReactiveDiscoveryClientHealthIndicator
4343
implements ReactiveDiscoveryHealthIndicator, Ordered, ApplicationListener<InstanceRegisteredEvent<?>> {
4444

45+
private static final Log LOG = LogFactory.getLog(ReactiveDiscoveryClientHealthIndicator.class);
46+
4547
private final ReactiveDiscoveryClient discoveryClient;
4648

4749
private final DiscoveryClientHealthIndicatorProperties properties;
4850

49-
private final Log log = LogFactory.getLog(ReactiveDiscoveryClientHealthIndicator.class);
50-
5151
private AtomicBoolean discoveryInitialized = new AtomicBoolean(false);
5252

5353
private int order = Ordered.HIGHEST_PRECEDENCE;
@@ -60,14 +60,14 @@ public ReactiveDiscoveryClientHealthIndicator(ReactiveDiscoveryClient discoveryC
6060

6161
@Override
6262
public void onApplicationEvent(InstanceRegisteredEvent<?> event) {
63-
if (this.discoveryInitialized.compareAndSet(false, true)) {
64-
this.log.debug("Discovery Client has been initialized");
63+
if (discoveryInitialized.compareAndSet(false, true)) {
64+
LOG.debug("Discovery Client has been initialized");
6565
}
6666
}
6767

6868
@Override
6969
public Mono<Health> health() {
70-
if (this.discoveryInitialized.get()) {
70+
if (discoveryInitialized.get()) {
7171
return doHealthCheck();
7272
}
7373
else {
@@ -78,38 +78,39 @@ public Mono<Health> health() {
7878

7979
private Mono<Health> doHealthCheck() {
8080
// @formatter:off
81-
return Mono.just(this.properties.isUseServicesQuery())
81+
return Mono.just(properties.isUseServicesQuery())
8282
.flatMap(useServices -> useServices ? doHealthCheckWithServices() : doHealthCheckWithProbe())
8383
.onErrorResume(exception -> {
84-
this.log.error("Error", exception);
84+
if (LOG.isErrorEnabled()) {
85+
LOG.error("Error", exception);
86+
}
8587
return Mono.just(Health.down().withException(exception).build());
8688
});
8789
// @formatter:on
8890
}
8991

9092
private Mono<Health> doHealthCheckWithProbe() {
91-
// @formatter:off
92-
return Mono.justOrEmpty(this.discoveryClient)
93-
.flatMap(client -> {
94-
client.probe();
95-
return Mono.just(client);
96-
})
97-
.map(client -> {
98-
String description = (this.properties.isIncludeDescription()) ? client.description() : "";
99-
return Health.status(new Status("UP", description)).build();
100-
});
101-
// @formatter:on
93+
return discoveryClient.reactiveProbe().doOnError(exception -> {
94+
if (LOG.isErrorEnabled()) {
95+
LOG.error("Probe has failed.", exception);
96+
}
97+
}).then(buildHealthUp(discoveryClient));
98+
}
99+
100+
private Mono<Health> buildHealthUp(ReactiveDiscoveryClient discoveryClient) {
101+
String description = (properties.isIncludeDescription()) ? discoveryClient.description() : "";
102+
return Mono.just(Health.status(new Status("UP", description)).build());
102103
}
103104

104105
private Mono<Health> doHealthCheckWithServices() {
105106
// @formatter:off
106-
return Mono.justOrEmpty(this.discoveryClient)
107+
return Mono.justOrEmpty(discoveryClient)
107108
.flatMapMany(ReactiveDiscoveryClient::getServices)
108109
.collectList()
109110
.defaultIfEmpty(emptyList())
110111
.map(services -> {
111-
String description = (this.properties.isIncludeDescription()) ?
112-
this.discoveryClient.description() : "";
112+
String description = (properties.isIncludeDescription()) ?
113+
discoveryClient.description() : "";
113114
return Health.status(new Status("UP", description))
114115
.withDetail("services", services).build();
115116
});
@@ -123,7 +124,7 @@ public String getName() {
123124

124125
@Override
125126
public int getOrder() {
126-
return this.order;
127+
return order;
127128
}
128129

129130
public void setOrder(int order) {

spring-cloud-commons/src/test/java/org/springframework/cloud/client/discovery/health/reactive/ReactiveDiscoveryClientHealthIndicatorTests.java

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727

2828
import org.springframework.boot.actuate.health.Health;
2929
import org.springframework.boot.actuate.health.Status;
30+
import org.springframework.cloud.client.DefaultServiceInstance;
31+
import org.springframework.cloud.client.ServiceInstance;
3032
import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient;
3133
import org.springframework.cloud.client.discovery.event.InstanceRegisteredEvent;
3234
import org.springframework.cloud.client.discovery.health.DiscoveryClientHealthIndicatorProperties;
@@ -35,12 +37,12 @@
3537
import static java.util.Collections.emptyList;
3638
import static java.util.Collections.singletonList;
3739
import static org.assertj.core.api.Assertions.assertThat;
38-
import static org.mockito.Mockito.doThrow;
3940
import static org.mockito.Mockito.when;
4041

4142
/**
4243
* @author Tim Ysewyn
4344
* @author Chris Bono
45+
* @author Olga Maciaszek-Sharma
4446
*/
4547
@ExtendWith(MockitoExtension.class)
4648
class ReactiveDiscoveryClientHealthIndicatorTests {
@@ -78,6 +80,9 @@ public void shouldReturnUnknownStatusWhenNotInitialized() {
7880
@Test
7981
public void shouldReturnUpStatusWhenNotUsingServicesQueryAndProbeSucceeds() {
8082
when(properties.isUseServicesQuery()).thenReturn(false);
83+
ReactiveDiscoveryClient discoveryClient = new TestDiscoveryClient();
84+
ReactiveDiscoveryClientHealthIndicator indicator = new ReactiveDiscoveryClientHealthIndicator(discoveryClient,
85+
properties);
8186
Health expectedHealth = Health.status(new Status(Status.UP.getCode(), "")).build();
8287

8388
indicator.onApplicationEvent(new InstanceRegisteredEvent<>(this, null));
@@ -88,10 +93,10 @@ public void shouldReturnUpStatusWhenNotUsingServicesQueryAndProbeSucceeds() {
8893

8994
@Test
9095
public void shouldReturnDownStatusWhenNotUsingServicesQueryAndProbeFails() {
91-
when(properties.isUseServicesQuery()).thenReturn(false);
92-
RuntimeException ex = new RuntimeException("something went wrong");
93-
doThrow(ex).when(discoveryClient).probe();
94-
Health expectedHealth = Health.down(ex).build();
96+
ExceptionThrowingDiscoveryClient discoveryClient = new ExceptionThrowingDiscoveryClient();
97+
ReactiveDiscoveryClientHealthIndicator indicator = new ReactiveDiscoveryClientHealthIndicator(discoveryClient,
98+
properties);
99+
Health expectedHealth = Health.down(discoveryClient.exception).build();
95100

96101
indicator.onApplicationEvent(new InstanceRegisteredEvent<>(this, null));
97102
Mono<Health> health = indicator.health();
@@ -140,4 +145,44 @@ public void shouldReturnDownStatusWhenUsingServicesQueryAndCallFails() {
140145
StepVerifier.create(health).expectNext(expectedHealth).expectComplete().verify();
141146
}
142147

148+
static class TestDiscoveryClient implements ReactiveDiscoveryClient {
149+
150+
@Override
151+
public String description() {
152+
return "Test";
153+
}
154+
155+
@Override
156+
public Flux<ServiceInstance> getInstances(String serviceId) {
157+
return Flux.just(new DefaultServiceInstance());
158+
}
159+
160+
@Override
161+
public Flux<String> getServices() {
162+
return Flux.just("Test");
163+
}
164+
165+
}
166+
167+
static class ExceptionThrowingDiscoveryClient implements ReactiveDiscoveryClient {
168+
169+
RuntimeException exception = new RuntimeException("something went wrong");
170+
171+
@Override
172+
public String description() {
173+
return "Exception";
174+
}
175+
176+
@Override
177+
public Flux<ServiceInstance> getInstances(String serviceId) {
178+
throw new RuntimeException("Test!");
179+
}
180+
181+
@Override
182+
public Flux<String> getServices() {
183+
throw new RuntimeException("something went wrong");
184+
}
185+
186+
}
187+
143188
}

src/checkstyle/checkstyle-suppressions.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,5 @@
1717
<suppress files=".*RefreshScopeConfigurationTests.*" checks="JavadocStyle"/>
1818
<suppress files=".*RefreshScopeConfigurationTests.*" checks="JavadocMethod"/>
1919
<suppress files=".*CachingServiceInstanceListSupplierTests.*" checks="RegexpSinglelineJava"/>
20+
<suppress files=".*ReactiveDiscoveryClient.*" checks="JavadocVariable"/>
2021
</suppressions>

0 commit comments

Comments
 (0)