Browse Source

Merge remote-tracking branch 'origin/3.1.x'

pull/1206/head
Olga Maciaszek-Sharma 2 years ago
parent
commit
3bbd9524ad
  1. 42
      .github/workflows/maven.yml
  2. 30
      spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/ReactiveDiscoveryClient.java
  3. 49
      spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/health/reactive/ReactiveDiscoveryClientHealthIndicator.java
  4. 55
      spring-cloud-commons/src/test/java/org/springframework/cloud/client/discovery/health/reactive/ReactiveDiscoveryClientHealthIndicatorTests.java
  5. 1
      src/checkstyle/checkstyle-suppressions.xml

42
.github/workflows/maven.yml

@ -0,0 +1,42 @@
# This workflow will build a Java project with Maven
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
name: Build
on:
push:
branches: [ main, 3.1.x ]
pull_request:
branches: [ main, 3.1.x ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK
uses: actions/setup-java@v2
with:
distribution: 'temurin'
java-version: '17'
- name: Cache local Maven repository
uses: actions/cache@v2
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-
- name: Build with Maven
run: ./mvnw -s .settings.xml clean org.jacoco:jacoco-maven-plugin:prepare-agent install -U -P sonar -nsu --batch-mode -Dmaven.test.redirectTestOutputToFile=true -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn
- name: Publish Test Report
uses: mikepenz/action-junit-report@v2
if: always() # always run even if the previous step fails
with:
report_paths: '**/surefire-reports/TEST-*.xml'
- name: Archive code coverage results
uses: actions/upload-artifact@v2
with:
name: surefire-reports
path: '**/surefire-reports/*'

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

@ -16,7 +16,10 @@
package org.springframework.cloud.client.discovery; package org.springframework.cloud.client.discovery;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.ServiceInstance;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
@ -26,9 +29,12 @@ import org.springframework.core.Ordered;
* Eureka or consul.io. * Eureka or consul.io.
* *
* @author Tim Ysewyn * @author Tim Ysewyn
* @author Olga Maciaszek-Sharma
*/ */
public interface ReactiveDiscoveryClient extends Ordered { public interface ReactiveDiscoveryClient extends Ordered {
Log LOG = LogFactory.getLog(ReactiveDiscoveryClient.class);
/** /**
* Default order of the discovery client. * Default order of the discovery client.
*/ */
@ -60,11 +66,35 @@ public interface ReactiveDiscoveryClient extends Ordered {
* <p> * <p>
* The default implementation simply calls {@link #getServices()} - client * The default implementation simply calls {@link #getServices()} - client
* implementations can override with a lighter weight operation if they choose to. * implementations can override with a lighter weight operation if they choose to.
* @deprecated in favour of {@link ReactiveDiscoveryClient#reactiveProbe()}. This
* method should not be used as is, as it contains a bug - the method called within
* returns a {@link Flux}, which is not accessible for subscription or blocking from
* within. We are leaving it with a deprecation in order not to bring downstream
* implementations.
*/ */
@Deprecated
default void probe() { default void probe() {
if (LOG.isWarnEnabled()) {
LOG.warn("ReactiveDiscoveryClient#probe has been called. If you're calling this method directly, "
+ "use ReactiveDiscoveryClient#reactiveProbe instead.");
}
getServices(); getServices();
} }
/**
* Can be used to verify the client is still valid and able to make calls.
* <p>
* A successful invocation with no exception thrown implies the client is able to make
* calls.
* <p>
* The default implementation simply calls {@link #getServices()} and wraps it with a
* {@link Mono} - client implementations can override with a lighter weight operation
* if they choose to.
*/
default Mono<Void> reactiveProbe() {
return getServices().then();
}
/** /**
* Default implementation for getting order of discovery clients. * Default implementation for getting order of discovery clients.
* @return order * @return order

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

@ -33,21 +33,21 @@ import org.springframework.core.Ordered;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
/** /**
* A health indicator which indicates whether or not the discovery client has been * A health indicator which indicates whether the discovery client has been initialized.
* initialized.
* *
* @author Tim Ysewyn * @author Tim Ysewyn
* @author Chris Bono * @author Chris Bono
* @author Olga Maciaszek-Sharma
*/ */
public class ReactiveDiscoveryClientHealthIndicator public class ReactiveDiscoveryClientHealthIndicator
implements ReactiveDiscoveryHealthIndicator, Ordered, ApplicationListener<InstanceRegisteredEvent<?>> { implements ReactiveDiscoveryHealthIndicator, Ordered, ApplicationListener<InstanceRegisteredEvent<?>> {
private static final Log LOG = LogFactory.getLog(ReactiveDiscoveryClientHealthIndicator.class);
private final ReactiveDiscoveryClient discoveryClient; private final ReactiveDiscoveryClient discoveryClient;
private final DiscoveryClientHealthIndicatorProperties properties; private final DiscoveryClientHealthIndicatorProperties properties;
private final Log log = LogFactory.getLog(ReactiveDiscoveryClientHealthIndicator.class);
private AtomicBoolean discoveryInitialized = new AtomicBoolean(false); private AtomicBoolean discoveryInitialized = new AtomicBoolean(false);
private int order = Ordered.HIGHEST_PRECEDENCE; private int order = Ordered.HIGHEST_PRECEDENCE;
@ -60,14 +60,14 @@ public class ReactiveDiscoveryClientHealthIndicator
@Override @Override
public void onApplicationEvent(InstanceRegisteredEvent<?> event) { public void onApplicationEvent(InstanceRegisteredEvent<?> event) {
if (this.discoveryInitialized.compareAndSet(false, true)) { if (discoveryInitialized.compareAndSet(false, true)) {
this.log.debug("Discovery Client has been initialized"); LOG.debug("Discovery Client has been initialized");
} }
} }
@Override @Override
public Mono<Health> health() { public Mono<Health> health() {
if (this.discoveryInitialized.get()) { if (discoveryInitialized.get()) {
return doHealthCheck(); return doHealthCheck();
} }
else { else {
@ -78,38 +78,39 @@ public class ReactiveDiscoveryClientHealthIndicator
private Mono<Health> doHealthCheck() { private Mono<Health> doHealthCheck() {
// @formatter:off // @formatter:off
return Mono.just(this.properties.isUseServicesQuery()) return Mono.just(properties.isUseServicesQuery())
.flatMap(useServices -> useServices ? doHealthCheckWithServices() : doHealthCheckWithProbe()) .flatMap(useServices -> useServices ? doHealthCheckWithServices() : doHealthCheckWithProbe())
.onErrorResume(exception -> { .onErrorResume(exception -> {
this.log.error("Error", exception); if (LOG.isErrorEnabled()) {
LOG.error("Error", exception);
}
return Mono.just(Health.down().withException(exception).build()); return Mono.just(Health.down().withException(exception).build());
}); });
// @formatter:on // @formatter:on
} }
private Mono<Health> doHealthCheckWithProbe() { private Mono<Health> doHealthCheckWithProbe() {
// @formatter:off return discoveryClient.reactiveProbe().doOnError(exception -> {
return Mono.justOrEmpty(this.discoveryClient) if (LOG.isErrorEnabled()) {
.flatMap(client -> { LOG.error("Probe has failed.", exception);
client.probe(); }
return Mono.just(client); }).then(buildHealthUp(discoveryClient));
}) }
.map(client -> {
String description = (this.properties.isIncludeDescription()) ? client.description() : ""; private Mono<Health> buildHealthUp(ReactiveDiscoveryClient discoveryClient) {
return Health.status(new Status("UP", description)).build(); String description = (properties.isIncludeDescription()) ? discoveryClient.description() : "";
}); return Mono.just(Health.status(new Status("UP", description)).build());
// @formatter:on
} }
private Mono<Health> doHealthCheckWithServices() { private Mono<Health> doHealthCheckWithServices() {
// @formatter:off // @formatter:off
return Mono.justOrEmpty(this.discoveryClient) return Mono.justOrEmpty(discoveryClient)
.flatMapMany(ReactiveDiscoveryClient::getServices) .flatMapMany(ReactiveDiscoveryClient::getServices)
.collectList() .collectList()
.defaultIfEmpty(emptyList()) .defaultIfEmpty(emptyList())
.map(services -> { .map(services -> {
String description = (this.properties.isIncludeDescription()) ? String description = (properties.isIncludeDescription()) ?
this.discoveryClient.description() : ""; discoveryClient.description() : "";
return Health.status(new Status("UP", description)) return Health.status(new Status("UP", description))
.withDetail("services", services).build(); .withDetail("services", services).build();
}); });
@ -123,7 +124,7 @@ public class ReactiveDiscoveryClientHealthIndicator
@Override @Override
public int getOrder() { public int getOrder() {
return this.order; return order;
} }
public void setOrder(int order) { public void setOrder(int order) {

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

@ -27,6 +27,8 @@ import reactor.test.StepVerifier;
import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.Status; import org.springframework.boot.actuate.health.Status;
import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient; import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient;
import org.springframework.cloud.client.discovery.event.InstanceRegisteredEvent; import org.springframework.cloud.client.discovery.event.InstanceRegisteredEvent;
import org.springframework.cloud.client.discovery.health.DiscoveryClientHealthIndicatorProperties; import org.springframework.cloud.client.discovery.health.DiscoveryClientHealthIndicatorProperties;
@ -35,12 +37,12 @@ import org.springframework.core.Ordered;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
/** /**
* @author Tim Ysewyn * @author Tim Ysewyn
* @author Chris Bono * @author Chris Bono
* @author Olga Maciaszek-Sharma
*/ */
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class ReactiveDiscoveryClientHealthIndicatorTests { class ReactiveDiscoveryClientHealthIndicatorTests {
@ -78,6 +80,9 @@ class ReactiveDiscoveryClientHealthIndicatorTests {
@Test @Test
public void shouldReturnUpStatusWhenNotUsingServicesQueryAndProbeSucceeds() { public void shouldReturnUpStatusWhenNotUsingServicesQueryAndProbeSucceeds() {
when(properties.isUseServicesQuery()).thenReturn(false); when(properties.isUseServicesQuery()).thenReturn(false);
ReactiveDiscoveryClient discoveryClient = new TestDiscoveryClient();
ReactiveDiscoveryClientHealthIndicator indicator = new ReactiveDiscoveryClientHealthIndicator(discoveryClient,
properties);
Health expectedHealth = Health.status(new Status(Status.UP.getCode(), "")).build(); Health expectedHealth = Health.status(new Status(Status.UP.getCode(), "")).build();
indicator.onApplicationEvent(new InstanceRegisteredEvent<>(this, null)); indicator.onApplicationEvent(new InstanceRegisteredEvent<>(this, null));
@ -88,10 +93,10 @@ class ReactiveDiscoveryClientHealthIndicatorTests {
@Test @Test
public void shouldReturnDownStatusWhenNotUsingServicesQueryAndProbeFails() { public void shouldReturnDownStatusWhenNotUsingServicesQueryAndProbeFails() {
when(properties.isUseServicesQuery()).thenReturn(false); ExceptionThrowingDiscoveryClient discoveryClient = new ExceptionThrowingDiscoveryClient();
RuntimeException ex = new RuntimeException("something went wrong"); ReactiveDiscoveryClientHealthIndicator indicator = new ReactiveDiscoveryClientHealthIndicator(discoveryClient,
doThrow(ex).when(discoveryClient).probe(); properties);
Health expectedHealth = Health.down(ex).build(); Health expectedHealth = Health.down(discoveryClient.exception).build();
indicator.onApplicationEvent(new InstanceRegisteredEvent<>(this, null)); indicator.onApplicationEvent(new InstanceRegisteredEvent<>(this, null));
Mono<Health> health = indicator.health(); Mono<Health> health = indicator.health();
@ -140,4 +145,44 @@ class ReactiveDiscoveryClientHealthIndicatorTests {
StepVerifier.create(health).expectNext(expectedHealth).expectComplete().verify(); StepVerifier.create(health).expectNext(expectedHealth).expectComplete().verify();
} }
static class TestDiscoveryClient implements ReactiveDiscoveryClient {
@Override
public String description() {
return "Test";
}
@Override
public Flux<ServiceInstance> getInstances(String serviceId) {
return Flux.just(new DefaultServiceInstance());
}
@Override
public Flux<String> getServices() {
return Flux.just("Test");
}
}
static class ExceptionThrowingDiscoveryClient implements ReactiveDiscoveryClient {
RuntimeException exception = new RuntimeException("something went wrong");
@Override
public String description() {
return "Exception";
}
@Override
public Flux<ServiceInstance> getInstances(String serviceId) {
throw new RuntimeException("Test!");
}
@Override
public Flux<String> getServices() {
throw new RuntimeException("something went wrong");
}
}
} }

1
src/checkstyle/checkstyle-suppressions.xml

@ -18,4 +18,5 @@
<suppress files=".*Tests.*" checks="JavadocVariable"/> <suppress files=".*Tests.*" checks="JavadocVariable"/>
<suppress files=".*Tests.*" checks="JavadocMethod"/> <suppress files=".*Tests.*" checks="JavadocMethod"/>
<suppress files=".*Tests.*" checks="HideUtilityClassConstructor"/> <suppress files=".*Tests.*" checks="HideUtilityClassConstructor"/>
<suppress files=".*ReactiveDiscoveryClient.*" checks="JavadocVariable"/>
</suppressions> </suppressions>

Loading…
Cancel
Save