Skip to content

Commit

Permalink
Issue ReactiveX#559: Support of @CIRCUITBREAKER annotation along with…
Browse files Browse the repository at this point in the history
… @FeignClient (ReactiveX#579)
  • Loading branch information
Harish Moyal authored and RobWin committed Aug 26, 2019
1 parent f8d0e5a commit 1f0e62f
Show file tree
Hide file tree
Showing 20 changed files with 465 additions and 48 deletions.
8 changes: 7 additions & 1 deletion libraries.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ ext {
wiremockVersion = '2.22.0'
validationApiVersion = '2.0.1.Final'
kotlinCoroutinesVersion = '1.2.0'
springBootOpenFeignVersion= '2.1.2.RELEASE'

libraries = [
// compile
Expand Down Expand Up @@ -121,7 +122,12 @@ ext {

// Kotlin addon
kotlin_stdlib: "org.jetbrains.kotlin:kotlin-stdlib-jdk8",
kotlin_coroutines: "org.jetbrains.kotlinx:kotlinx-coroutines-core:${kotlinCoroutinesVersion}"
kotlin_coroutines: "org.jetbrains.kotlinx:kotlinx-coroutines-core:${kotlinCoroutinesVersion}",

// Spring cloud addon
spring_cloud_openfeign_core: "org.springframework.cloud:spring-cloud-openfeign-core:${springBootOpenFeignVersion}",
spring_cloud_starter_openfeign: "org.springframework.cloud:spring-cloud-starter-openfeign:${springBootOpenFeignVersion}",
spring_cloud_context: "org.springframework.cloud:spring-cloud-context:${springBootOpenFeignVersion}"
]

}
4 changes: 4 additions & 0 deletions resilience4j-spring-boot2/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ dependencies {
testCompile(libraries.metrics)
testCompile(libraries.spring_boot2_actuator)
testCompile project(':resilience4j-micrometer')
testCompile (libraries.spring_cloud_openfeign_core)
testCompile (libraries.spring_cloud_starter_openfeign)
testCompile (libraries.spring_cloud_context)
testCompile (libraries.feign_wiremock)
}

compileJava.dependsOn(processResources)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,21 @@
*/
package io.github.resilience4j.bulkhead;

import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.junit.WireMockRule;
import io.github.resilience4j.bulkhead.autoconfigure.BulkheadProperties;
import io.github.resilience4j.bulkhead.autoconfigure.ThreadPoolBulkheadProperties;
import io.github.resilience4j.bulkhead.configure.BulkheadAspect;
import io.github.resilience4j.bulkhead.event.BulkheadEvent;
import io.github.resilience4j.common.bulkhead.monitoring.endpoint.BulkheadEndpointResponse;
import io.github.resilience4j.common.bulkhead.monitoring.endpoint.BulkheadEventDTO;
import io.github.resilience4j.common.bulkhead.monitoring.endpoint.BulkheadEventsEndpointResponse;
import io.github.resilience4j.service.test.DummyFeignClient;
import io.github.resilience4j.service.test.TestApplication;
import io.github.resilience4j.service.test.bulkhead.BulkheadDummyService;
import io.github.resilience4j.service.test.bulkhead.BulkheadReactiveDummyService;
import org.apache.commons.lang3.StringUtils;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
Expand All @@ -36,6 +41,7 @@
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
Expand Down Expand Up @@ -73,6 +79,89 @@ public class BulkheadAutoConfigurationTest {
@Autowired
private TestRestTemplate restTemplate;

@Autowired
private DummyFeignClient dummyFeignClient;

@Rule
public WireMockRule wireMockRule = new WireMockRule(8090);


/**
* This test verifies that the combination of @FeignClient and @Bulkhead annotation works as same as @Bulkhead alone works with any normal service class
*/
@Test
@DirtiesContext
public void testFeignClient() {

int expectedConcurrentCalls = 3;
int expectedRejectedCalls = 2;
WireMock.stubFor(WireMock
.get(WireMock.urlEqualTo("/sample/"))
.willReturn(WireMock.aResponse().withStatus(200).withBody("This is successful call")
.withFixedDelay(3000))
);

CompletableFuture<Void> future1 = CompletableFuture.runAsync(this::callService);
CompletableFuture<Void> future2 = CompletableFuture.runAsync(this::callService);
CompletableFuture<Void> future3 = CompletableFuture.runAsync(this::callService);
CompletableFuture<Void> future4 = CompletableFuture.runAsync(this::callService);
CompletableFuture<Void> future5 = CompletableFuture.runAsync(this::callService);

int actualSuccessfulCalls = 0;
try {
future1.get();
actualSuccessfulCalls++;
} catch (Exception e) {
// Ignore me
}
try {
future2.get();
actualSuccessfulCalls++;
} catch (Exception e) {
// Ignore me
}

try {
future3.get();
actualSuccessfulCalls++;
} catch (Exception e) {
// Ignore me
}

try {
future4.get();
actualSuccessfulCalls++;
} catch (Exception e) {
// Ignore me
}

try {
future5.get();
actualSuccessfulCalls++;
} catch (Exception e) {
// Ignore me
}
int actualRejectedCalls = 0;

ResponseEntity<BulkheadEventsEndpointResponse> bulkheadEventList = restTemplate.getForEntity("/actuator/bulkheadevents", BulkheadEventsEndpointResponse.class);
List<BulkheadEventDTO> bulkheadEvents = bulkheadEventList.getBody().getBulkheadEvents();
for (BulkheadEventDTO eventDTO : bulkheadEvents) {
if (eventDTO.getType().equals(BulkheadEvent.Type.CALL_REJECTED)) {
actualRejectedCalls++;
}
}
Bulkhead bulkhead = bulkheadRegistry.bulkhead("dummyFeignClient");

assertThat(bulkhead).isNotNull();
assertThat(bulkhead.getMetrics().getMaxAllowedConcurrentCalls()).isEqualTo(3);
assertThat(bulkhead.getMetrics().getAvailableConcurrentCalls()).isEqualTo(3);
assertThat(actualSuccessfulCalls).isEqualTo(expectedConcurrentCalls);
assertThat(actualRejectedCalls).isEqualTo(expectedRejectedCalls);
}

private void callService() {
dummyFeignClient.doSomething(StringUtils.EMPTY);
}

/**
* The test verifies that a Bulkhead instance is created and configured properly when the BulkheadDummyService is invoked and
Expand Down Expand Up @@ -104,7 +193,7 @@ public void testBulkheadAutoConfigurationThreadPool() {
// Test Actuator endpoints

ResponseEntity<BulkheadEndpointResponse> bulkheadList = restTemplate.getForEntity("/actuator/bulkheads", BulkheadEndpointResponse.class);
assertThat(bulkheadList.getBody().getBulkheads()).hasSize(4).containsExactly("backendA", "backendB", "backendB", "backendC");
assertThat(bulkheadList.getBody().getBulkheads()).hasSize(5).containsExactly("backendA", "backendB", "backendB", "backendC", "dummyFeignClient");

for (int i = 0; i < 5; i++) {
es.submit(dummyService::doSomethingAsync);
Expand Down Expand Up @@ -157,7 +246,7 @@ public void testBulkheadAutoConfiguration() {
// Test Actuator endpoints

ResponseEntity<BulkheadEndpointResponse> bulkheadList = restTemplate.getForEntity("/actuator/bulkheads", BulkheadEndpointResponse.class);
assertThat(bulkheadList.getBody().getBulkheads()).hasSize(4).containsExactly("backendA", "backendB", "backendB", "backendC");
assertThat(bulkheadList.getBody().getBulkheads()).hasSize(5).containsExactly("backendA", "backendB", "backendB", "backendC", "dummyFeignClient");

for (int i = 0; i < 5; i++) {
es.submit(dummyService::doSomething);
Expand Down Expand Up @@ -276,7 +365,7 @@ private void commonAssertions() {
// Test Actuator endpoints

ResponseEntity<BulkheadEndpointResponse> bulkheadList = restTemplate.getForEntity("/actuator/bulkheads", BulkheadEndpointResponse.class);
assertThat(bulkheadList.getBody().getBulkheads()).hasSize(4).containsExactly("backendA", "backendB", "backendB", "backendC");
assertThat(bulkheadList.getBody().getBulkheads()).hasSize(5).containsExactly("backendA", "backendB", "backendB", "backendC", "dummyFeignClient");

ResponseEntity<BulkheadEventsEndpointResponse> bulkheadEventList = restTemplate.getForEntity("/actuator/bulkheadevents", BulkheadEventsEndpointResponse.class);
List<BulkheadEventDTO> bulkheadEvents = bulkheadEventList.getBody().getBulkheadEvents();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ public void testCircuitBreakerAutoConfigurationReactiveRxJava2() throws IOExcept

// expect circuitbreakers actuator endpoint contains both circuitbreakers
ResponseEntity<CircuitBreakerEndpointResponse> circuitBreakerList = restTemplate.getForEntity("/actuator/circuitbreakers", CircuitBreakerEndpointResponse.class);
assertThat(circuitBreakerList.getBody().getCircuitBreakers()).hasSize(4).containsExactly("backendA", "backendB", "backendSharedA", "backendSharedB");
assertThat(circuitBreakerList.getBody().getCircuitBreakers()).hasSize(5).containsExactly("backendA", "backendB", "backendSharedA", "backendSharedB", "dummyFeignClient");

// expect circuitbreaker-event actuator endpoint recorded both events
ResponseEntity<CircuitBreakerEventsEndpointResponse> circuitBreakerEventList = restTemplate.getForEntity("/actuator/circuitbreakerevents", CircuitBreakerEventsEndpointResponse.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,18 @@
*/
package io.github.resilience4j.circuitbreaker;

import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.junit.WireMockRule;
import io.github.resilience4j.circuitbreaker.autoconfigure.CircuitBreakerProperties;
import io.github.resilience4j.circuitbreaker.configure.CircuitBreakerAspect;
import io.github.resilience4j.common.circuitbreaker.monitoring.endpoint.CircuitBreakerEndpointResponse;
import io.github.resilience4j.common.circuitbreaker.monitoring.endpoint.CircuitBreakerEventsEndpointResponse;
import io.github.resilience4j.service.test.DummyFeignClient;
import io.github.resilience4j.service.test.DummyService;
import io.github.resilience4j.service.test.ReactiveDummyService;
import io.github.resilience4j.service.test.TestApplication;
import org.apache.commons.lang3.StringUtils;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
Expand Down Expand Up @@ -63,6 +68,46 @@ public class CircuitBreakerAutoConfigurationTest {
@Autowired
private ReactiveDummyService reactiveDummyService;

@Autowired
private DummyFeignClient dummyFeignClient;

@Rule
public WireMockRule wireMockRule = new WireMockRule(8090);


/**
* This test verifies that the combination of @FeignClient and @CircuitBreaker annotation works as same as @CircuitBreaker alone works with any normal service class
*/
@Test
@DirtiesContext
public void testFeignClient() {

WireMock.stubFor(WireMock.get(WireMock.urlEqualTo("/sample/"))
.willReturn(WireMock.aResponse().withStatus(200).withBody("This is successful call")));
WireMock.stubFor(WireMock.get(WireMock.urlMatching("^.*\\/sample\\/error.*$"))
.willReturn(WireMock.aResponse().withStatus(400).withBody("This is error")));

try {
dummyFeignClient.doSomething("error");
} catch (Exception e) {
// Ignore the error, we want to increase the error counts
}
try {
dummyFeignClient.doSomething("errorAgain");
} catch (Exception e) {
// Ignore the error, we want to increase the error counts
}
dummyFeignClient.doSomething(StringUtils.EMPTY);
dummyFeignClient.doSomething(StringUtils.EMPTY);

CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("dummyFeignClient");
assertThat(circuitBreaker).isNotNull();
assertThat(circuitBreaker.getMetrics().getNumberOfBufferedCalls()).isEqualTo(4);
assertThat(circuitBreaker.getMetrics().getNumberOfSuccessfulCalls()).isEqualTo(2);
assertThat(circuitBreaker.getMetrics().getNumberOfFailedCalls()).isEqualTo(2);
assertThat(circuitBreaker.getCircuitBreakerConfig().getSlidingWindowSize()).isEqualTo(18);
assertThat(circuitBreaker.getCircuitBreakerConfig().getPermittedNumberOfCallsInHalfOpenState()).isEqualTo(6);
}
/**
* The test verifies that a CircuitBreaker instance is created and configured properly when the DummyService is invoked and
* that the CircuitBreaker records successful and failed calls.
Expand Down Expand Up @@ -99,7 +144,7 @@ public void testCircuitBreakerAutoConfiguration() throws IOException {

// expect circuitbreakers actuator endpoint contains all circuitbreakers
ResponseEntity<CircuitBreakerEndpointResponse> circuitBreakerList = restTemplate.getForEntity("/actuator/circuitbreakers", CircuitBreakerEndpointResponse.class);
assertThat(circuitBreakerList.getBody().getCircuitBreakers()).hasSize(5).containsExactly("backendA", "backendB", "backendSharedA", "backendSharedB", "dynamicBackend");
assertThat(circuitBreakerList.getBody().getCircuitBreakers()).hasSize(6).containsExactly("backendA", "backendB", "backendSharedA", "backendSharedB", "dummyFeignClient", "dynamicBackend");

// expect circuitbreaker-event actuator endpoint recorded all events
ResponseEntity<CircuitBreakerEventsEndpointResponse> circuitBreakerEventList = restTemplate.getForEntity("/actuator/circuitbreakerevents", CircuitBreakerEventsEndpointResponse.class);
Expand Down Expand Up @@ -186,7 +231,7 @@ public void testCircuitBreakerAutoConfigurationAsync() throws IOException, Execu

// expect circuitbreakers actuator endpoint contains both circuitbreakers
ResponseEntity<CircuitBreakerEndpointResponse> circuitBreakerList = restTemplate.getForEntity("/actuator/circuitbreakers", CircuitBreakerEndpointResponse.class);
assertThat(circuitBreakerList.getBody().getCircuitBreakers()).hasSize(4).containsExactly("backendA", "backendB", "backendSharedA", "backendSharedB");
assertThat(circuitBreakerList.getBody().getCircuitBreakers()).hasSize(5).containsExactly("backendA", "backendB", "backendSharedA", "backendSharedB", "dummyFeignClient");

// expect circuitbreaker-event actuator endpoint recorded both events
ResponseEntity<CircuitBreakerEventsEndpointResponse> circuitBreakerEventList = restTemplate.getForEntity("/actuator/circuitbreakerevents", CircuitBreakerEventsEndpointResponse.class);
Expand Down Expand Up @@ -238,7 +283,7 @@ public void testCircuitBreakerAutoConfigurationReactive() throws IOException {

// expect circuitbreakers actuator endpoint contains all circuitbreakers
ResponseEntity<CircuitBreakerEndpointResponse> circuitBreakerList = restTemplate.getForEntity("/actuator/circuitbreakers", CircuitBreakerEndpointResponse.class);
assertThat(circuitBreakerList.getBody().getCircuitBreakers()).hasSize(4).containsExactly("backendA", "backendB", "backendSharedA", "backendSharedB");
assertThat(circuitBreakerList.getBody().getCircuitBreakers()).hasSize(5).containsExactly("backendA", "backendB", "backendSharedA", "backendSharedB", "dummyFeignClient");

// expect circuitbreaker-event actuator endpoint recorded both events
ResponseEntity<CircuitBreakerEventsEndpointResponse> circuitBreakerEventList = restTemplate.getForEntity("/actuator/circuitbreakerevents", CircuitBreakerEventsEndpointResponse.class);
Expand Down
Loading

0 comments on commit 1f0e62f

Please sign in to comment.