Skip to content
Permalink
Browse files

Merge pull request #101 from OpenWiseSolutions/feature/OHFJIRA-106-ci…

…rcuit-breaker

[OHFJIRA-106] : Circuit breaker
  • Loading branch information
kkovarik committed Nov 10, 2019
2 parents e2e9d3c + c832765 commit 172b7cdd1e33f565024d90b97ba145fe6a28a07a
Showing with 1,834 additions and 3 deletions.
  1. +91 −0 components/src/main/java/org/openhubframework/openhub/component/circuitbreaker/CircuitComponent.java
  2. +82 −0 components/src/main/java/org/openhubframework/openhub/component/circuitbreaker/CircuitEndpoint.java
  3. +62 −0 components/src/main/java/org/openhubframework/openhub/component/circuitbreaker/CircuitProducer.java
  4. +4 −0 components/src/main/java/org/openhubframework/openhub/component/circuitbreaker/package-info.java
  5. +1 −0 components/src/main/resources/META-INF/services/org/apache/camel/component/circuit
  6. +101 −0 ...nts/src/test/java/org/openhubframework/openhub/component/circuitbreaker/CircuitComponentTest.java
  7. +6 −2 core-api/src/main/java/org/openhubframework/openhub/api/exception/InternalErrorEnum.java
  8. +78 −0 core-spi/src/main/java/org/openhubframework/openhub/spi/circuitbreaker/CircuitBreaker.java
  9. +101 −0 core-spi/src/main/java/org/openhubframework/openhub/spi/circuitbreaker/CircuitConfiguration.java
  10. +22 −0 core-spi/src/main/java/org/openhubframework/openhub/spi/circuitbreaker/CircuitDownException.java
  11. +211 −0 core/src/main/java/org/openhubframework/openhub/core/circuitbreaker/AbstractCircuitBreaker.java
  12. +79 −0 ...c/main/java/org/openhubframework/openhub/core/circuitbreaker/CircuitBreakerAutoConfiguration.java
  13. +111 −0 core/src/main/java/org/openhubframework/openhub/core/circuitbreaker/CircuitBreakerHazelcastImpl.java
  14. +40 −0 core/src/main/java/org/openhubframework/openhub/core/circuitbreaker/CircuitBreakerInMemoryImpl.java
  15. +56 −0 core/src/main/java/org/openhubframework/openhub/core/circuitbreaker/CircuitState.java
  16. +31 −0 ...in/java/org/openhubframework/openhub/core/circuitbreaker/hazelcast/CircuitDownEntryProcessor.java
  17. +27 −0 ...ain/java/org/openhubframework/openhub/core/circuitbreaker/hazelcast/FailedCallEntryProcessor.java
  18. +37 −0 ...java/org/openhubframework/openhub/core/circuitbreaker/hazelcast/RemoveOldCallsEntryProcessor.java
  19. +27 −0 ...in/java/org/openhubframework/openhub/core/circuitbreaker/hazelcast/SuccessCallEntryProcessor.java
  20. +4 −0 core/src/main/java/org/openhubframework/openhub/core/circuitbreaker/hazelcast/package-info.java
  21. +4 −0 core/src/main/java/org/openhubframework/openhub/core/circuitbreaker/package-info.java
  22. +2 −1 core/src/main/resources/META-INF/spring.factories
  23. +140 −0 core/src/test/java/org/openhubframework/openhub/core/circuitbreaker/AbstractCircuitBreakerTest.java
  24. +259 −0 ...c/test/java/org/openhubframework/openhub/core/circuitbreaker/CircuitBreakerHazelcastImplTest.java
  25. +258 −0 ...rc/test/java/org/openhubframework/openhub/core/circuitbreaker/CircuitBreakerInMemoryImplTest.java
@@ -0,0 +1,91 @@
package org.openhubframework.openhub.component.circuitbreaker;

import java.util.Map;

import org.apache.camel.CamelContext;
import org.apache.camel.Endpoint;
import org.apache.camel.Produce;
import org.apache.camel.ProducerTemplate;
import org.apache.camel.impl.DefaultComponent;
import org.apache.camel.util.StringHelper;
import org.openhubframework.openhub.spi.circuitbreaker.CircuitBreaker;
import org.openhubframework.openhub.spi.circuitbreaker.CircuitConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Assert;

/**
* Custom component for simple use of circuit breaker.
*
* Usage:
* .to("circuit:<circuit-name>:<uri-to-call>"),
* where:
* <circuit-name> is unique name of circuit, recommended granularity
* is one circuit per target system.
* <uri-to-call> is uri to be invoked if circuit is up.
* Configuration:
* {@link CircuitConfiguration} in Exchange property {@link CircuitBreaker#CONFIGURATION_PROPERTY}
*
* @author Karel Kovarik
* @see CircuitBreaker
* @since 2.2
*/
public class CircuitComponent extends DefaultComponent {

/**
* CircuitBreaker interface implementation.
* If none is provided, component cannot be used.
*/
@Autowired
private CircuitBreaker circuitBreaker;

/**
* Camel producer template.
*/
@Produce
private ProducerTemplate producerTemplate;

/**
* CamelContext.
*/
@Autowired
private CamelContext camelContext;


@Override
protected Endpoint createEndpoint(String uri, String remaining, Map<String, Object> parameters) throws Exception {
final CircuitEndpoint endpoint = new CircuitEndpoint(uri, this);

final String endpointURI = StringHelper.after(uri, ":");
Assert.hasText(endpointURI, "the endpointURI must not be empty");

final String name = StringHelper.before(endpointURI, ":");
Assert.hasText(name, "the circuitName must not be empty.");
final String trimmedName = name.replaceAll("/", "").trim();
Assert.hasText(trimmedName, "the trimmed circuitName must not be empty.");
final String targetUri = StringHelper.after(endpointURI, ":");
Assert.hasText(targetUri, "the targetUri must not be empty.");

endpoint.setCircuitName(trimmedName);
endpoint.setTargetUri(targetUri);
return endpoint;
}

/**
* Get instance of CircuitBreaker implementation.
*/
protected CircuitBreaker getCircuitBreaker() {
return circuitBreaker;
}

/**
* Get instance of producerTemplate.
*/
protected ProducerTemplate getProducerTemplate() {
return producerTemplate;
}

@Override
protected void validateParameters(String uri, Map<String, Object> parameters, String optionPrefix) {
// do nothing, do not call validation from parent - DefaultComponent
}
}
@@ -0,0 +1,82 @@
package org.openhubframework.openhub.component.circuitbreaker;

import org.apache.camel.Consumer;
import org.apache.camel.Processor;
import org.apache.camel.Producer;
import org.apache.camel.ProducerTemplate;
import org.apache.camel.impl.DefaultEndpoint;
import org.openhubframework.openhub.spi.circuitbreaker.CircuitBreaker;


/**
* Circuit breaker endpoint.
*
* @author Karel Kovarik
* @since 2.2
*/
public class CircuitEndpoint extends DefaultEndpoint {

private String circuitName;
private String targetUri;

public CircuitEndpoint(String endpointUri, CircuitComponent component) {
super(endpointUri, component);
}

@Override
public Producer createProducer() throws Exception {
return new CircuitProducer(this);
}

@Override
public Consumer createConsumer(Processor processor) throws Exception {
throw new UnsupportedOperationException("Circuit breaker does not support consumer endpoint.");
}

@Override
public boolean isSingleton() {
return true;
}

/**
* Get circuit name.
*/
public String getCircuitName() {
return circuitName;
}

/**
* Set circuit name.
*/
public void setCircuitName(String circuitName) {
this.circuitName = circuitName;
}

/**
* Get target uri.
*/
public String getTargetUri() {
return targetUri;
}

/**
* Set target uri.
*/
public void setTargetUri(String targetUri) {
this.targetUri = targetUri;
}

/**
* Get circuit breaker instance.
*/
protected CircuitBreaker getCircuitBreaker() {
return ((CircuitComponent) getComponent()).getCircuitBreaker();
}

/**
* Get producer template instance.
*/
protected ProducerTemplate getProducerTemplate() {
return ((CircuitComponent) getComponent()).getProducerTemplate();
}
}
@@ -0,0 +1,62 @@
package org.openhubframework.openhub.component.circuitbreaker;

import static org.springframework.util.StringUtils.hasText;

import org.apache.camel.Exchange;
import org.apache.camel.impl.DefaultProducer;
import org.openhubframework.openhub.spi.circuitbreaker.CircuitBreaker;
import org.openhubframework.openhub.spi.circuitbreaker.CircuitConfiguration;
import org.springframework.util.Assert;

/**
* Camel Producer for Circuit breaker component.
*
* @author Karel Kovarik
* @see org.apache.camel.Producer
* @see CircuitComponent
* @since 2.2
*/
public class CircuitProducer extends DefaultProducer {

/**
* New instance of circuit producer.
*
* @param endpoint the related circuit endpoint.
*/
public CircuitProducer(CircuitEndpoint endpoint) {
super(endpoint);
}

@Override
public void process(Exchange exchange) throws Exception {
// verify configuration is present, otherwise fail as it is misconfigured
final CircuitConfiguration circuitConfiguration = exchange.getProperty(
CircuitBreaker.CONFIGURATION_PROPERTY, CircuitConfiguration.class);
Assert.notNull(circuitConfiguration, "the circuitConfiguration was not found,"
+ "it is expected to be set in exchange property with name ["
+ CircuitBreaker.CONFIGURATION_PROPERTY + "].");

// fill circuitName (if not set in configuration already)
if (!hasText(circuitConfiguration.getCircuitName())) {
circuitConfiguration.setCircuitName(getCircuitEndpoint().getCircuitName());
}

final CircuitEndpoint endpoint = getCircuitEndpoint();
try {
// check circuit state, will throw exception if circuit is down
endpoint.getCircuitBreaker().checkCircuitIsOpen().process(exchange);
// invoke targetUri
exchange = endpoint.getProducerTemplate().send(endpoint.getTargetUri(), exchange);
} finally {
// update circuitState
endpoint.getCircuitBreaker().updateCircuitState().process(exchange);
}
}

/**
* Get related circuit endpoint.
*/
public CircuitEndpoint getCircuitEndpoint() {
return (CircuitEndpoint) getEndpoint();
}
}
@@ -0,0 +1,4 @@
/**
* "circuit" camel component.
*/
package org.openhubframework.openhub.component.circuitbreaker;
@@ -0,0 +1 @@
class=org.openhubframework.openhub.component.circuitbreaker.CircuitComponent
@@ -0,0 +1,101 @@
package org.openhubframework.openhub.component.circuitbreaker;

import org.apache.camel.EndpointInject;
import org.apache.camel.Produce;
import org.apache.camel.ProducerTemplate;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.mock.MockEndpoint;
import org.junit.Before;
import org.junit.Test;
import org.openhubframework.openhub.api.route.AbstractBasicRoute;
import org.openhubframework.openhub.component.AbstractComponentsTest;
import org.openhubframework.openhub.spi.circuitbreaker.CircuitBreaker;
import org.openhubframework.openhub.spi.circuitbreaker.CircuitConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.test.context.TestPropertySource;

/**
* Simple test suite for {@link CircuitComponent}.
*
* @author Karel Kovarik
* @since 2.2
*/
@TestPropertySource(properties = {
"ohf.circuitbreaker.enabled=true",
"ohf.circuitbreaker.impl=org.openhubframework.openhub.core.circuitbreaker.CircuitBreakerInMemoryImpl",
"ohf.foo.circuitbreaker.enabled=true",
"ohf.foo.circuitbreaker.thresholdPercentage=90",
"ohf.foo.circuitbreaker.windowSizeInMillis=10000",
"ohf.foo.circuitbreaker.minimalCountInWindow=10",
"ohf.foo.circuitbreaker.sleepInMillis=30000",
})
@EnableConfigurationProperties(CircuitComponentTest.FooCircuitConfigurationProperties.class)
public class CircuitComponentTest extends AbstractComponentsTest {

@Produce(uri = "direct:start")
private ProducerTemplate producer;

@EndpointInject(uri = "mock:test")
private MockEndpoint mock;

@Autowired
private FooCircuitConfigurationProperties fooCircuitConfigProps;

@Before
public void prepareRoutes() throws Exception {
final RouteBuilder testedRoute = new AbstractBasicRoute() {
@Override
public void doConfigure() throws Exception {
from("direct:start")
// configure circuit breaker component
.setProperty(CircuitBreaker.CONFIGURATION_PROPERTY, constant(fooCircuitConfigProps))
// circuit:<circuit-name>:<uri>
.to("circuit:foo:mock:test");
}
};
// add to camel context
getCamelContext().addRoutes(testedRoute);
}

@Test
public void test_passThrough() throws Exception {

mock.expectedMessageCount(2);
producer.sendBody("payload-1");
producer.sendBody("payload-2");
mock.assertIsSatisfied();
}

@Test
public void test_circuitDown() throws Exception {
fooCircuitConfigProps.setMinimalCountInWindow(1L);
fooCircuitConfigProps.setThresholdPercentage(1);
fooCircuitConfigProps.setSleepInMillis(60_000);
fooCircuitConfigProps.setWindowSizeInMillis(30_000);

mock.whenExchangeReceived(1, exchange -> {
throw new RuntimeException("Something went wrong.");
});

mock.expectedMessageCount(1);
sendBodyInTryCatch("payload-1");
sendBodyInTryCatch("payload-2");
mock.assertIsSatisfied();
}

// util methods
protected void sendBodyInTryCatch(Object body) {
try {
producer.sendBody(body);
} catch (Exception ex) {
// do nothing
}
}

@ConfigurationProperties(prefix = "ohf.foo.circuitbreaker")
public static class FooCircuitConfigurationProperties extends CircuitConfiguration {
// nothing in here, already inherited from CircuitConfiguration
}
}
@@ -139,9 +139,13 @@
/**
* the configuration error - error state of configuration item
*/
E122("the configuration error - error state of configuration item");

E122("the configuration error - error state of configuration item"),

/**
* Circuit breaker - circuit is down exception.
*/
E123("the circuit breaker is down, target route will not be invoked.")
;

private String errDesc;

0 comments on commit 172b7cd

Please sign in to comment.
You can’t perform that action at this time.